mergestate: handle additional record types specially
authorSiddharth Agarwal <sid0@fb.com>
Wed, 18 Nov 2015 15:46:45 -0800
changeset 27027 a01ecbcfaf84
parent 27026 c93b44b79359
child 27028 f92053df8f0b
mergestate: handle additional record types specially This works around a bug in older Mercurial versions' handling of the v2 merge state. We also add a bunch of tests that make sure that (1) we correctly abort when the merge state has an unsupported record type (2) aborting the merge, rebase or histedit continues to work and clears out the merge state.
mercurial/merge.py
tests/fakemergerecord.py
tests/test-histedit-non-commute-abort.t
tests/test-rebase-abort.t
tests/test-resolve.t
--- a/mercurial/merge.py	Wed Nov 18 23:42:32 2015 -0800
+++ b/mercurial/merge.py	Wed Nov 18 15:46:45 2015 -0800
@@ -62,6 +62,8 @@
        (experimental)
     m: the external merge driver defined for this merge plus its run state
        (experimental)
+    X: unsupported mandatory record type (used in tests)
+    x: unsupported advisory record type (used in tests)
 
     Merge driver run states (experimental):
     u: driver-resolved files unmarked -- needs to be run next time we're about
@@ -231,6 +233,13 @@
         `type` is a single character, `length` is a 4 byte integer, and
         `content` is an arbitrary byte sequence of length `length`.
 
+        Mercurial versions prior to 3.7 have a bug where if there are
+        unsupported mandatory merge records, attempting to clear out the merge
+        state with hg update --clean or similar aborts. The 't' record type
+        works around that by writing out what those versions treat as an
+        advisory record, but later versions interpret as special: the first
+        character is the 'real' record type and everything onwards is the data.
+
         Returns list of records [(TYPE, data), ...]."""
         records = []
         try:
@@ -245,6 +254,8 @@
                 off += 4
                 record = data[off:(off + length)]
                 off += length
+                if rtype == 't':
+                    rtype, record = record[0], record[1:]
                 records.append((rtype, record))
             f.close()
         except IOError as err:
@@ -326,10 +337,16 @@
         f.close()
 
     def _writerecordsv2(self, records):
-        """Write current state on disk in a version 2 file"""
+        """Write current state on disk in a version 2 file
+
+        See the docstring for _readrecordsv2 for why we use 't'."""
+        # these are the records that all version 2 clients can read
+        whitelist = 'LOF'
         f = self._repo.vfs(self.statepathv2, 'w')
         for key, data in records:
             assert len(key) == 1
+            if key not in whitelist:
+                key, data = 't', '%s%s' % (key, data)
             format = '>sI%is' % len(data)
             f.write(_pack(format, key, len(data), data))
         f.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/fakemergerecord.py	Wed Nov 18 15:46:45 2015 -0800
@@ -0,0 +1,25 @@
+# Extension to write out fake unsupported records into the merge state
+#
+#
+
+from __future__ import absolute_import
+
+from mercurial import (
+    cmdutil,
+    merge,
+)
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+
+@command('fakemergerecord',
+         [('X', 'mandatory', None, 'add a fake mandatory record'),
+          ('x', 'advisory', None, 'add a fake advisory record')], '')
+def fakemergerecord(ui, repo, *pats, **opts):
+    ms = merge.mergestate.read(repo)
+    records = ms._makerecords()
+    if opts.get('mandatory'):
+        records.append(('X', 'mandatory record'))
+    if opts.get('advisory'):
+        records.append(('x', 'advisory record'))
+    ms._writerecords(records)
--- a/tests/test-histedit-non-commute-abort.t	Wed Nov 18 23:42:32 2015 -0800
+++ b/tests/test-histedit-non-commute-abort.t	Wed Nov 18 15:46:45 2015 -0800
@@ -75,10 +75,45 @@
   warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
   Fix up the change and run hg histedit --continue
 
+insert unsupported advisory merge record
+  $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
+  $ hg debugmergestate
+  * version 2 records
+  local: 8f7551c7e4a2f2efe0bc8c741baf7f227d65d758
+  other: e860deea161a2f77de56603b340ebbb4536308ae
+  unrecognized entry: x	advisory record
+  file: e (record type "F", state "u", hash 58e6b3a414a1e090dfc6029add0f3555ccba127f)
+    local path: e (flags "")
+    ancestor path: e (node 0000000000000000000000000000000000000000)
+    other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f)
+  $ hg resolve -l
+  U e
 
-abort the edit
+insert unsupported mandatory merge record
+  $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
+  $ hg debugmergestate
+  * version 2 records
+  local: 8f7551c7e4a2f2efe0bc8c741baf7f227d65d758
+  other: e860deea161a2f77de56603b340ebbb4536308ae
+  file: e (record type "F", state "u", hash 58e6b3a414a1e090dfc6029add0f3555ccba127f)
+    local path: e (flags "")
+    ancestor path: e (node 0000000000000000000000000000000000000000)
+    other path: e (node 6b67ccefd5ce6de77e7ead4f5292843a0255329f)
+  unrecognized entry: X	mandatory record
+  $ hg resolve -l
+  abort: unsupported merge state records: X
+  (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
+  [255]
+  $ hg resolve -ma
+  abort: unsupported merge state records: X
+  (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
+  [255]
+
+abort the edit (should clear out merge state)
   $ hg histedit --abort 2>&1 | fixbundle
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg debugmergestate
+  no merge state found
 
 log after abort
   $ hg resolve -l
--- a/tests/test-rebase-abort.t	Wed Nov 18 23:42:32 2015 -0800
+++ b/tests/test-rebase-abort.t	Wed Nov 18 15:46:45 2015 -0800
@@ -68,11 +68,49 @@
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
 
-Abort:
+Insert unsupported advisory merge record:
+
+  $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
+  $ hg debugmergestate
+  * version 2 records
+  local: 3e046f2ecedb793b97ed32108086edd1a162f8bc
+  other: 46f0b057b5c061d276b91491c22151f78698abd2
+  unrecognized entry: x	advisory record
+  file: common (record type "F", state "u", hash 94c8c21d08740f5da9eaa38d1f175c592692f0d1)
+    local path: common (flags "")
+    ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6)
+    other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5)
+  $ hg resolve -l
+  U common
+
+Insert unsupported mandatory merge record:
+
+  $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
+  $ hg debugmergestate
+  * version 2 records
+  local: 3e046f2ecedb793b97ed32108086edd1a162f8bc
+  other: 46f0b057b5c061d276b91491c22151f78698abd2
+  file: common (record type "F", state "u", hash 94c8c21d08740f5da9eaa38d1f175c592692f0d1)
+    local path: common (flags "")
+    ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6)
+    other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5)
+  unrecognized entry: X	mandatory record
+  $ hg resolve -l
+  abort: unsupported merge state records: X
+  (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
+  [255]
+  $ hg resolve -ma
+  abort: unsupported merge state records: X
+  (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
+  [255]
+
+Abort (should clear out unsupported merge state):
 
   $ hg rebase --abort
   saved backup bundle to $TESTTMP/a/.hg/strip-backup/3e046f2ecedb-6beef7d5-backup.hg (glob)
   rebase aborted
+  $ hg debugmergestate
+  no merge state found
 
   $ hg tglog
   @  4:draft 'L2'
--- a/tests/test-resolve.t	Wed Nov 18 23:42:32 2015 -0800
+++ b/tests/test-resolve.t	Wed Nov 18 15:46:45 2015 -0800
@@ -249,9 +249,69 @@
   $ cat file1
   resolved
 
+insert unsupported advisory merge record
+
+  $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
+  $ hg debugmergestate
+  * version 2 records
+  local: 57653b9f834a4493f7240b0681efcb9ae7cab745
+  other: dc77451844e37f03f5c559e3b8529b2b48d381d1
+  unrecognized entry: x	advisory record
+  file: file1 (record type "F", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390)
+    local path: file1 (flags "")
+    ancestor path: file1 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
+    other path: file1 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
+  file: file2 (record type "F", state "u", hash cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523)
+    local path: file2 (flags "")
+    ancestor path: file2 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
+    other path: file2 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
+  $ hg resolve -l
+  R file1
+  U file2
+
+insert unsupported mandatory merge record
+
+  $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
+  $ hg debugmergestate
+  * version 2 records
+  local: 57653b9f834a4493f7240b0681efcb9ae7cab745
+  other: dc77451844e37f03f5c559e3b8529b2b48d381d1
+  file: file1 (record type "F", state "r", hash 60b27f004e454aca81b0480209cce5081ec52390)
+    local path: file1 (flags "")
+    ancestor path: file1 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
+    other path: file1 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
+  file: file2 (record type "F", state "u", hash cb99b709a1978bd205ab9dfd4c5aaa1fc91c7523)
+    local path: file2 (flags "")
+    ancestor path: file2 (node 2ed2a3912a0b24502043eae84ee4b279c18b90dd)
+    other path: file2 (node 6f4310b00b9a147241b071a60c28a650827fb03d)
+  unrecognized entry: X	mandatory record
+  $ hg resolve -l
+  abort: unsupported merge state records: X
+  (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
+  [255]
+  $ hg resolve -ma
+  abort: unsupported merge state records: X
+  (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
+  [255]
+  $ hg summary
+  parent: 2:57653b9f834a 
+   append baz to files
+  parent: 1:dc77451844e3 
+   append bar to files
+  branch: default
+  warning: merge state has unsupported record types: X
+  commit: 2 modified, 2 unknown (merge)
+  update: 2 new changesets (update)
+  phases: 5 draft
+
+update --clean shouldn't abort on unsupported records
+
+  $ hg up -qC 1
+  $ hg debugmergestate
+  no merge state found
+
 test crashed merge with empty mergestate
 
-  $ hg up -qC 1
   $ mkdir .hg/merge
   $ touch .hg/merge/state