[PATCH rfc] merge: present merge part labels to user in prompts

Simon Farnsworth simonfar at fb.com
Wed Mar 16 19:59:03 EDT 2016


# HG changeset patch
# User Simon Farnsworth <simonfar at fb.com>
# Date 1458172710 0
#      Wed Mar 16 23:58:30 2016 +0000
# Node ID 85202d019e4f6d4093fdbb35b0aff105db1767b4
# Parent  70c2f8a982766b512e9d7f41f2d93fdb92f5481f
merge: present merge part labels to user in prompts

"local", "remote" and "other" are not always clear; we rename them in
conflict markers to try and clarify what's meant by each label. Present the
conflict marker names in the merge prompts, too.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -2896,6 +2896,14 @@
 
                 ui.write(('file extras: %s (%s)\n')
                          % (filename, ', '.join(extrastrings)))
+            elif rtype == 'l':
+                labels = record.split('\0', 2)
+                labels = [l for l in labels if len(l) > 0]
+                ui.write(('labels:\n'))
+                ui.write(('  local: %s\n' % labels[0]))
+                ui.write(('  other: %s\n' % labels[1]))
+                if len(labels) > 2:
+                    ui.write(('  base:  %s\n' % labels[2]))
             else:
                 ui.write(('unrecognized entry: %s\t%s\n')
                          % (rtype, record.replace('\0', '\t')))
diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -228,32 +228,51 @@
             if newdata != data:
                 util.writefile(file, newdata)
 
+def _getpromptsfromlabels(labels, fd):
+    if labels is None:
+        return {
+            "local": "local",
+            "other": "remote",
+            "localact": "(l)ocal",
+            "otheract": "(o)ther",
+            "fd": fd,
+        }
+
+    return {
+        "local": "local [%s]" % labels[0],
+        "other": "remote [%s]" % labels[1],
+        "localact": "(l)ocal [%s]" % labels[0],
+        "otheract": "(r)emote [%s]" % labels[1],
+        "fd": fd,
+    }
+
 @internaltool('prompt', nomerge)
-def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf):
+def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
     """Asks the user which of the local or the other version to keep as
     the merged version."""
     ui = repo.ui
     fd = fcd.path()
 
+    prompts = _getpromptsfromlabels(labels, fd)
     try:
         if fco.isabsent():
             index = ui.promptchoice(
-                _("local changed %s which remote deleted\n"
+                _("%(local)s changed %(fd)s which %(other)s deleted\n"
                   "use (c)hanged version, (d)elete, or leave (u)nresolved?"
-                  "$$ &Changed $$ &Delete $$ &Unresolved") % fd, 2)
+                  "$$ &Changed $$ &Delete $$ &Unresolved") % prompts, 2)
             choice = ['local', 'other', 'unresolved'][index]
         elif fcd.isabsent():
             index = ui.promptchoice(
-                _("remote changed %s which local deleted\n"
+                _("%(other)s changed %(fd)s which %(local)s deleted\n"
                   "use (c)hanged version, leave (d)eleted, or "
                   "leave (u)nresolved?"
-                  "$$ &Changed $$ &Deleted $$ &Unresolved") % fd, 2)
+                  "$$ &Changed $$ &Deleted $$ &Unresolved") % prompts, 2)
             choice = ['other', 'local', 'unresolved'][index]
         else:
             index = ui.promptchoice(
-                _("no tool found to merge %s\n"
-                  "keep (l)ocal, take (o)ther, or leave (u)nresolved?"
-                  "$$ &Local $$ &Other $$ &Unresolved") % fd, 2)
+                _("no tool found to merge %(fd)s\n"
+                  "keep %(localact)s, take %(otheract)s, or leave (u)nresolved?"
+                  "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
             choice = ['local', 'other', 'unresolved'][index]
 
         if choice == 'other':
@@ -267,12 +286,12 @@
         return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf)
 
 @internaltool('local', nomerge)
-def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
+def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
     """Uses the local version of files as the merged version."""
     return 0, fcd.isabsent()
 
 @internaltool('other', nomerge)
-def _iother(repo, mynode, orig, fcd, fco, fca, toolconf):
+def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
     """Uses the other version of files as the merged version."""
     if fco.isabsent():
         # local changed, remote deleted -- 'deleted' picked
@@ -284,7 +303,7 @@
     return 0, deleted
 
 @internaltool('fail', nomerge)
-def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf):
+def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
     """
     Rather than attempting to merge files that were modified on both
     branches, it marks them as unresolved. The resolve command must be
@@ -587,7 +606,7 @@
     toolconf = tool, toolpath, binary, symlink
 
     if mergetype == nomerge:
-        r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf)
+        r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
         return True, r, deleted
 
     if premerge:
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -68,6 +68,7 @@
     f: a (filename, dictonary) tuple of optional values for a given file
     X: unsupported mandatory record type (used in tests)
     x: unsupported advisory record type (used in tests)
+    l: the labels for the parts of the merge.
 
     Merge driver run states (experimental):
     u: driver-resolved files unmarked -- needs to be run next time we're about
@@ -80,11 +81,11 @@
     statepathv2 = 'merge/state2'
 
     @staticmethod
-    def clean(repo, node=None, other=None):
+    def clean(repo, node=None, other=None, labels=None):
         """Initialize a brand new merge state, removing any existing state on
         disk."""
         ms = mergestate(repo)
-        ms.reset(node, other)
+        ms.reset(node, other, labels)
         return ms
 
     @staticmethod
@@ -100,12 +101,14 @@
         Do not use this directly! Instead call read() or clean()."""
         self._repo = repo
         self._dirty = False
+        self._labels = None
 
-    def reset(self, node=None, other=None):
+    def reset(self, node=None, other=None, labels=None):
         self._state = {}
         self._stateextras = {}
         self._local = None
         self._other = None
+        self._labels = labels
         for var in ('localctx', 'otherctx'):
             if var in vars(self):
                 delattr(self, var)
@@ -165,6 +168,9 @@
                     i += 2
 
                 self._stateextras[filename] = extras
+            elif rtype == 'l':
+                labels = record.split('\0', 2)
+                self._labels = [l for l in labels if len(l) > 0]
             elif not rtype.islower():
                 unsupported.add(rtype)
         self._results = {}
@@ -353,6 +359,9 @@
             rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
                                   extras.iteritems())
             records.append(('f', '%s\0%s' % (filename, rawextras)))
+        if self._labels is not None:
+            labels = '\0'.join(self._labels)
+            records.append(('l', labels))
         return records
 
     def _writerecords(self, records):
@@ -444,7 +453,7 @@
     def extras(self, filename):
         return self._stateextras.setdefault(filename, {})
 
-    def _resolve(self, preresolve, dfile, wctx, labels=None):
+    def _resolve(self, preresolve, dfile, wctx):
         """rerun merge process for file path `dfile`"""
         if self[dfile] in 'rd':
             return True, 0
@@ -481,11 +490,11 @@
                 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
             complete, r, deleted = filemerge.premerge(self._repo, self._local,
                                                       lfile, fcd, fco, fca,
-                                                      labels=labels)
+                                                      labels=self._labels)
         else:
             complete, r, deleted = filemerge.filemerge(self._repo, self._local,
                                                        lfile, fcd, fco, fca,
-                                                       labels=labels)
+                                                       labels=self._labels)
         if r is None:
             # no real conflict
             del self._state[dfile]
@@ -523,17 +532,17 @@
         else:
             return ctx[f]
 
-    def preresolve(self, dfile, wctx, labels=None):
+    def preresolve(self, dfile, wctx):
         """run premerge process for dfile
 
         Returns whether the merge is complete, and the exit code."""
-        return self._resolve(True, dfile, wctx, labels=labels)
+        return self._resolve(True, dfile, wctx)
 
-    def resolve(self, dfile, wctx, labels=None):
+    def resolve(self, dfile, wctx):
         """run merge process (assuming premerge was run) for dfile
 
         Returns the exit code of the merge."""
-        return self._resolve(False, dfile, wctx, labels=labels)[1]
+        return self._resolve(False, dfile, wctx)[1]
 
     def counts(self):
         """return counts for updated, merged and removed files in this
@@ -1094,7 +1103,7 @@
     """
 
     updated, merged, removed = 0, 0, 0
-    ms = mergestate.clean(repo, wctx.p1().node(), mctx.node())
+    ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
     moves = []
     for m, l in actions.items():
         l.sort()
@@ -1247,7 +1256,7 @@
                              overwrite)
             continue
         audit(f)
-        complete, r = ms.preresolve(f, wctx, labels=labels)
+        complete, r = ms.preresolve(f, wctx)
         if not complete:
             numupdates += 1
             tocomplete.append((f, args, msg))
@@ -1257,7 +1266,7 @@
         repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
         z += 1
         progress(_updating, z, item=f, total=numupdates, unit=_files)
-        ms.resolve(f, wctx, labels=labels)
+        ms.resolve(f, wctx)
 
     ms.commit()
 
@@ -1528,11 +1537,12 @@
         if '.hgsubstate' in actionbyfile:
             f = '.hgsubstate'
             m, args, msg = actionbyfile[f]
+            prompts = filemerge._getpromptsfromlabels(labels, f)
             if m == 'cd':
                 if repo.ui.promptchoice(
-                    _("local changed %s which remote deleted\n"
+                    _("%(local)s changed %(fd)s which %(other)s deleted\n"
                       "use (c)hanged version or (d)elete?"
-                      "$$ &Changed $$ &Delete") % f, 0):
+                      "$$ &Changed $$ &Delete") % prompts, 0):
                     actionbyfile[f] = ('r', None, "prompt delete")
                 elif f in p1:
                     actionbyfile[f] = ('am', None, "prompt keep")
@@ -1542,9 +1552,9 @@
                 f1, f2, fa, move, anc = args
                 flags = p2[f2].flags()
                 if repo.ui.promptchoice(
-                    _("remote changed %s which local deleted\n"
+                    _("%(other)s changed %(fd)s which %(local)s deleted\n"
                       "use (c)hanged version or leave (d)eleted?"
-                      "$$ &Changed $$ &Deleted") % f, 0) == 0:
+                      "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
                     actionbyfile[f] = ('g', (flags, False), "prompt recreating")
                 else:
                     del actionbyfile[f]
diff --git a/tests/test-copy-move-merge.t b/tests/test-copy-move-merge.t
--- a/tests/test-copy-move-merge.t
+++ b/tests/test-copy-move-merge.t
@@ -85,7 +85,7 @@
   > c
   > EOF
   rebasing 2:add3f11052fa "other" (tip)
-  remote changed a which local deleted
+  remote [source] changed a which local [dest] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
 
   $ cat b
diff --git a/tests/test-graft.t b/tests/test-graft.t
--- a/tests/test-graft.t
+++ b/tests/test-graft.t
@@ -452,7 +452,7 @@
   c
   =======
   b
-  >>>>>>> other: 5d205f8b35b6  - bar: 1
+  >>>>>>> graft: 5d205f8b35b6  - bar: 1
   $ echo b > a
   $ hg resolve -m a
   (no more unresolved files)
diff --git a/tests/test-histedit-non-commute-abort.t b/tests/test-histedit-non-commute-abort.t
--- a/tests/test-histedit-non-commute-abort.t
+++ b/tests/test-histedit-non-commute-abort.t
@@ -86,6 +86,9 @@
     local path: e (flags "")
     ancestor path: e (node null)
     other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f)
+  labels:
+    local: local
+    other: histedit
   $ hg resolve -l
   U e
 
@@ -100,6 +103,9 @@
     local path: e (flags "")
     ancestor path: e (node null)
     other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f)
+  labels:
+    local: local
+    other: histedit
   unrecognized entry: X	mandatory record
   $ hg resolve -l
   abort: unsupported merge state records: X
diff --git a/tests/test-largefiles-update.t b/tests/test-largefiles-update.t
--- a/tests/test-largefiles-update.t
+++ b/tests/test-largefiles-update.t
@@ -611,7 +611,7 @@
   > EOF
   rebasing 1:72518492caa6 "#1"
   rebasing 4:07d6153b5c04 "#4"
-  local changed .hglf/large1 which remote deleted
+  local [dest] changed .hglf/large1 which remote [source] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? c
 
   $ hg diff -c "tip~1" --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
diff --git a/tests/test-merge-changedelete.t b/tests/test-merge-changedelete.t
--- a/tests/test-merge-changedelete.t
+++ b/tests/test-merge-changedelete.t
@@ -717,9 +717,9 @@
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 -y
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -746,6 +746,9 @@
     local path: file2 (flags "")
     ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e)
     other path: file2 (node e7c1328648519852e723de86c0c0525acd779257)
+  labels:
+    local: working copy
+    other: destination
   --- file1 ---
   1
   changed
@@ -786,6 +789,9 @@
     local path: file2 (flags "")
     ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e)
     other path: file2 (node e7c1328648519852e723de86c0c0525acd779257)
+  labels:
+    local: working copy
+    other: destination
   --- file1 ---
   1
   changed
@@ -824,6 +830,9 @@
     local path: file2 (flags "")
     ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e)
     other path: file2 (node e7c1328648519852e723de86c0c0525acd779257)
+  labels:
+    local: working copy
+    other: destination
   *** file1 does not exist
   --- file2 ---
   2
@@ -864,6 +873,9 @@
     local path: file2 (flags "")
     ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e)
     other path: file2 (node e7c1328648519852e723de86c0c0525acd779257)
+  labels:
+    local: working copy
+    other: destination
   --- file1 ---
   1
   changed
@@ -881,9 +893,9 @@
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 --config ui.interactive=True --tool :prompt
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -910,6 +922,9 @@
     local path: file2 (flags "")
     ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e)
     other path: file2 (node e7c1328648519852e723de86c0c0525acd779257)
+  labels:
+    local: working copy
+    other: destination
   --- file1 ---
   1
   changed
@@ -928,9 +943,9 @@
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 --tool :merge3
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -957,6 +972,9 @@
     local path: file2 (flags "")
     ancestor path: file2 (node 5d9299349fc01ddd25d0070d149b124d8f10411e)
     other path: file2 (node e7c1328648519852e723de86c0c0525acd779257)
+  labels:
+    local: working copy
+    other: destination
   --- file1 ---
   1
   changed
@@ -981,9 +999,9 @@
   (status identical)
   
   === :other -> :prompt ===
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
@@ -1008,9 +1026,9 @@
   (status identical)
   
   === :local -> :prompt ===
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
@@ -1025,9 +1043,9 @@
   (status identical)
   
   === :fail -> :prompt ===
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
diff --git a/tests/test-merge-types.t b/tests/test-merge-types.t
--- a/tests/test-merge-types.t
+++ b/tests/test-merge-types.t
@@ -173,7 +173,7 @@
   (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re)
   picked tool ':prompt' for a (binary False symlink True changedelete False)
   no tool found to merge a
-  keep (l)ocal, take (o)ther, or leave (u)nresolved? u
+  keep (l)ocal [working copy], take (r)emote [destination], or leave (u)nresolved? u
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   1 other heads for branch "default"
diff --git a/tests/test-rebase-abort.t b/tests/test-rebase-abort.t
--- a/tests/test-rebase-abort.t
+++ b/tests/test-rebase-abort.t
@@ -81,6 +81,9 @@
     local path: common (flags "")
     ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6)
     other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5)
+  labels:
+    local: dest
+    other: source
   $ hg resolve -l
   U common
 
@@ -96,6 +99,9 @@
     local path: common (flags "")
     ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6)
     other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5)
+  labels:
+    local: dest
+    other: source
   unrecognized entry: X	mandatory record
   $ hg resolve -l
   abort: unsupported merge state records: X
diff --git a/tests/test-rebase-newancestor.t b/tests/test-rebase-newancestor.t
--- a/tests/test-rebase-newancestor.t
+++ b/tests/test-rebase-newancestor.t
@@ -135,7 +135,7 @@
   note: rebase of 1:1d1a643d390e created no changes to commit
   rebasing 2:ec2c14fb2984 "dev: f-dev stuff"
   rebasing 4:4b019212aaf6 "dev: merge default"
-  remote changed f-default which local deleted
+  remote [source] changed f-default which local [dest] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
   rebasing 6:9455ee510502 "dev: merge default"
   saved backup bundle to $TESTTMP/ancestor-merge/.hg/strip-backup/1d1a643d390e-43e9e04b-backup.hg (glob)
@@ -164,7 +164,7 @@
   > EOF
   rebasing 2:ec2c14fb2984 "dev: f-dev stuff"
   rebasing 4:4b019212aaf6 "dev: merge default"
-  remote changed f-default which local deleted
+  remote [source] changed f-default which local [dest] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
   rebasing 6:9455ee510502 "dev: merge default"
   saved backup bundle to $TESTTMP/ancestor-merge-2/.hg/strip-backup/ec2c14fb2984-62d0b222-backup.hg (glob)
diff --git a/tests/test-subrepo-missing.t b/tests/test-subrepo-missing.t
--- a/tests/test-subrepo-missing.t
+++ b/tests/test-subrepo-missing.t
@@ -62,7 +62,7 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm .hgsubstate
   $ hg up 0
-  remote changed .hgsubstate which local deleted
+  remote [destination] changed .hgsubstate which local [working copy] deleted
   use (c)hanged version or leave (d)eleted? c
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg st


More information about the Mercurial-devel mailing list