largefiles: rewrite merge code using dictionary with entry per file
authorMartin von Zweigbergk <martinvonz@google.com>
Tue, 09 Dec 2014 22:03:53 -0800
changeset 23529 38e55e55ae4d
parent 23528 5a6d85bae97f
child 23530 42ae1b1f048f
largefiles: rewrite merge code using dictionary with entry per file In overridecalculateupdates(), we currently only deal with conflicts that result in a 'g' action for either the largefile or a standin. We will soon want to deal cases with 'cd' and 'dc' actions here. It will be easier to reason about such cases if we rewrite it using a dict from filename to action. A side-effect of this change is that the output can only have one action per file (which should be a good change). Before this change, when one of the tests in test-issue3084 received this input (the 'a' in the input was a result of 'cd' conflict resolved in favor of the modified file): 'g': [('.hglf/f', ('',), 'remote created')], 'a': [('f', None, 'prompt keep')], and the user chose to keep the local largefile, it produced this output: 'g': [('.hglf/f', ('',), 'remote created')], 'r': [('f', None, 'replaced by standin')], 'a': [('f', None, 'prompt keep')], Although 'a' actions are processed after 'r' actions by recordupdates(), it still worked because 'a' actions have no effect on merges (only on updates). After this change, the output is: 'g': [('.hglf/f', ('',), 'remote created')], 'r': [('f', None, 'replaced by standin')], Similarly, there are several tests in test-largefiles-update that get inputs like: 'a': [('.hglf/large2', None, 'prompt keep')], 'g': [('large2', ('',), 'remote created')], and when the user chooses to keep the local largefile, they produce this output: 'a': [('.hglf/large2', None, 'prompt keep'), ('.hglf/large2', None, 'keep standin')], 'lfmr': [('large2', None, 'forget non-standin largefile')], In this case, it was not a merge but an update, so the 'a' action does have an effect. However, since dirstate.add() is idempotent, it still has no obserable effect. After this change, the output is: 'a': [('.hglf/large2', None, 'keep standin')], 'lfmr': [('large2', None, 'forget non-standin largefile')],
hgext/largefiles/overrides.py
--- a/hgext/largefiles/overrides.py	Tue Dec 09 09:53:26 2014 -0800
+++ b/hgext/largefiles/overrides.py	Tue Dec 09 22:03:53 2014 -0800
@@ -425,10 +425,14 @@
     if overwrite:
         return actions, diverge, renamedelete
 
+    # Convert to dictionary with filename as key and action as value.
+    actionbyfile = {}
+    for m, l in actions.iteritems():
+        for f, args, msg in l:
+            actionbyfile[f] = m, args, msg
+
     removes = set(a[0] for a in actions['r'])
 
-    newglist = []
-    lfmr = [] # LargeFiles: Mark as Removed
     for action in actions['g']:
         f, args, msg = action
         splitstandin = f and lfutil.splitstandin(f)
@@ -442,15 +446,14 @@
                         'use (l)argefile or keep (n)ormal file?'
                         '$$ &Largefile $$ &Normal file') % lfile
             if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
-                actions['r'].append((lfile, None, 'replaced by standin'))
-                newglist.append(action)
+                actionbyfile[lfile] = ('r', None, 'replaced by standin')
             else: # keep local normal file
                 if branchmerge:
-                    actions['k'].append((standin, None,
-                                         'replaced by non-standin'))
+                    actionbyfile[standin] = ('k', None,
+                                             'replaced by non-standin')
                 else:
-                    actions['r'].append((standin, None,
-                                         'replaced by non-standin'))
+                    actionbyfile[standin] = ('r', None,
+                                             'replaced by non-standin')
         elif lfutil.standin(f) in p1 and lfutil.standin(f) not in removes:
             # Case 2: largefile in the working copy, normal file in
             # the second parent
@@ -462,23 +465,24 @@
             if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
                 if branchmerge:
                     # largefile can be restored from standin safely
-                    actions['k'].append((lfile, None, 'replaced by standin'))
+                    actionbyfile[lfile] = ('k', None, 'replaced by standin')
                 else:
                     # "lfile" should be marked as "removed" without
                     # removal of itself
-                    lfmr.append((lfile, None, 'forget non-standin largefile'))
+                    actionbyfile[lfile] = ('lfmr', None,
+                                           'forget non-standin largefile')
 
                     # linear-merge should treat this largefile as 're-added'
-                    actions['a'].append((standin, None, 'keep standin'))
+                    actionbyfile[standin] = ('a', None, 'keep standin')
             else: # pick remote normal file
-                actions['r'].append((standin, None, 'replaced by non-standin'))
-                newglist.append(action)
-        else:
-            newglist.append(action)
+                actionbyfile[standin] = ('r', None, 'replaced by non-standin')
 
-    actions['g'] = newglist
-    if lfmr:
-        actions['lfmr'] = lfmr
+    # Convert back to dictionary-of-lists format
+    for l in actions.itervalues():
+        l[:] = []
+    actions['lfmr'] = []
+    for f, (m, args, msg) in actionbyfile.iteritems():
+        actions[m].append((f, args, msg))
 
     return actions, diverge, renamedelete