Patchwork [3,of,5,mergedriver] merge: add a new action type representing files to add/mark as modified

login
register
mail settings
Submitter Siddharth Agarwal
Date Nov. 30, 2015, 6:34 p.m.
Message ID <ed68353d325ad3505803.1448908493@dev666.prn1.facebook.com>
Download mbox | patch
Permalink /patch/11676/
State Accepted
Delegated to: Martin von Zweigbergk
Headers show

Comments

Siddharth Agarwal - Nov. 30, 2015, 6:34 p.m.
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1448907579 28800
#      Mon Nov 30 10:19:39 2015 -0800
# Node ID ed68353d325ad35058034561852d2535847717df
# Parent  637d1c9f4e5bf7024e9e95c5ba5786d1a91eae61
# Available At http://42.netv6.net/sid0-wip/hg/
#              hg pull http://42.netv6.net/sid0-wip/hg/ -r ed68353d325a
merge: add a new action type representing files to add/mark as modified

This is somewhat different from the currently existing 'a' action, for the
following case:

- dirty working copy, with file 'fa' added and 'fm' modified
- hg merge --force with a rev that neither has 'fa' nor 'fm'
- for the change/delete conflicts we pick 'changed' for both 'fa' and 'fm'.

In this case 'branchmerge' is true, but we need to distinguish between 'fa',
which should ultimately be marked added, and 'fm', which should be marked
modified.

Our current strategy is to just not touch the dirstate at all. That works for
now, but won't work once we move change/delete conflicts to the resolve phase.
In that case we may perform repeated re-resolves, some of which might mark the
file removed or remove the file from the dirstate. We'll need to re-add the
file to the dirstate, and we need to be able to figure out whether we mark the
file added or modified. That is what the new 'am' action lets us do.

Patch

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -479,7 +479,10 @@  class mergestate(object):
                 if fcd.isabsent(): # dc: remote picked
                     action = 'g'
                 elif fco.isabsent(): # cd: local picked
-                    action = 'a'
+                    if dfile in self.localctx:
+                        action = 'am'
+                    else:
+                        action = 'a'
                 # else: regular merges (no action necessary)
             self._results[dfile] = r, action
 
@@ -524,7 +527,7 @@  class mergestate(object):
 
     def actions(self):
         """return lists of actions to perform on the dirstate"""
-        actions = {'r': [], 'f': [], 'a': [], 'g': []}
+        actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
         for f, (r, action) in self._results.iteritems():
             if action is not None:
                 actions[action].append((f, None, "merge result"))
@@ -631,7 +634,7 @@  def _checkcollision(repo, wmf, actions):
 
     if actions:
         # k, dr, e and rd are no-op
-        for m in 'a', 'f', 'g', 'cd', 'dc':
+        for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
             for f, args, msg in actions[m]:
                 pmmf.add(f)
         for f, args, msg in actions['r']:
@@ -1065,6 +1068,12 @@  def applyupdates(repo, actions, wctx, mc
         z += 1
         progress(_updating, z, item=f, total=numupdates, unit=_files)
 
+    # re-add/mark as modified (manifest only, just log it)
+    for f, args, msg in actions['am']:
+        repo.ui.debug(" %s: %s -> am\n" % (f, msg))
+        z += 1
+        progress(_updating, z, item=f, total=numupdates, unit=_files)
+
     # keep (noop, just log it)
     for f, args, msg in actions['k']:
         repo.ui.debug(" %s: %s -> k\n" % (f, msg))
@@ -1189,6 +1198,13 @@  def recordupdates(repo, actions, branchm
         if not branchmerge:
             repo.dirstate.add(f)
 
+    # re-add/mark as modified
+    for f, args, msg in actions.get('am', []):
+        if branchmerge:
+            repo.dirstate.normallookup(f)
+        else:
+            repo.dirstate.add(f)
+
     # exec change
     for f, args, msg in actions.get('e', []):
         repo.dirstate.normallookup(f)
@@ -1390,7 +1406,7 @@  def update(repo, node, branchmerge, forc
             repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
             followcopies)
         # Convert to dictionary-of-lists format
-        actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
+        actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
         for f, (m, args, msg) in actionbyfile.iteritems():
             if m not in actions:
                 actions[m] = []
@@ -1411,6 +1427,8 @@  def update(repo, node, branchmerge, forc
                   "use (c)hanged version or (d)elete?"
                   "$$ &Changed $$ &Delete") % f, 0):
                 actions['r'].append((f, None, "prompt delete"))
+            elif f in p1:
+                actions['am'].append((f, None, "prompt keep"))
             else:
                 actions['a'].append((f, None, "prompt keep"))
 
diff --git a/tests/test-rename-merge2.t b/tests/test-rename-merge2.t
--- a/tests/test-rename-merge2.t
+++ b/tests/test-rename-merge2.t
@@ -725,7 +725,7 @@  m "um a c" "um x c" "      " "10 do merg
   use (c)hanged version or (d)elete? c
    preserving b for resolve of b
    preserving rev for resolve of rev
-   a: prompt keep -> a
+   a: prompt keep -> am
    b: both created -> m (premerge)
   picked tool 'python ../merge' for b (binary False symlink False)
   merging b