[PATCH 1 of 1] rebase: add a --rev option to rebase

Pierre-Yves David pierre-yves.david at ens-lyon.org
Wed Oct 12 16:42:01 CDT 2011


# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at ens-lyon.org>
# Date 1318455664 -7200
# Node ID 4fae86ab22887c43473df899dd27a93d9fadb247
# Parent  b7a59f645f0e67bd424b0e9ceef1f8f6ffa7900d
rebase: add a --rev option to rebase

This option allows to specify a strict subset of changeset to rebase.
Rebase will fail if the revset contains ancestors of destination. Only
changeset in the set will be rebased not their descendants. Using revset
in rebase implies two new cases to handle:
- rebasing revset with multiple head,
- changeset rebased without they descendant and no --keep option.
Later commit are dedicated to handle them properly.

diff --git a/hg b/hg
--- a/hg
+++ b/hg
@@ -36,3 +36,4 @@
     mercurial.util.setbinary(fp)
 
 mercurial.dispatch.run()
+ 
diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -15,7 +15,7 @@
 '''
 
 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
-from mercurial import extensions, patch
+from mercurial import extensions, patch, scmutil
 from mercurial.commands import templateopts
 from mercurial.node import nullrev
 from mercurial.lock import release
@@ -36,6 +36,8 @@
      _('REV')),
     ('d', 'dest', '',
      _('rebase onto the specified changeset'), _('REV')),
+    ('r', 'rev', [],
+     _('rebase the specified revision only'), _('REV')),
     ('', 'collapse', False, _('collapse the rebased changesets')),
     ('m', 'message', '',
      _('use text as collapse commit message'), _('TEXT')),
@@ -119,6 +121,7 @@
         destf = opts.get('dest', None)
         srcf = opts.get('source', None)
         basef = opts.get('base', None)
+        revf = opts.get('rev', [])
         contf = opts.get('continue')
         abortf = opts.get('abort')
         collapsef = opts.get('collapse', False)
@@ -143,7 +146,7 @@
                     _('cannot use collapse with continue or abort'))
             if detachf:
                 raise util.Abort(_('cannot use detach with continue or abort'))
-            if srcf or basef or destf:
+            if srcf or basef or destf or revf:
                 raise util.Abort(
                     _('abort and continue do not allow specifying revisions'))
             if opts.get('tool', False):
@@ -157,6 +160,12 @@
             if srcf and basef:
                 raise util.Abort(_('cannot specify both a '
                                    'revision and a base'))
+            if revf and basef:
+                raise util.Abort(_('cannot specify both a'
+                                   'revset and a base'))
+            if revf and srcf:
+                raise util.Abort(_('cannot specify both a'
+                                   'revset and a source revision'))
             if detachf:
                 if not srcf:
                     raise util.Abort(
@@ -164,8 +173,15 @@
                 if basef:
                     raise util.Abort(_('cannot specify a base with detach'))
 
+            if revf:
+                revs = [int(c) for c in scmutil.revrange(repo, revf)]
+                if not revs:
+                    raise util.Abort( _('empty revset'))
+            else:
+                revs = None
+
             cmdutil.bailifchanged(repo)
-            result = buildstate(repo, destf, srcf, basef, detachf)
+            result = buildstate(repo, destf, srcf, basef, detachf, revs)
             if not result:
                 # Empty state built, nothing to rebase
                 ui.status(_('nothing to rebase\n'))
@@ -507,7 +523,7 @@
         repo.ui.warn(_('rebase aborted\n'))
         return 0
 
-def buildstate(repo, dest, src, base, detach):
+def buildstate(repo, dest, src, base, detach, revs=None):
     'Define which revisions are going to be rebased and where'
     targetancestors = set()
     detachset = set()
@@ -526,7 +542,9 @@
                             [s.node for s in repo.mq.applied]):
         raise util.Abort(_('cannot rebase onto an applied mq patch'))
 
-    if src:
+    if src or revs:
+        if not src:
+            src = min(revs)
         commonbase = repo[src].ancestor(repo[dest])
         if commonbase == repo[src]:
             raise util.Abort(_('source is ancestor of destination'))
@@ -543,6 +561,7 @@
             baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
             detachset = srcancestors - baseancestors
             detachset.discard(commonbase.rev())
+
     else:
         if base:
             cwd = repo[base].rev()
@@ -568,7 +587,9 @@
         source = min(rebasingbranch)
 
     repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
-    state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
+    if revs is None:
+        revs = repo.changelog.descendants(source)
+    state = dict.fromkeys(revs, nullrev)
     state.update(dict.fromkeys(detachset, nullmerge))
     state[source] = nullrev
     return repo['.'].rev(), repo[dest].rev(), state
diff --git a/tests/bundles/rebase-revset.hg b/tests/bundles/rebase-revset.hg
new file mode 100644
index 0000000000000000000000000000000000000000..2a016a38aab1229a00cbd1425942c67a02c9a98c
GIT binary patch
literal 1844
zc$@(=2g~?KM=>x$T4*^jL0KkKSxR?b#sC0pfB*mg+kgN6|NsC0|NsC0|NsBn|KH2^
z|9}7g{r~>%bG6U~yTcOz8rgu1l_4calTD~?A*ZO&$Qm>TnI52IXaEC7jR47@^)xg9
zXvw2PKmZJbK=e%j00w}`pa3xjhD{AJJwyNi8UO$Q007CL0B8UJ0002N8X6h^00000
z01X-d0001J4GbE9XbGbzQ&2PjGyv0000T#;Gy#)CMw$Qs(8-_ypa3)t0MktdgFqSp
z27mwopa4Js0iXZ?000b{00w{n0000C5uu@=00000007aT00006fY8AuNlz4do{v*O
zG-4VLQ$PdM$OAwCXa<4l13)x105Jg2&}aYv0000qXa<1OKnBzXgC>WV2j(-n-fu^i
z%kIwU+D?1g_p~#`FU^{3cc$aq<fb at XzbT&3d4QXhz`|#Mh7v|}1`;hGLq@={izXdT
zu)|icwGbj;p2$c&diFyg1uO|rAZqT~fd)gxI%WaWEWkRhKtk4H5CT&y2`E-ypV at 4X
z<$;&>i88`Kvupv33A|KE7J;$>y9a?3WLgZ#Fj$7-BiPW6!=`$XEg(~Al<0_vihQmv
zMa!9NNm6B&ty;Bfsd{~@u=L^C|2YET1Z#C%k<21sht|`^hYhNi`=V0LB6Lg&mH@>c
zRBlV^If|SrZva~y(TYrwo<qHWSj?)QnO^-UT=6Z at J$q&>ih`4EYpcMwV(oFl%u|#F
zG9TuRXW>^SX at x>~0?2|zfyh<Gt}}wU24M_|^6dOf2p;#~C*6v2Nvg`u3#^PoP%bxD
zTm6|R#;pbbX+`p_JG*&>Kv)n(#ymk8ZesssL1eDRjB8R6;F6%Uy$c1vsy8Gp#H14(
zcs+_cAjSo*BWIaI9w&<=T<e=Dqa~UEY;?UzRZ5UmW?&I37!U)Nbm~@xLZo+Mv?BuG
z3=oURWrr4CWU&z`ur1VqIX1>63!6o;j*6%tcD<wDFhPj!Fb6|W^D024VKBfwWQGMQ
z;T2v~XsxuuR=&cx&4R>MGp5LxG at p6`6$)8^v2vTF#)%v+AQbkkgz3bJg(|L75=yr5
zZY-H>PZ(PeliJxOhrx5Eb>TsfYZ((ILI#sWoT6qSl|{g!H}P(e8W5cieW(EmxFv at d
z9&A!h8z?YMn^JE at 54A_5n6{=B^u|=eOPpDAZd)@30s?HA$>5t;&aQ<i9S8|hlp at BZ
z5<280i8vHVvVbs+kHUE#f(w!z+{O}n*tG+-uVtM!zmd<FV>5T{y<y%V92hM)s at _Y!
z{SdwiBK{)6g5rHHIHBujf$S#HISGLyWwILPTc}mp`6*Jw#4XK^YUiT(RbE)DJa3vb
z)t>NL*S9ie9&;GRGWyoFukAKK4H1ZliyUE>Fzx{n5p0GSb%bIO!-ozWPlNg!H*Vd&
z^Sz}-ITe$47)^^px8V5{BzDhyi1d2DM9lAiDy_4qlfEIKHy2=AmDdxm$Yy^?!6t}t
zxJS<oA>YKsGy*srk?xT3F)0%IxUr5rET0cpRS^gg`6#B?)?`9*c<Ye0qYHw`?5I1L
ziflr0so`L9k<{|kPn<qv!V?)yt at hm0&2=cqOzkyEQ3*SqEv%hH22M{RTk}Q-F#QFx
zohc`#Ru$5Zk5|W|5KGh>N<m^(y4!sd3-OA{RRu8lDrE0^i9OkC6F_si#$yFa$GUYF
zJ{DM-MT*r5py~l+qm|2Dk<D}I92lM9THksJZ@{}}TjWA8WNAuSB)N@)tb^1~QxN#n
z>9t6E8=1f`$I1=#3OpUHns3^AvW%_Fb5tpKqHE2IQ+o8?`B=RxSQwocz%cTO&}ko^
z8xQboPIGcF)<6TO)%P%v1)>kNs`BFG<e!{n at 6598$HV-XB|(+2ugwR`WGcbtTSIUT
z!u9)`o>haSMM8<X68rRXm(?8<GsIIInlv_vMJgQ8*@(K at AlDY-(7@;*6k;b!bZDDT
zqO24z%!46Yc|%}taAM&_vvUtahFT(R95HKkEhEeB5S>GzK&d}3N|cG}Zk&%<aC1lf
z4>BOgl86|14rGI$^+eJ*NQVc!x5 at -ft3fzT3EaZN674d0O}4QV7H+#GSQGl3l5uw)
z(qWIvJ4lc=<&3BgK>~}xD@(f^l_mQZ&X7AV#BhD#p|Z at dPVUg*pG8njtTb7f?lI9e
zEA1RXgVp{y5O7TzLCS5>*+oDs^g)0yR0w#B!RpQi-o)baOzy%8CwebrSb32&F9|;_
z7kh|xNDpg8PN0K)qC-N6M$rTZK at 8{ufR`e+D7X&<_%kY|p(q9PI3?t^MxuB>%7|N0
zT*X5TtP4y(T@^z-Dv+c at Dx*jxSd$x0qx-^R;wa$H%$Tb_B_H6I#5yDCD1zEW6Ti^3
i%A$xhR+3w2FY&?9Mr7(gtnqHY;_gVN3K9uU>-fMt$~1)l

diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -269,4 +269,212 @@
   |/
   o  0: 'A'
   
+  $ cd ..
 
+Test for revset
+
+We need a bit different graph
+All destination are B
+
+  $ hg init ah
+  $ cd ah
+  $ hg unbundle $TESTDIR/bundles/rebase-revset.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 9 changesets with 9 changes to 9 files (+2 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg tglog
+  o  8: 'I'
+  |
+  o  7: 'H'
+  |
+  o  6: 'G'
+  |
+  | o  5: 'F'
+  | |
+  | o  4: 'E'
+  |/
+  o  3: 'D'
+  |
+  o  2: 'C'
+  |
+  | o  1: 'B'
+  |/
+  o  0: 'A'
+  
+  $ cd ..
+
+
+Simple case with keep:
+
+Source on have two descendant heads but ask for one
+
+  $ hg clone -q -u . ah ah1
+  $ cd ah1
+  $ hg rebase -r '2::8' -d 1 --keep
+  $ hg tglog
+  @  13: 'I'
+  |
+  o  12: 'H'
+  |
+  o  11: 'G'
+  |
+  o  10: 'D'
+  |
+  o  9: 'C'
+  |
+  | o  8: 'I'
+  | |
+  | o  7: 'H'
+  | |
+  | o  6: 'G'
+  | |
+  | | o  5: 'F'
+  | | |
+  | | o  4: 'E'
+  | |/
+  | o  3: 'D'
+  | |
+  | o  2: 'C'
+  | |
+  o |  1: 'B'
+  |/
+  o  0: 'A'
+  
+
+  $ cd ..
+
+Base on have one descendant heads we ask for but common ancestor have two
+
+  $ hg clone -q -u . ah ah2
+  $ cd ah2
+  $ hg rebase -r '3::8' -d 1 --keep
+  $ hg tglog
+  @  12: 'I'
+  |
+  o  11: 'H'
+  |
+  o  10: 'G'
+  |
+  o    9: 'D'
+  |\
+  | | o  8: 'I'
+  | | |
+  | | o  7: 'H'
+  | | |
+  | | o  6: 'G'
+  | | |
+  | | | o  5: 'F'
+  | | | |
+  | | | o  4: 'E'
+  | | |/
+  | | o  3: 'D'
+  | |/
+  | o  2: 'C'
+  | |
+  o |  1: 'B'
+  |/
+  o  0: 'A'
+  
+
+  $ cd ..
+
+rebase subset
+
+  $ hg clone -q -u . ah ah3
+  $ cd ah3
+  $ hg rebase -r '3::7' -d 1 --keep
+  $ hg tglog
+  @  11: 'H'
+  |
+  o  10: 'G'
+  |
+  o    9: 'D'
+  |\
+  | | o  8: 'I'
+  | | |
+  | | o  7: 'H'
+  | | |
+  | | o  6: 'G'
+  | | |
+  | | | o  5: 'F'
+  | | | |
+  | | | o  4: 'E'
+  | | |/
+  | | o  3: 'D'
+  | |/
+  | o  2: 'C'
+  | |
+  o |  1: 'B'
+  |/
+  o  0: 'A'
+  
+
+  $ cd ..
+
+rebase subset with multiple head
+
+  $ hg clone -q -u . ah ah4
+  $ cd ah4
+  $ hg rebase -r '3::(7+5)' -d 1 --keep
+  $ hg tglog
+  @  13: 'H'
+  |
+  o  12: 'G'
+  |
+  | o  11: 'F'
+  | |
+  | o  10: 'E'
+  |/
+  o    9: 'D'
+  |\
+  | | o  8: 'I'
+  | | |
+  | | o  7: 'H'
+  | | |
+  | | o  6: 'G'
+  | | |
+  | | | o  5: 'F'
+  | | | |
+  | | | o  4: 'E'
+  | | |/
+  | | o  3: 'D'
+  | |/
+  | o  2: 'C'
+  | |
+  o |  1: 'B'
+  |/
+  o  0: 'A'
+  
+
+  $ cd ..
+
+More advanced test
+
+rebase on ancestor with revset
+
+  $ hg clone -q -u . ah ah5
+  $ cd ah5
+  $ hg rebase -r '6::' -d 2
+  saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-backup.hg
+  $ hg tglog
+  @  8: 'I'
+  |
+  o  7: 'H'
+  |
+  o  6: 'G'
+  |
+  | o  5: 'F'
+  | |
+  | o  4: 'E'
+  | |
+  | o  3: 'D'
+  |/
+  o  2: 'C'
+  |
+  | o  1: 'B'
+  |/
+  o  0: 'A'
+  
+


More information about the Mercurial-devel mailing list