rebase: respect checkunknown and checkignored in more cases
authorSiddharth Agarwal <sid0@fb.com>
Wed, 03 Feb 2016 13:12:06 -0800
changeset 28022 e397b58c0563
parent 28021 d0d805df6a44
child 28023 b7bf2b0731be
rebase: respect checkunknown and checkignored in more cases checkunknown and checkignored are currently respected for updates and regular merges, but not for certain kinds of rebases. To be precise, they aren't respected for rebases when: (1) we're rebasing while currently on the destination commit, and (2) an untracked or ignored file F is currently in the working copy, and (3) the same file F is in a source commit, and (4) F has different contents in the source commit. This happens because rebases set force to True when calling merge.update. Setting force to True makes a lot of sense in general, but it turns out the force option is overloaded: there's a deprecated '--force' option in merge that allows you to merge in outstanding changes, including changes in untracked files. We use the 'mergeforce' parameter to tell those two cases apart. I think the behavior during rebases when checkunknown is 'abort' (the default) is wrong -- we should abort on or overwrite differing untracked files, not try to merge them in. However that currently breaks rebases by aborting in the middle -- we need better handling for that case before we can change the default.
mercurial/merge.py
tests/test-rebase-scenario-global.t
--- a/mercurial/merge.py	Wed Feb 03 13:11:34 2016 -0800
+++ b/mercurial/merge.py	Wed Feb 03 13:12:06 2016 -0800
@@ -634,6 +634,39 @@
         unknownconflicts = conflicts - ignoredconflicts
         collectconflicts(ignoredconflicts, ignoredconfig)
         collectconflicts(unknownconflicts, unknownconfig)
+    else:
+        for f, (m, args, msg) in actions.iteritems():
+            if m == 'cm':
+                fl2, anc = args
+                different = _checkunknownfile(repo, wctx, mctx, f)
+                if repo.dirstate._ignore(f):
+                    config = ignoredconfig
+                else:
+                    config = unknownconfig
+
+                # The behavior when force is True is described by this table:
+                #  config  different  mergeforce  |    action    backup
+                #    *         n          *       |      get        n
+                #    *         y          y       |     merge       -
+                #   abort      y          n       |     merge       -   (1)
+                #   warn       y          n       |  warn + get     y
+                #  ignore      y          n       |      get        y
+                #
+                # (1) this is probably the wrong behavior here -- we should
+                #     probably abort, but some actions like rebases currently
+                #     don't like an abort happening in the middle of
+                #     merge.update.
+                if not different:
+                    actions[f] = ('g', (fl2, False), "remote created")
+                elif mergeforce or config == 'abort':
+                    actions[f] = ('m', (f, f, None, False, anc),
+                                  "remote differs from untracked local")
+                elif config == 'abort':
+                    abortconflicts.add(f)
+                else:
+                    if config == 'warn':
+                        warnconflicts.add(f)
+                    actions[f] = ('g', (fl2, True), "remote created")
 
     for f in sorted(abortconflicts):
         repo.ui.warn(_("%s: untracked file differs\n") % f)
@@ -649,14 +682,6 @@
         if m == 'c':
             flags, = args
             actions[f] = ('g', (flags, backup), msg)
-        elif m == 'cm':
-            fl2, anc = args
-            different = _checkunknownfile(repo, wctx, mctx, f)
-            if different:
-                actions[f] = ('m', (f, f, None, False, anc),
-                              "remote differs from untracked local")
-            else:
-                actions[f] = ('g', (fl2, backup), "remote created")
 
 def _forgetremoved(wctx, mctx, branchmerge):
     """
--- a/tests/test-rebase-scenario-global.t	Wed Feb 03 13:11:34 2016 -0800
+++ b/tests/test-rebase-scenario-global.t	Wed Feb 03 13:12:06 2016 -0800
@@ -25,7 +25,8 @@
 
 Rebasing
 D onto H - simple rebase:
-(this also tests that editor is invoked if '--edit' is specified)
+(this also tests that editor is invoked if '--edit' is specified, and that we
+can abort or warn for colliding untracked files)
 
   $ hg clone -q -u . a a1
   $ cd a1
@@ -50,8 +51,10 @@
 
   $ hg status --rev "3^1" --rev 3
   A D
-  $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit
+  $ echo collide > D
+  $ HGEDITOR=cat hg rebase -s 3 -d 7 --edit --config merge.checkunknown=warn
   rebasing 3:32af7686d403 "D"
+  D: replacing untracked file
   D
   
   
@@ -62,6 +65,9 @@
   HG: branch 'default'
   HG: added D
   saved backup bundle to $TESTTMP/a1/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
+  $ cat D.orig
+  collide
+  $ rm D.orig
 
   $ hg tglog
   o  7: 'D'
@@ -84,14 +90,19 @@
 
 
 D onto F - intermediate point:
-(this also tests that editor is not invoked if '--edit' is not specified)
+(this also tests that editor is not invoked if '--edit' is not specified, and
+that we can ignore for colliding untracked files)
 
   $ hg clone -q -u . a a2
   $ cd a2
+  $ echo collide > D
 
-  $ HGEDITOR=cat hg rebase -s 3 -d 5
+  $ HGEDITOR=cat hg rebase -s 3 -d 5 --config merge.checkunknown=ignore
   rebasing 3:32af7686d403 "D"
   saved backup bundle to $TESTTMP/a2/.hg/strip-backup/32af7686d403-6f7dface-backup.hg (glob)
+  $ cat D.orig
+  collide
+  $ rm D.orig
 
   $ hg tglog
   o  7: 'D'
@@ -114,15 +125,21 @@
 
 
 E onto H - skip of G:
+(this also tests that we can overwrite untracked files and don't create backups
+if they have the same contents)
 
   $ hg clone -q -u . a a3
   $ cd a3
+  $ hg cat -r 4 E | tee E
+  E
 
   $ hg rebase -s 4 -d 7
   rebasing 4:9520eea781bc "E"
   rebasing 6:eea13746799a "G"
   note: rebase of 6:eea13746799a created no changes to commit
   saved backup bundle to $TESTTMP/a3/.hg/strip-backup/9520eea781bc-fcd8edd4-backup.hg (glob)
+  $ f E.orig
+  E.orig: file not found
 
   $ hg tglog
   o  6: 'E'