[PATCH 2 of 4] transplant: add --diff-parent-from to transplant merges

Peter Arrenbrecht peter.arrenbrecht at gmail.com
Sat Oct 10 05:52:10 CDT 2009


# HG changeset patch
# User Peter Arrenbrecht <peter.arrenbrecht at gmail.com>
# Date 1255171562 -7200
transplant: add --diff-parent-from to transplant merges

When transplanting from one named branch to another, allow transplanting
merge changesets by diffing them against the parent from the specified
named branch, if they have exactly one parent on this branch.

diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -64,6 +64,13 @@
         del self.transplants[self.transplants.index(transplant)]
         self.dirty = True
 
+def branch(repo, node):
+    _mfst, _user, _time, _files, _desc, extra = repo.changelog.read(node)
+    return extra['branch']
+
+MERGES = '# Merges'
+DIFFPARENT = '# --diff-parent-from'
+
 class transplanter(object):
     def __init__(self, ui, repo):
         self.ui = ui
@@ -87,7 +94,7 @@
                 return True
         return False
 
-    def apply(self, repo, source, revmap, merges, opts={}):
+    def apply(self, repo, source, revmap, merges, mergeparentfrom, opts={}):
         '''apply the revisions in revmap one by one in revision order'''
         revs = sorted(revmap)
         p1, p2 = repo.dirstate.parents()
@@ -132,14 +139,22 @@
                     if not hasnode(repo, node):
                         repo.pull(source, heads=[node])
 
+                patchfile = True
+                parent = parents[0]
                 if parents[1] != revlog.nullid:
-                    self.ui.note(_('skipping merge changeset %s:%s\n')
+                    branches = [branch(source, p) for p in parents]
+                    if (not mergeparentfrom
+                        or branches[0] == branches[1]
+                        or mergeparentfrom not in branches):
+                        self.ui.note(_('skipping merge changeset %s:%s\n')
                                  % (rev, revlog.short(node)))
-                    patchfile = None
-                else:
+                        patchfile = None
+                    elif mergeparentfrom == branches[1]:
+                        parent = parents[1]
+                if patchfile:
                     fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
                     fp = os.fdopen(fd, 'w')
-                    gen = patch.diff(source, parents[0], node, opts=diffopts)
+                    gen = patch.diff(source, parent, node, opts=diffopts)
                     for chunk in gen:
                         fp.write(chunk)
                     fp.close()
@@ -166,7 +181,7 @@
                 repo.pull(source, heads=pulls)
                 merge.update(repo, pulls[-1], False, False, None)
         finally:
-            self.saveseries(revmap, merges)
+            self.saveseries(revmap, merges, mergeparentfrom)
             self.transplants.write()
             lock.release()
             wlock.release()
@@ -267,13 +282,13 @@
         if not os.path.exists(seriespath):
             self.transplants.write()
             return
-        nodes, merges = self.readseries()
+        nodes, merges, mergeparentfrom = self.readseries()
         revmap = {}
         for n in nodes:
             revmap[source.changelog.rev(n)] = n
         os.unlink(seriespath)
 
-        self.apply(repo, source, revmap, merges, opts)
+        self.apply(repo, source, revmap, merges, mergeparentfrom, opts)
 
     def recover(self, repo, force):
         '''commit working directory using journal metadata'''
@@ -307,16 +322,20 @@
     def readseries(self):
         nodes = []
         merges = []
+        mergeparentfrom = None
         cur = nodes
         for line in self.opener('series').read().splitlines():
-            if line.startswith('# Merges'):
+            if line.startswith(MERGES):
                 cur = merges
                 continue
+            if line.startswith(DIFFPARENT):
+                mergeparentfrom = line[len(DIFFPARENT):].strip()
+                continue
             cur.append(revlog.bin(line))
 
-        return (nodes, merges)
+        return (nodes, merges, mergeparentfrom)
 
-    def saveseries(self, revmap, merges):
+    def saveseries(self, revmap, merges, mergeparentfrom):
         if not revmap:
             return
 
@@ -326,9 +345,11 @@
         for rev in sorted(revmap):
             series.write(revlog.hex(revmap[rev]) + '\n')
         if merges:
-            series.write('# Merges\n')
+            series.write('%s\n' % MERGES)
             for m in merges:
                 series.write(revlog.hex(m) + '\n')
+        if mergeparentfrom:
+            series.write('%s %s\n' % (DIFFPARENT, mergeparentfrom))
         series.close()
 
     def parselog(self, fp):
@@ -376,12 +397,19 @@
         if os.path.exists(absdst):
             os.unlink(absdst)
 
-    def transplantfilter(self, repo, source, root):
+    def transplantfilter(self, repo, source, root, mergeparentfrom):
         def matchfn(node):
             if self.applied(repo, node, root):
                 return False
-            if source.changelog.parents(node)[1] != revlog.nullid:
-                return False
+            ps = source.changelog.parents(node)
+            if ps[1] != revlog.nullid:
+                # Only pass merges where exactly one parent is on the single,
+                # selected branch. This will become the diff parent.
+                if not mergeparentfrom:
+                    return False
+                bs = [branch(source, p) for p in ps]
+                if bs[0] == bs[1] or mergeparentfrom not in bs:
+                    return False
             extra = source.changelog.read(node)[5]
             cnode = extra.get('transplant_source')
             if cnode and self.applied(repo, cnode, root):
@@ -462,6 +490,11 @@
     branch (up to the named revision) onto your current working
     directory.
 
+    Merge changesets are silently skipped, unless --diff-parent-from
+    NAME is given, where NAME is a named branch. Then a merge changeset
+    is transplanted if exactly one of its parents resides on the given
+    named branch, which is used as the diff parent for the patch.
+
     You can optionally mark selected transplanted changesets as merge
     changesets. You will not be prompted to transplant any ancestors
     of a merged transplant, and you can merge descendants of them
@@ -559,7 +592,8 @@
             tp.resume(repo, source, opts.get('force'), opts)
             return
 
-        tf=tp.transplantfilter(repo, source, p1)
+        mergeparentfrom = opts.get('diff_parent_from')
+        tf=tp.transplantfilter(repo, source, p1, mergeparentfrom)
         if opts.get('prune'):
             prune = [source.lookup(r)
                      for r in cmdutil.revrange(source, opts.get('prune'))]
@@ -589,7 +623,7 @@
         for r in merges:
             revmap[source.changelog.rev(r)] = r
 
-        tp.apply(repo, source, revmap, merges, opts)
+        tp.apply(repo, source, revmap, merges, mergeparentfrom, opts)
     finally:
         if bundle:
             source.close()
@@ -603,6 +637,8 @@
           ('a', 'all', None, _('pull all changesets up to BRANCH')),
           ('p', 'prune', [], _('skip over REV')),
           ('m', 'merge', [], _('merge at REV')),
+          ('d', 'diff-parent-from', '', _('diff merges against parent '
+                                          'from this named branch')),
           ('', 'log', None, _('append transplant info to log message')),
           ('c', 'continue', None, _('continue last transplant session '
                                     'after repair')),
@@ -610,5 +646,5 @@
                                  'no changes')),
           ('', 'filter', '', _('filter changesets through FILTER'))],
          _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] '
-           '[-m REV] [REV]...'))
+           '[-m REV] [-d NAME] [REV]...'))
 }
diff --git a/tests/test-transplant b/tests/test-transplant
--- a/tests/test-transplant
+++ b/tests/test-transplant
@@ -191,3 +191,34 @@
 hg transplant -s ../twin1 tip
 python -c "print repr(file('b', 'rb').read())"
 cd ..
+
+echo '% transplant merge nodes'
+hg init tmerge
+cd tmerge
+hg branch A
+echo a >a
+hg ci -Amx -d '0 0'
+hg branch B
+echo b >b
+hg ci -Amx -d '0 0'
+hg branch C
+echo c >c
+hg ci -Amx -d '0 0'
+
+hg up B
+echo b1 >b1
+hg ci -Amx -d '0 0'
+
+hg up C
+hg merge B
+hg ci -mx -d '0 0'
+hg tag -l tfrom
+echo c1 >c1
+hg ci -Amx -d '0 0'
+hg tag -l tto
+
+hg up A
+echo '%% transplanting'
+hg transplant --diff-parent-from C tfrom:tto
+hg manifest
+cd ..
diff --git a/tests/test-transplant.out b/tests/test-transplant.out
--- a/tests/test-transplant.out
+++ b/tests/test-transplant.out
@@ -204,3 +204,26 @@
 applying 2e849d776c17
 2e849d776c17 transplanted to 589cea8ba85b
 'a\r\nb\r\n'
+% transplant merge nodes
+marked working directory as branch A
+adding a
+marked working directory as branch B
+adding b
+marked working directory as branch C
+adding c
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+adding b1
+created new head
+1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+adding c1
+0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+%% transplanting
+applying 5de1a31e9bea
+5de1a31e9bea transplanted to d890996b85f3
+applying 988f7fc412bb
+988f7fc412bb transplanted to c8970920d3ca
+a
+b1
+c1


More information about the Mercurial-devel mailing list