[PATCH 3 of 4] transplant: add --no-parent-from to skip merges from a branch

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


# HG changeset patch
# User Peter Arrenbrecht <peter.arrenbrecht at gmail.com>
# Date 1255171583 -7200
transplant: add --no-parent-from to skip merges from a branch

If --no-parent-from NAME is given, no merge changesets with a parent
in the given named branch are transplanted. This is useful for
skipping merges from upstream branches into local branches (when you
only want to transplant your own changes, not upstream work).

diff --git a/hgext/transplant.py b/hgext/transplant.py
--- a/hgext/transplant.py
+++ b/hgext/transplant.py
@@ -70,6 +70,7 @@
 
 MERGES = '# Merges'
 DIFFPARENT = '# --diff-parent-from'
+NOPARENT = '# --no-parent-from'
 
 class transplanter(object):
     def __init__(self, ui, repo):
@@ -94,7 +95,8 @@
                 return True
         return False
 
-    def apply(self, repo, source, revmap, merges, mergeparentfrom, opts={}):
+    def apply(self, repo, source, revmap, merges, mergeparentfrom,
+              noparentfrom, opts={}):
         '''apply the revisions in revmap one by one in revision order'''
         revs = sorted(revmap)
         p1, p2 = repo.dirstate.parents()
@@ -143,7 +145,8 @@
                 parent = parents[0]
                 if parents[1] != revlog.nullid:
                     branches = [branch(source, p) for p in parents]
-                    if (not mergeparentfrom
+                    if ((noparentfrom and noparentfrom in branches)
+                        or not mergeparentfrom
                         or branches[0] == branches[1]
                         or mergeparentfrom not in branches):
                         self.ui.note(_('skipping merge changeset %s:%s\n')
@@ -181,7 +184,7 @@
                 repo.pull(source, heads=pulls)
                 merge.update(repo, pulls[-1], False, False, None)
         finally:
-            self.saveseries(revmap, merges, mergeparentfrom)
+            self.saveseries(revmap, merges, mergeparentfrom, noparentfrom)
             self.transplants.write()
             lock.release()
             wlock.release()
@@ -282,13 +285,14 @@
         if not os.path.exists(seriespath):
             self.transplants.write()
             return
-        nodes, merges, mergeparentfrom = self.readseries()
+        nodes, merges, mergeparentfrom, noparentfrom = self.readseries()
         revmap = {}
         for n in nodes:
             revmap[source.changelog.rev(n)] = n
         os.unlink(seriespath)
 
-        self.apply(repo, source, revmap, merges, mergeparentfrom, opts)
+        self.apply(repo, source, revmap, merges, mergeparentfrom, noparentfrom,
+                   opts)
 
     def recover(self, repo, force):
         '''commit working directory using journal metadata'''
@@ -323,6 +327,7 @@
         nodes = []
         merges = []
         mergeparentfrom = None
+        noparentfrom = None
         cur = nodes
         for line in self.opener('series').read().splitlines():
             if line.startswith(MERGES):
@@ -331,11 +336,14 @@
             if line.startswith(DIFFPARENT):
                 mergeparentfrom = line[len(DIFFPARENT):].strip()
                 continue
+            if line.startswith(NOPARENT):
+                noparentfrom = line[len(NOPARENT):].strip()
+                continue
             cur.append(revlog.bin(line))
 
-        return (nodes, merges, mergeparentfrom)
+        return (nodes, merges, mergeparentfrom, noparentfrom)
 
-    def saveseries(self, revmap, merges, mergeparentfrom):
+    def saveseries(self, revmap, merges, mergeparentfrom, noparentfrom):
         if not revmap:
             return
 
@@ -350,6 +358,8 @@
                 series.write(revlog.hex(m) + '\n')
         if mergeparentfrom:
             series.write('%s %s\n' % (DIFFPARENT, mergeparentfrom))
+        if noparentfrom:
+            series.write('%s %s\n' % (NOPARENT, noparentfrom))
         series.close()
 
     def parselog(self, fp):
@@ -397,7 +407,8 @@
         if os.path.exists(absdst):
             os.unlink(absdst)
 
-    def transplantfilter(self, repo, source, root, mergeparentfrom):
+    def transplantfilter(self, repo, source, root,
+                         mergeparentfrom, noparentfrom):
         def matchfn(node):
             if self.applied(repo, node, root):
                 return False
@@ -408,6 +419,8 @@
                 if not mergeparentfrom:
                     return False
                 bs = [branch(source, p) for p in ps]
+                if noparentfrom and noparentfrom in bs:
+                    return False
                 if bs[0] == bs[1] or mergeparentfrom not in bs:
                     return False
             extra = source.changelog.read(node)[5]
@@ -495,6 +508,11 @@
     is transplanted if exactly one of its parents resides on the given
     named branch, which is used as the diff parent for the patch.
 
+    If --no-parent-from NAME is given, no merge changesets with a parent
+    in the given named branch are transplanted. This is useful for
+    skipping merges from upstream branches into local branches (when you
+    only want to transplant your own changes, not upstream work).
+
     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
@@ -593,7 +611,8 @@
             return
 
         mergeparentfrom = opts.get('diff_parent_from')
-        tf=tp.transplantfilter(repo, source, p1, mergeparentfrom)
+        noparentfrom = opts.get('no_parent_from')
+        tf=tp.transplantfilter(repo, source, p1, mergeparentfrom, noparentfrom)
         if opts.get('prune'):
             prune = [source.lookup(r)
                      for r in cmdutil.revrange(source, opts.get('prune'))]
@@ -623,7 +642,8 @@
         for r in merges:
             revmap[source.changelog.rev(r)] = r
 
-        tp.apply(repo, source, revmap, merges, mergeparentfrom, opts)
+        tp.apply(repo, source, revmap, merges, mergeparentfrom, noparentfrom,
+                 opts)
     finally:
         if bundle:
             source.close()
@@ -639,6 +659,8 @@
           ('m', 'merge', [], _('merge at REV')),
           ('d', 'diff-parent-from', '', _('diff merges against parent '
                                           'from this named branch')),
+          ('B', 'no-parent-from', '', _('skip merges with a parent from '
+                                        'this named branch')),
           ('', 'log', None, _('append transplant info to log message')),
           ('c', 'continue', None, _('continue last transplant session '
                                     'after repair')),
diff --git a/tests/test-transplant b/tests/test-transplant
--- a/tests/test-transplant
+++ b/tests/test-transplant
@@ -222,3 +222,102 @@
 hg transplant --diff-parent-from C tfrom:tto
 hg manifest
 cd ..
+
+
+echo '% transplant no merges from branch'
+# This scenario is somewhat special. I have to track three incremental
+# upstream branches (fix, rel, dev) with manual merges between them. I
+# import newer versions of these manually from Svn.
+# On top of them, I have three local branches with my own work. For my own
+# work, I want to use Hg to merge forward in a controlled fashion. Since
+# this is interleaved with upstream changes, I have to use transplant in a
+# slightly complex setup.
+echo 'graphlog=' >> $HGRCPATH
+
+hg init ttrack
+cd ttrack
+
+echo '%% setup fix'
+hg branch up-fix
+echo up1 >up1
+hg ci -Amup-fix -d '0 0'
+hg branch my-fix
+echo my1 >my1
+hg ci -Ammy-fix -d '0 0'
+
+echo '%% setup rel'
+hg up up-fix
+hg branch up-rel
+echo rel >rel
+hg ci -Amup-rel -d '0 0'
+hg up my-fix
+hg branch my-rel
+hg merge up-rel
+hg ci -mmy-rel -d '0 0'
+
+echo '%% setup dev'
+hg up up-rel
+hg branch up-dev
+echo dev >dev
+hg ci -Amup-dev -d '0 0'
+hg up my-rel
+hg branch my-dev
+hg merge up-dev
+hg ci -mmy-dev -d '0 0'
+
+echo '%% upstream changes fix'
+hg up up-fix
+echo up1-fix >up1
+hg ci -mup-fix-fixed -d '0 0'
+hg up my-fix
+hg merge up-fix
+hg ci -mmerge-up-fix -d '0 0'
+
+echo '%% upstream changes rel'
+hg up up-rel
+echo up1-rel >up1
+hg ci -mup-rel-fixed -d '0 0'
+hg up my-rel
+hg merge up-rel
+hg ci -mmerge-up-rel -d '0 0'
+
+echo '%% I fix'
+hg up my-fix
+echo my1-fix >my1
+hg ci -mmy-fix-change -d '0 0'
+
+echo '%% port to rel'
+hg up my-rel
+hg glog --template '{rev}/{node|short}: [{branches}] {desc}\n'
+# Cannot `hg merge` here, as that would pull the change to up-fix,
+# which I mustn't do myself. I merge on the final transplant to
+# indicate the conceptual merge in history.
+# This doesn't show the need for the merge transplants yet.
+hg transplant --branch my-fix --all --merge my-fix --log
+cat up1
+cat my1
+
+echo '%% I change rel'
+echo my2-rel >my2
+hg ci -Ammy-rel-change -d '0 0'
+
+echo '%% port to dev'
+hg up my-dev
+hg glog --template '{rev}/{node|short}: [{branches}] {desc}\n'
+# Here we need the new --diff-parent-from and --no-parent-from options.
+# There are two merges:
+#  * my-fix to my-rel (the transplanted "my-fix-change"), which we want, and
+#  * up-rel to my-rel ("merge-up-rel"), which we don't.
+# In practice, there can be longish sequences of merges we either want or don't.
+# The new options allow me to automate such a transplant.
+# Note: To be fully general, I'd need a --merge-last option, since the tip of my-rel
+# might actually be an unwanted merge from up-rel.
+hg transplant --branch my-rel --all --merge my-rel --diff-parent-from my-rel --no-parent-from up-rel --log
+cat up1
+cat my1
+cat my2
+
+echo '%% final result'
+hg glog --template '{rev}/{node|short}: [{branches}] {desc}\n'
+
+cd ..
diff --git a/tests/test-transplant.out b/tests/test-transplant.out
--- a/tests/test-transplant.out
+++ b/tests/test-transplant.out
@@ -227,3 +227,138 @@
 a
 b1
 c1
+% transplant no merges from branch
+%% setup fix
+marked working directory as branch up-fix
+adding up1
+marked working directory as branch my-fix
+adding my1
+%% setup rel
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+marked working directory as branch up-rel
+adding rel
+created new head
+1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+marked working directory as branch my-rel
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+%% setup dev
+0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+marked working directory as branch up-dev
+adding dev
+created new head
+1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+marked working directory as branch my-dev
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+%% upstream changes fix
+0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+created new head
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+%% upstream changes rel
+2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+created new head
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+(branch merge, don't forget to commit)
+%% I fix
+1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+%% port to rel
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+o  10/b1c99ca86ae9: [my-fix] my-fix-change
+|
+| @    9/5af79cffbf5a: [my-rel] merge-up-rel
+| |\
+| | o  8/8f2fcb8e3106: [up-rel] up-rel-fixed
+| | |
+o | |    7/b07e380635c1: [my-fix] merge-up-fix
+|\ \ \
+| o | |  6/b38451fed68b: [up-fix] up-fix-fixed
+| | | |
+| | +---o  5/a396c7632151: [my-dev] my-dev
+| | | | |
+| | | | o  4/c138da2414fc: [up-dev] up-dev
+| | | |/
++---o |  3/b8a745f8955f: [my-rel] my-rel
+| | |/
+| | o  2/b1a16fe7fb52: [up-rel] up-rel
+| |/
+o |  1/6e1643e6b4aa: [my-fix] my-fix
+|/
+o  0/1dc89647ce6f: [up-fix] up-fix
+
+applying b1c99ca86ae9
+10:b1c99ca86ae9 merged at 49eeaf726a7a
+up1-rel
+my1-fix
+%% I change rel
+adding my2
+%% port to dev
+3 files updated, 0 files merged, 1 files removed, 0 files unresolved
+o  12/30584df1821b: [my-rel] my-rel-change
+|
+o    11/49eeaf726a7a: [my-rel] my-fix-change
+|\   (transplanted from b1c99ca86ae9f7ade6f79b84eba618e630868bce)
+| o  10/b1c99ca86ae9: [my-fix] my-fix-change
+| |
+o |    9/5af79cffbf5a: [my-rel] merge-up-rel
+|\ \
+| o |  8/8f2fcb8e3106: [up-rel] up-rel-fixed
+| | |
+| | o    7/b07e380635c1: [my-fix] merge-up-fix
+| | |\
+| | | o  6/b38451fed68b: [up-fix] up-fix-fixed
+| | | |
++-------@  5/a396c7632151: [my-dev] my-dev
+| | | | |
+| +-----o  4/c138da2414fc: [up-dev] up-dev
+| | | |
+o---+ |  3/b8a745f8955f: [my-rel] my-rel
+|/ / /
+o---+  2/b1a16fe7fb52: [up-rel] up-rel
+ / /
+o /  1/6e1643e6b4aa: [my-fix] my-fix
+|/
+o  0/1dc89647ce6f: [up-fix] up-fix
+
+applying 49eeaf726a7a
+49eeaf726a7a transplanted to 9a3d1c11bcf7
+applying 30584df1821b
+12:30584df1821b merged at c1e26d61818f
+up1
+my1-fix
+my2-rel
+%% final result
+@    14/c1e26d61818f: [my-dev] my-rel-change
+|\   (transplanted from 30584df1821b396956af32fe94ea2bb95f829530)
+| o  13/9a3d1c11bcf7: [my-dev] my-fix-change
+| |  (transplanted from b1c99ca86ae9f7ade6f79b84eba618e630868bce)
+| |  (transplanted from 49eeaf726a7ad68b166cf926883ff1cb40d94c41)
+o |  12/30584df1821b: [my-rel] my-rel-change
+| |
+o |    11/49eeaf726a7a: [my-rel] my-fix-change
+|\ \   (transplanted from b1c99ca86ae9f7ade6f79b84eba618e630868bce)
+| o |  10/b1c99ca86ae9: [my-fix] my-fix-change
+| | |
+o | |    9/5af79cffbf5a: [my-rel] merge-up-rel
+|\ \ \
+| o | |  8/8f2fcb8e3106: [up-rel] up-rel-fixed
+| | | |
+| | o |    7/b07e380635c1: [my-fix] merge-up-fix
+| | |\ \
+| | | o |  6/b38451fed68b: [up-fix] up-fix-fixed
+| | | | |
++-------o  5/a396c7632151: [my-dev] my-dev
+| | | | |
+| +-----o  4/c138da2414fc: [up-dev] up-dev
+| | | |
+o---+ |  3/b8a745f8955f: [my-rel] my-rel
+|/ / /
+o---+  2/b1a16fe7fb52: [up-rel] up-rel
+ / /
+o /  1/6e1643e6b4aa: [my-fix] my-fix
+|/
+o  0/1dc89647ce6f: [up-fix] up-fix
+


More information about the Mercurial-devel mailing list