Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/merge.py @ 18778:1ef89df2c248
rebase: fix --collapse when a file was added then removed
When a series of commits first adds a file and then removes it,
hg rebase --collapse prompts whether to keep the file or delete it. This is
due to it reusing the branch merge code. In a noninteractive terminal it
defaults to keeping the file, which results in a collapsed commit that is
has a file that should be deleted. This bug resulted in developers accidentally
commiting unintentional changes to our repo twice today, so it's fairly
important to get fixed.
This change allows rebase --collapse to tell the merge code to accept the
latest version every time without prompting.
Adds a test as well.
author | Durham Goode <durham@fb.com> |
---|---|
date | Fri, 15 Mar 2013 11:23:29 -0700 |
parents | e556659340f0 |
children | 0705ad73e878 |
comparison
equal
deleted
inserted
replaced
18777:8048c519dc6a | 18778:1ef89df2c248 |
---|---|
183 if f not in mctx: | 183 if f not in mctx: |
184 actions.append((f, "f", None, "forget removed")) | 184 actions.append((f, "f", None, "forget removed")) |
185 | 185 |
186 return actions | 186 return actions |
187 | 187 |
188 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial): | 188 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial, |
189 acceptremote=False): | |
189 """ | 190 """ |
190 Merge p1 and p2 with ancestor pa and generate merge action list | 191 Merge p1 and p2 with ancestor pa and generate merge action list |
191 | 192 |
192 branchmerge and force are as passed in to update | 193 branchmerge and force are as passed in to update |
193 partial = function to filter file lists | 194 partial = function to filter file lists |
195 acceptremote = accept the incoming changes without prompting | |
194 """ | 196 """ |
195 | 197 |
196 overwrite = force and not branchmerge | 198 overwrite = force and not branchmerge |
197 actions, copy, movewithdir = [], {}, {} | 199 actions, copy, movewithdir = [], {}, {} |
198 | 200 |
329 raise util.Abort(_("untracked files in working directory differ " | 331 raise util.Abort(_("untracked files in working directory differ " |
330 "from files in requested revision")) | 332 "from files in requested revision")) |
331 | 333 |
332 for f, m in sorted(prompts): | 334 for f, m in sorted(prompts): |
333 if m == "cd": | 335 if m == "cd": |
334 if repo.ui.promptchoice( | 336 if acceptremote: |
337 actions.append((f, "r", None, "remote delete")) | |
338 elif repo.ui.promptchoice( | |
335 _("local changed %s which remote deleted\n" | 339 _("local changed %s which remote deleted\n" |
336 "use (c)hanged version or (d)elete?") % f, | 340 "use (c)hanged version or (d)elete?") % f, |
337 (_("&Changed"), _("&Delete")), 0): | 341 (_("&Changed"), _("&Delete")), 0): |
338 actions.append((f, "r", None, "prompt delete")) | 342 actions.append((f, "r", None, "prompt delete")) |
339 else: | 343 else: |
340 actions.append((f, "a", None, "prompt keep")) | 344 actions.append((f, "a", None, "prompt keep")) |
341 elif m == "dc": | 345 elif m == "dc": |
342 if repo.ui.promptchoice( | 346 if acceptremote: |
347 actions.append((f, "g", (m2.flags(f),), "remote recreating")) | |
348 elif repo.ui.promptchoice( | |
343 _("remote changed %s which local deleted\n" | 349 _("remote changed %s which local deleted\n" |
344 "use (c)hanged version or leave (d)eleted?") % f, | 350 "use (c)hanged version or leave (d)eleted?") % f, |
345 (_("&Changed"), _("&Deleted")), 0) == 0: | 351 (_("&Changed"), _("&Deleted")), 0) == 0: |
346 actions.append((f, "g", (m2.flags(f),), "prompt recreating")) | 352 actions.append((f, "g", (m2.flags(f),), "prompt recreating")) |
347 else: assert False, m | 353 else: assert False, m |
510 ms.commit() | 516 ms.commit() |
511 progress(_updating, None, total=numupdates, unit=_files) | 517 progress(_updating, None, total=numupdates, unit=_files) |
512 | 518 |
513 return updated, merged, removed, unresolved | 519 return updated, merged, removed, unresolved |
514 | 520 |
515 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial): | 521 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial, |
522 acceptremote=False): | |
516 "Calculate the actions needed to merge mctx into tctx" | 523 "Calculate the actions needed to merge mctx into tctx" |
517 actions = [] | 524 actions = [] |
518 folding = not util.checkcase(repo.path) | 525 folding = not util.checkcase(repo.path) |
519 if folding: | 526 if folding: |
520 # collision check is not needed for clean update | 527 # collision check is not needed for clean update |
524 else: | 531 else: |
525 _checkcollision(mctx, (tctx, ancestor)) | 532 _checkcollision(mctx, (tctx, ancestor)) |
526 actions += manifestmerge(repo, tctx, mctx, | 533 actions += manifestmerge(repo, tctx, mctx, |
527 ancestor, | 534 ancestor, |
528 branchmerge, force, | 535 branchmerge, force, |
529 partial) | 536 partial, acceptremote) |
530 if tctx.rev() is None: | 537 if tctx.rev() is None: |
531 actions += _forgetremoved(tctx, mctx, branchmerge) | 538 actions += _forgetremoved(tctx, mctx, branchmerge) |
532 return actions | 539 return actions |
533 | 540 |
534 def recordupdates(repo, actions, branchmerge): | 541 def recordupdates(repo, actions, branchmerge): |
600 | 607 |
601 node = the node to update to, or None if unspecified | 608 node = the node to update to, or None if unspecified |
602 branchmerge = whether to merge between branches | 609 branchmerge = whether to merge between branches |
603 force = whether to force branch merging or file overwriting | 610 force = whether to force branch merging or file overwriting |
604 partial = a function to filter file lists (dirstate not updated) | 611 partial = a function to filter file lists (dirstate not updated) |
605 mergeancestor = if false, merging with an ancestor (fast-forward) | 612 mergeancestor = whether it is merging with an ancestor. If true, |
606 is only allowed between different named branches. This flag | 613 we should accept the incoming changes for any prompts that occur. |
607 is used by rebase extension as a temporary fix and should be | 614 If false, merging with an ancestor (fast-forward) is only allowed |
608 avoided in general. | 615 between different named branches. This flag is used by rebase extension |
616 as a temporary fix and should be avoided in general. | |
609 | 617 |
610 The table below shows all the behaviors of the update command | 618 The table below shows all the behaviors of the update command |
611 given the -c and -C or no options, whether the working directory | 619 given the -c and -C or no options, whether the working directory |
612 is dirty, whether a revision is specified, and the relationship of | 620 is dirty, whether a revision is specified, and the relationship of |
613 the parent rev to the target rev (linear, on the same named | 621 the parent rev to the target rev (linear, on the same named |
691 # Allow jumping branches if clean and specific rev given | 699 # Allow jumping branches if clean and specific rev given |
692 pa = p1 | 700 pa = p1 |
693 | 701 |
694 ### calculate phase | 702 ### calculate phase |
695 actions = calculateupdates(repo, wc, p2, pa, | 703 actions = calculateupdates(repo, wc, p2, pa, |
696 branchmerge, force, partial) | 704 branchmerge, force, partial, mergeancestor) |
697 | 705 |
698 ### apply phase | 706 ### apply phase |
699 if not branchmerge: # just jump to the new rev | 707 if not branchmerge: # just jump to the new rev |
700 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' | 708 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' |
701 if not partial: | 709 if not partial: |