##// END OF EJS Templates
rebase: allow rebase even if some revisions need no rebase (BC) (issue5422)...
Martin von Zweigbergk -
r32272:78496ac3 default
parent child Browse files
Show More
@@ -0,0 +1,95 b''
1 Tests rebasing with part of the rebase set already in the
2 destination (issue5422)
3
4 $ cat >> $HGRCPATH <<EOF
5 > [extensions]
6 > rebase=
7 > drawdag=$TESTDIR/drawdag.py
8 >
9 > [experimental]
10 > evolution=createmarkers,allowunstable
11 >
12 > [alias]
13 > tglog = log -G --template "{rev}: {desc}"
14 > EOF
15
16 $ rebasewithdag() {
17 > N=`$PYTHON -c "print($N+1)"`
18 > hg init repo$N && cd repo$N
19 > hg debugdrawdag
20 > hg rebase "$@" > _rebasetmp
21 > r=$?
22 > grep -v 'saved backup bundle' _rebasetmp
23 > [ $r -eq 0 ] && hg tglog
24 > cd ..
25 > return $r
26 > }
27
28 Rebase two commits, of which one is already in the right place
29
30 $ rebasewithdag -r C+D -d B <<EOF
31 > C
32 > |
33 > B D
34 > |/
35 > A
36 > EOF
37 rebasing 2:b18e25de2cf5 "D" (D)
38 already rebased 3:26805aba1e60 "C" (C tip)
39 o 4: D
40 |
41 | o 3: C
42 |/
43 | x 2: D
44 | |
45 o | 1: B
46 |/
47 o 0: A
48
49 Can collapse commits even if one is already in the right place
50
51 $ rebasewithdag --collapse -r C+D -d B <<EOF
52 > C
53 > |
54 > B D
55 > |/
56 > A
57 > EOF
58 rebasing 2:b18e25de2cf5 "D" (D)
59 rebasing 3:26805aba1e60 "C" (C tip)
60 o 4: Collapsed revision
61 | * D
62 | * C
63 | x 3: C
64 |/
65 | x 2: D
66 | |
67 o | 1: B
68 |/
69 o 0: A
70
71 Rebase with "holes". The commits after the hole should end up on the parent of
72 the hole (B below), not on top of the destination (A).
73
74 $ rebasewithdag -r B+D -d A <<EOF
75 > D
76 > |
77 > C
78 > |
79 > B
80 > |
81 > A
82 > EOF
83 already rebased 1:112478962961 "B" (B)
84 not rebasing ignored 2:26805aba1e60 "C" (C)
85 rebasing 3:f585351a92f8 "D" (D tip)
86 o 4: D
87 |
88 | x 3: D
89 | |
90 | o 2: C
91 |/
92 o 1: B
93 |
94 o 0: A
95
@@ -1,1521 +1,1536 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 https://mercurial-scm.org/wiki/RebaseExtension
14 https://mercurial-scm.org/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from __future__ import absolute_import
17 from __future__ import absolute_import
18
18
19 import errno
19 import errno
20 import os
20 import os
21
21
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 from mercurial.node import (
23 from mercurial.node import (
24 hex,
24 hex,
25 nullid,
25 nullid,
26 nullrev,
26 nullrev,
27 short,
27 short,
28 )
28 )
29 from mercurial import (
29 from mercurial import (
30 bookmarks,
30 bookmarks,
31 cmdutil,
31 cmdutil,
32 commands,
32 commands,
33 copies,
33 copies,
34 destutil,
34 destutil,
35 dirstateguard,
35 dirstateguard,
36 error,
36 error,
37 extensions,
37 extensions,
38 hg,
38 hg,
39 lock,
39 lock,
40 merge as mergemod,
40 merge as mergemod,
41 mergeutil,
41 mergeutil,
42 obsolete,
42 obsolete,
43 patch,
43 patch,
44 phases,
44 phases,
45 registrar,
45 registrar,
46 repair,
46 repair,
47 repoview,
47 repoview,
48 revset,
48 revset,
49 scmutil,
49 scmutil,
50 smartset,
50 smartset,
51 util,
51 util,
52 )
52 )
53
53
54 release = lock.release
54 release = lock.release
55 templateopts = commands.templateopts
55 templateopts = commands.templateopts
56
56
57 # The following constants are used throughout the rebase module. The ordering of
57 # The following constants are used throughout the rebase module. The ordering of
58 # their values must be maintained.
58 # their values must be maintained.
59
59
60 # Indicates that a revision needs to be rebased
60 # Indicates that a revision needs to be rebased
61 revtodo = -1
61 revtodo = -1
62 nullmerge = -2
62 nullmerge = -2
63 revignored = -3
63 revignored = -3
64 # successor in rebase destination
64 # successor in rebase destination
65 revprecursor = -4
65 revprecursor = -4
66 # plain prune (no successor)
66 # plain prune (no successor)
67 revpruned = -5
67 revpruned = -5
68 revskipped = (revignored, revprecursor, revpruned)
68 revskipped = (revignored, revprecursor, revpruned)
69
69
70 cmdtable = {}
70 cmdtable = {}
71 command = cmdutil.command(cmdtable)
71 command = cmdutil.command(cmdtable)
72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
72 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
73 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
74 # be specifying the version(s) of Mercurial they are tested with, or
74 # be specifying the version(s) of Mercurial they are tested with, or
75 # leave the attribute unspecified.
75 # leave the attribute unspecified.
76 testedwith = 'ships-with-hg-core'
76 testedwith = 'ships-with-hg-core'
77
77
78 def _nothingtorebase():
78 def _nothingtorebase():
79 return 1
79 return 1
80
80
81 def _savegraft(ctx, extra):
81 def _savegraft(ctx, extra):
82 s = ctx.extra().get('source', None)
82 s = ctx.extra().get('source', None)
83 if s is not None:
83 if s is not None:
84 extra['source'] = s
84 extra['source'] = s
85 s = ctx.extra().get('intermediate-source', None)
85 s = ctx.extra().get('intermediate-source', None)
86 if s is not None:
86 if s is not None:
87 extra['intermediate-source'] = s
87 extra['intermediate-source'] = s
88
88
89 def _savebranch(ctx, extra):
89 def _savebranch(ctx, extra):
90 extra['branch'] = ctx.branch()
90 extra['branch'] = ctx.branch()
91
91
92 def _makeextrafn(copiers):
92 def _makeextrafn(copiers):
93 """make an extrafn out of the given copy-functions.
93 """make an extrafn out of the given copy-functions.
94
94
95 A copy function takes a context and an extra dict, and mutates the
95 A copy function takes a context and an extra dict, and mutates the
96 extra dict as needed based on the given context.
96 extra dict as needed based on the given context.
97 """
97 """
98 def extrafn(ctx, extra):
98 def extrafn(ctx, extra):
99 for c in copiers:
99 for c in copiers:
100 c(ctx, extra)
100 c(ctx, extra)
101 return extrafn
101 return extrafn
102
102
103 def _destrebase(repo, sourceset, destspace=None):
103 def _destrebase(repo, sourceset, destspace=None):
104 """small wrapper around destmerge to pass the right extra args
104 """small wrapper around destmerge to pass the right extra args
105
105
106 Please wrap destutil.destmerge instead."""
106 Please wrap destutil.destmerge instead."""
107 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
107 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
108 onheadcheck=False, destspace=destspace)
108 onheadcheck=False, destspace=destspace)
109
109
110 revsetpredicate = registrar.revsetpredicate()
110 revsetpredicate = registrar.revsetpredicate()
111
111
112 @revsetpredicate('_destrebase')
112 @revsetpredicate('_destrebase')
113 def _revsetdestrebase(repo, subset, x):
113 def _revsetdestrebase(repo, subset, x):
114 # ``_rebasedefaultdest()``
114 # ``_rebasedefaultdest()``
115
115
116 # default destination for rebase.
116 # default destination for rebase.
117 # # XXX: Currently private because I expect the signature to change.
117 # # XXX: Currently private because I expect the signature to change.
118 # # XXX: - bailing out in case of ambiguity vs returning all data.
118 # # XXX: - bailing out in case of ambiguity vs returning all data.
119 # i18n: "_rebasedefaultdest" is a keyword
119 # i18n: "_rebasedefaultdest" is a keyword
120 sourceset = None
120 sourceset = None
121 if x is not None:
121 if x is not None:
122 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
122 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
123 return subset & smartset.baseset([_destrebase(repo, sourceset)])
123 return subset & smartset.baseset([_destrebase(repo, sourceset)])
124
124
125 class rebaseruntime(object):
125 class rebaseruntime(object):
126 """This class is a container for rebase runtime state"""
126 """This class is a container for rebase runtime state"""
127 def __init__(self, repo, ui, opts=None):
127 def __init__(self, repo, ui, opts=None):
128 if opts is None:
128 if opts is None:
129 opts = {}
129 opts = {}
130
130
131 self.repo = repo
131 self.repo = repo
132 self.ui = ui
132 self.ui = ui
133 self.opts = opts
133 self.opts = opts
134 self.originalwd = None
134 self.originalwd = None
135 self.external = nullrev
135 self.external = nullrev
136 # Mapping between the old revision id and either what is the new rebased
136 # Mapping between the old revision id and either what is the new rebased
137 # revision or what needs to be done with the old revision. The state
137 # revision or what needs to be done with the old revision. The state
138 # dict will be what contains most of the rebase progress state.
138 # dict will be what contains most of the rebase progress state.
139 self.state = {}
139 self.state = {}
140 self.activebookmark = None
140 self.activebookmark = None
141 self.currentbookmarks = None
141 self.currentbookmarks = None
142 self.dest = None
142 self.dest = None
143 self.skipped = set()
143 self.skipped = set()
144 self.destancestors = set()
144 self.destancestors = set()
145
145
146 self.collapsef = opts.get('collapse', False)
146 self.collapsef = opts.get('collapse', False)
147 self.collapsemsg = cmdutil.logmessage(ui, opts)
147 self.collapsemsg = cmdutil.logmessage(ui, opts)
148 self.date = opts.get('date', None)
148 self.date = opts.get('date', None)
149
149
150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
151 self.extrafns = [_savegraft]
151 self.extrafns = [_savegraft]
152 if e:
152 if e:
153 self.extrafns = [e]
153 self.extrafns = [e]
154
154
155 self.keepf = opts.get('keep', False)
155 self.keepf = opts.get('keep', False)
156 self.keepbranchesf = opts.get('keepbranches', False)
156 self.keepbranchesf = opts.get('keepbranches', False)
157 # keepopen is not meant for use on the command line, but by
157 # keepopen is not meant for use on the command line, but by
158 # other extensions
158 # other extensions
159 self.keepopen = opts.get('keepopen', False)
159 self.keepopen = opts.get('keepopen', False)
160 self.obsoletenotrebased = {}
160 self.obsoletenotrebased = {}
161
161
162 def storestatus(self, tr=None):
162 def storestatus(self, tr=None):
163 """Store the current status to allow recovery"""
163 """Store the current status to allow recovery"""
164 if tr:
164 if tr:
165 tr.addfilegenerator('rebasestate', ('rebasestate',),
165 tr.addfilegenerator('rebasestate', ('rebasestate',),
166 self._writestatus, location='plain')
166 self._writestatus, location='plain')
167 else:
167 else:
168 with self.repo.vfs("rebasestate", "w") as f:
168 with self.repo.vfs("rebasestate", "w") as f:
169 self._writestatus(f)
169 self._writestatus(f)
170
170
171 def _writestatus(self, f):
171 def _writestatus(self, f):
172 repo = self.repo.unfiltered()
172 repo = self.repo.unfiltered()
173 f.write(repo[self.originalwd].hex() + '\n')
173 f.write(repo[self.originalwd].hex() + '\n')
174 f.write(repo[self.dest].hex() + '\n')
174 f.write(repo[self.dest].hex() + '\n')
175 f.write(repo[self.external].hex() + '\n')
175 f.write(repo[self.external].hex() + '\n')
176 f.write('%d\n' % int(self.collapsef))
176 f.write('%d\n' % int(self.collapsef))
177 f.write('%d\n' % int(self.keepf))
177 f.write('%d\n' % int(self.keepf))
178 f.write('%d\n' % int(self.keepbranchesf))
178 f.write('%d\n' % int(self.keepbranchesf))
179 f.write('%s\n' % (self.activebookmark or ''))
179 f.write('%s\n' % (self.activebookmark or ''))
180 for d, v in self.state.iteritems():
180 for d, v in self.state.iteritems():
181 oldrev = repo[d].hex()
181 oldrev = repo[d].hex()
182 if v >= 0:
182 if v >= 0:
183 newrev = repo[v].hex()
183 newrev = repo[v].hex()
184 elif v == revtodo:
184 elif v == revtodo:
185 # To maintain format compatibility, we have to use nullid.
185 # To maintain format compatibility, we have to use nullid.
186 # Please do remove this special case when upgrading the format.
186 # Please do remove this special case when upgrading the format.
187 newrev = hex(nullid)
187 newrev = hex(nullid)
188 else:
188 else:
189 newrev = v
189 newrev = v
190 f.write("%s:%s\n" % (oldrev, newrev))
190 f.write("%s:%s\n" % (oldrev, newrev))
191 repo.ui.debug('rebase status stored\n')
191 repo.ui.debug('rebase status stored\n')
192
192
193 def restorestatus(self):
193 def restorestatus(self):
194 """Restore a previously stored status"""
194 """Restore a previously stored status"""
195 repo = self.repo
195 repo = self.repo
196 keepbranches = None
196 keepbranches = None
197 dest = None
197 dest = None
198 collapse = False
198 collapse = False
199 external = nullrev
199 external = nullrev
200 activebookmark = None
200 activebookmark = None
201 state = {}
201 state = {}
202
202
203 try:
203 try:
204 f = repo.vfs("rebasestate")
204 f = repo.vfs("rebasestate")
205 for i, l in enumerate(f.read().splitlines()):
205 for i, l in enumerate(f.read().splitlines()):
206 if i == 0:
206 if i == 0:
207 originalwd = repo[l].rev()
207 originalwd = repo[l].rev()
208 elif i == 1:
208 elif i == 1:
209 dest = repo[l].rev()
209 dest = repo[l].rev()
210 elif i == 2:
210 elif i == 2:
211 external = repo[l].rev()
211 external = repo[l].rev()
212 elif i == 3:
212 elif i == 3:
213 collapse = bool(int(l))
213 collapse = bool(int(l))
214 elif i == 4:
214 elif i == 4:
215 keep = bool(int(l))
215 keep = bool(int(l))
216 elif i == 5:
216 elif i == 5:
217 keepbranches = bool(int(l))
217 keepbranches = bool(int(l))
218 elif i == 6 and not (len(l) == 81 and ':' in l):
218 elif i == 6 and not (len(l) == 81 and ':' in l):
219 # line 6 is a recent addition, so for backwards
219 # line 6 is a recent addition, so for backwards
220 # compatibility check that the line doesn't look like the
220 # compatibility check that the line doesn't look like the
221 # oldrev:newrev lines
221 # oldrev:newrev lines
222 activebookmark = l
222 activebookmark = l
223 else:
223 else:
224 oldrev, newrev = l.split(':')
224 oldrev, newrev = l.split(':')
225 if newrev in (str(nullmerge), str(revignored),
225 if newrev in (str(nullmerge), str(revignored),
226 str(revprecursor), str(revpruned)):
226 str(revprecursor), str(revpruned)):
227 state[repo[oldrev].rev()] = int(newrev)
227 state[repo[oldrev].rev()] = int(newrev)
228 elif newrev == nullid:
228 elif newrev == nullid:
229 state[repo[oldrev].rev()] = revtodo
229 state[repo[oldrev].rev()] = revtodo
230 # Legacy compat special case
230 # Legacy compat special case
231 else:
231 else:
232 state[repo[oldrev].rev()] = repo[newrev].rev()
232 state[repo[oldrev].rev()] = repo[newrev].rev()
233
233
234 except IOError as err:
234 except IOError as err:
235 if err.errno != errno.ENOENT:
235 if err.errno != errno.ENOENT:
236 raise
236 raise
237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
238
238
239 if keepbranches is None:
239 if keepbranches is None:
240 raise error.Abort(_('.hg/rebasestate is incomplete'))
240 raise error.Abort(_('.hg/rebasestate is incomplete'))
241
241
242 skipped = set()
242 skipped = set()
243 # recompute the set of skipped revs
243 # recompute the set of skipped revs
244 if not collapse:
244 if not collapse:
245 seen = set([dest])
245 seen = set([dest])
246 for old, new in sorted(state.items()):
246 for old, new in sorted(state.items()):
247 if new != revtodo and new in seen:
247 if new != revtodo and new in seen:
248 skipped.add(old)
248 skipped.add(old)
249 seen.add(new)
249 seen.add(new)
250 repo.ui.debug('computed skipped revs: %s\n' %
250 repo.ui.debug('computed skipped revs: %s\n' %
251 (' '.join(str(r) for r in sorted(skipped)) or None))
251 (' '.join(str(r) for r in sorted(skipped)) or None))
252 repo.ui.debug('rebase status resumed\n')
252 repo.ui.debug('rebase status resumed\n')
253 _setrebasesetvisibility(repo, set(state.keys()) | set([originalwd]))
253 _setrebasesetvisibility(repo, set(state.keys()) | set([originalwd]))
254
254
255 self.originalwd = originalwd
255 self.originalwd = originalwd
256 self.dest = dest
256 self.dest = dest
257 self.state = state
257 self.state = state
258 self.skipped = skipped
258 self.skipped = skipped
259 self.collapsef = collapse
259 self.collapsef = collapse
260 self.keepf = keep
260 self.keepf = keep
261 self.keepbranchesf = keepbranches
261 self.keepbranchesf = keepbranches
262 self.external = external
262 self.external = external
263 self.activebookmark = activebookmark
263 self.activebookmark = activebookmark
264
264
265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
266 """Compute structures necessary for skipping obsolete revisions
266 """Compute structures necessary for skipping obsolete revisions
267
267
268 rebaserevs: iterable of all revisions that are to be rebased
268 rebaserevs: iterable of all revisions that are to be rebased
269 obsoleterevs: iterable of all obsolete revisions in rebaseset
269 obsoleterevs: iterable of all obsolete revisions in rebaseset
270 dest: a destination revision for the rebase operation
270 dest: a destination revision for the rebase operation
271 """
271 """
272 self.obsoletenotrebased = {}
272 self.obsoletenotrebased = {}
273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
274 default=True):
274 default=True):
275 return
275 return
276 rebaseset = set(rebaserevs)
276 rebaseset = set(rebaserevs)
277 obsoleteset = set(obsoleterevs)
277 obsoleteset = set(obsoleterevs)
278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
279 obsoleteset, dest)
279 obsoleteset, dest)
280 skippedset = set(self.obsoletenotrebased)
280 skippedset = set(self.obsoletenotrebased)
281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
282
282
283 def _prepareabortorcontinue(self, isabort):
283 def _prepareabortorcontinue(self, isabort):
284 try:
284 try:
285 self.restorestatus()
285 self.restorestatus()
286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
287 except error.RepoLookupError:
287 except error.RepoLookupError:
288 if isabort:
288 if isabort:
289 clearstatus(self.repo)
289 clearstatus(self.repo)
290 clearcollapsemsg(self.repo)
290 clearcollapsemsg(self.repo)
291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
292 ' only broken state is cleared)\n'))
292 ' only broken state is cleared)\n'))
293 return 0
293 return 0
294 else:
294 else:
295 msg = _('cannot continue inconsistent rebase')
295 msg = _('cannot continue inconsistent rebase')
296 hint = _('use "hg rebase --abort" to clear broken state')
296 hint = _('use "hg rebase --abort" to clear broken state')
297 raise error.Abort(msg, hint=hint)
297 raise error.Abort(msg, hint=hint)
298 if isabort:
298 if isabort:
299 return abort(self.repo, self.originalwd, self.dest,
299 return abort(self.repo, self.originalwd, self.dest,
300 self.state, activebookmark=self.activebookmark)
300 self.state, activebookmark=self.activebookmark)
301
301
302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
304
304
305 def _preparenewrebase(self, dest, rebaseset):
305 def _preparenewrebase(self, dest, rebaseset):
306 if dest is None:
306 if dest is None:
307 return _nothingtorebase()
307 return _nothingtorebase()
308
308
309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
310 if (not (self.keepf or allowunstable)
310 if (not (self.keepf or allowunstable)
311 and self.repo.revs('first(children(%ld) - %ld)',
311 and self.repo.revs('first(children(%ld) - %ld)',
312 rebaseset, rebaseset)):
312 rebaseset, rebaseset)):
313 raise error.Abort(
313 raise error.Abort(
314 _("can't remove original changesets with"
314 _("can't remove original changesets with"
315 " unrebased descendants"),
315 " unrebased descendants"),
316 hint=_('use --keep to keep original changesets'))
316 hint=_('use --keep to keep original changesets'))
317
317
318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
319 self._handleskippingobsolete(rebaseset, obsrevs, dest)
319 self._handleskippingobsolete(rebaseset, obsrevs, dest)
320
320
321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
322 self.obsoletenotrebased)
322 self.obsoletenotrebased)
323
323
324 if not result:
324 if not result:
325 # Empty state built, nothing to rebase
325 # Empty state built, nothing to rebase
326 self.ui.status(_('nothing to rebase\n'))
326 self.ui.status(_('nothing to rebase\n'))
327 return _nothingtorebase()
327 return _nothingtorebase()
328
328
329 for root in self.repo.set('roots(%ld)', rebaseset):
329 for root in self.repo.set('roots(%ld)', rebaseset):
330 if not self.keepf and not root.mutable():
330 if not self.keepf and not root.mutable():
331 raise error.Abort(_("can't rebase public changeset %s")
331 raise error.Abort(_("can't rebase public changeset %s")
332 % root,
332 % root,
333 hint=_("see 'hg help phases' for details"))
333 hint=_("see 'hg help phases' for details"))
334
334
335 (self.originalwd, self.dest, self.state) = result
335 (self.originalwd, self.dest, self.state) = result
336 if self.collapsef:
336 if self.collapsef:
337 self.destancestors = self.repo.changelog.ancestors(
337 self.destancestors = self.repo.changelog.ancestors(
338 [self.dest],
338 [self.dest],
339 inclusive=True)
339 inclusive=True)
340 self.external = externalparent(self.repo, self.state,
340 self.external = externalparent(self.repo, self.state,
341 self.destancestors)
341 self.destancestors)
342
342
343 if dest.closesbranch() and not self.keepbranchesf:
343 if dest.closesbranch() and not self.keepbranchesf:
344 self.ui.status(_('reopening closed branch head %s\n') % dest)
344 self.ui.status(_('reopening closed branch head %s\n') % dest)
345
345
346 def _performrebase(self, tr):
346 def _performrebase(self, tr):
347 repo, ui, opts = self.repo, self.ui, self.opts
347 repo, ui, opts = self.repo, self.ui, self.opts
348 if self.keepbranchesf:
348 if self.keepbranchesf:
349 # insert _savebranch at the start of extrafns so if
349 # insert _savebranch at the start of extrafns so if
350 # there's a user-provided extrafn it can clobber branch if
350 # there's a user-provided extrafn it can clobber branch if
351 # desired
351 # desired
352 self.extrafns.insert(0, _savebranch)
352 self.extrafns.insert(0, _savebranch)
353 if self.collapsef:
353 if self.collapsef:
354 branches = set()
354 branches = set()
355 for rev in self.state:
355 for rev in self.state:
356 branches.add(repo[rev].branch())
356 branches.add(repo[rev].branch())
357 if len(branches) > 1:
357 if len(branches) > 1:
358 raise error.Abort(_('cannot collapse multiple named '
358 raise error.Abort(_('cannot collapse multiple named '
359 'branches'))
359 'branches'))
360
360
361 # Rebase
361 # Rebase
362 if not self.destancestors:
362 if not self.destancestors:
363 self.destancestors = repo.changelog.ancestors([self.dest],
363 self.destancestors = repo.changelog.ancestors([self.dest],
364 inclusive=True)
364 inclusive=True)
365
365
366 # Keep track of the current bookmarks in order to reset them later
366 # Keep track of the current bookmarks in order to reset them later
367 self.currentbookmarks = repo._bookmarks.copy()
367 self.currentbookmarks = repo._bookmarks.copy()
368 self.activebookmark = self.activebookmark or repo._activebookmark
368 self.activebookmark = self.activebookmark or repo._activebookmark
369 if self.activebookmark:
369 if self.activebookmark:
370 bookmarks.deactivate(repo)
370 bookmarks.deactivate(repo)
371
371
372 # Store the state before we begin so users can run 'hg rebase --abort'
372 # Store the state before we begin so users can run 'hg rebase --abort'
373 # if we fail before the transaction closes.
373 # if we fail before the transaction closes.
374 self.storestatus()
374 self.storestatus()
375
375
376 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
376 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
377 cands = [k for k, v in self.state.iteritems() if v == revtodo]
377 cands = [k for k, v in self.state.iteritems() if v == revtodo]
378 total = len(cands)
378 total = len(cands)
379 pos = 0
379 pos = 0
380 for rev in sortedrevs:
380 for rev in sortedrevs:
381 ctx = repo[rev]
381 ctx = repo[rev]
382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
382 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
383 ctx.description().split('\n', 1)[0])
383 ctx.description().split('\n', 1)[0])
384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
384 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
385 if names:
385 if names:
386 desc += ' (%s)' % ' '.join(names)
386 desc += ' (%s)' % ' '.join(names)
387 if self.state[rev] == revtodo:
387 if self.state[rev] == rev:
388 ui.status(_('already rebased %s\n') % desc)
389 elif self.state[rev] == revtodo:
388 pos += 1
390 pos += 1
389 ui.status(_('rebasing %s\n') % desc)
391 ui.status(_('rebasing %s\n') % desc)
390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
392 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
391 _('changesets'), total)
393 _('changesets'), total)
392 p1, p2, base = defineparents(repo, rev, self.dest,
394 p1, p2, base = defineparents(repo, rev, self.dest,
393 self.state,
395 self.state,
394 self.destancestors,
396 self.destancestors,
395 self.obsoletenotrebased)
397 self.obsoletenotrebased)
396 self.storestatus(tr=tr)
398 self.storestatus(tr=tr)
397 storecollapsemsg(repo, self.collapsemsg)
399 storecollapsemsg(repo, self.collapsemsg)
398 if len(repo[None].parents()) == 2:
400 if len(repo[None].parents()) == 2:
399 repo.ui.debug('resuming interrupted rebase\n')
401 repo.ui.debug('resuming interrupted rebase\n')
400 else:
402 else:
401 try:
403 try:
402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
404 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
403 'rebase')
405 'rebase')
404 stats = rebasenode(repo, rev, p1, base, self.state,
406 stats = rebasenode(repo, rev, p1, base, self.state,
405 self.collapsef, self.dest)
407 self.collapsef, self.dest)
406 if stats and stats[3] > 0:
408 if stats and stats[3] > 0:
407 raise error.InterventionRequired(
409 raise error.InterventionRequired(
408 _('unresolved conflicts (see hg '
410 _('unresolved conflicts (see hg '
409 'resolve, then hg rebase --continue)'))
411 'resolve, then hg rebase --continue)'))
410 finally:
412 finally:
411 ui.setconfig('ui', 'forcemerge', '', 'rebase')
413 ui.setconfig('ui', 'forcemerge', '', 'rebase')
412 if not self.collapsef:
414 if not self.collapsef:
413 merging = p2 != nullrev
415 merging = p2 != nullrev
414 editform = cmdutil.mergeeditform(merging, 'rebase')
416 editform = cmdutil.mergeeditform(merging, 'rebase')
415 editor = cmdutil.getcommiteditor(editform=editform, **opts)
417 editor = cmdutil.getcommiteditor(editform=editform, **opts)
416 newnode = concludenode(repo, rev, p1, p2,
418 newnode = concludenode(repo, rev, p1, p2,
417 extrafn=_makeextrafn(self.extrafns),
419 extrafn=_makeextrafn(self.extrafns),
418 editor=editor,
420 editor=editor,
419 keepbranches=self.keepbranchesf,
421 keepbranches=self.keepbranchesf,
420 date=self.date)
422 date=self.date)
421 else:
423 else:
422 # Skip commit if we are collapsing
424 # Skip commit if we are collapsing
423 repo.dirstate.beginparentchange()
425 repo.dirstate.beginparentchange()
424 repo.setparents(repo[p1].node())
426 repo.setparents(repo[p1].node())
425 repo.dirstate.endparentchange()
427 repo.dirstate.endparentchange()
426 newnode = None
428 newnode = None
427 # Update the state
429 # Update the state
428 if newnode is not None:
430 if newnode is not None:
429 self.state[rev] = repo[newnode].rev()
431 self.state[rev] = repo[newnode].rev()
430 ui.debug('rebased as %s\n' % short(newnode))
432 ui.debug('rebased as %s\n' % short(newnode))
431 else:
433 else:
432 if not self.collapsef:
434 if not self.collapsef:
433 ui.warn(_('note: rebase of %d:%s created no changes '
435 ui.warn(_('note: rebase of %d:%s created no changes '
434 'to commit\n') % (rev, ctx))
436 'to commit\n') % (rev, ctx))
435 self.skipped.add(rev)
437 self.skipped.add(rev)
436 self.state[rev] = p1
438 self.state[rev] = p1
437 ui.debug('next revision set to %s\n' % p1)
439 ui.debug('next revision set to %s\n' % p1)
438 elif self.state[rev] == nullmerge:
440 elif self.state[rev] == nullmerge:
439 ui.debug('ignoring null merge rebase of %s\n' % rev)
441 ui.debug('ignoring null merge rebase of %s\n' % rev)
440 elif self.state[rev] == revignored:
442 elif self.state[rev] == revignored:
441 ui.status(_('not rebasing ignored %s\n') % desc)
443 ui.status(_('not rebasing ignored %s\n') % desc)
442 elif self.state[rev] == revprecursor:
444 elif self.state[rev] == revprecursor:
443 destctx = repo[self.obsoletenotrebased[rev]]
445 destctx = repo[self.obsoletenotrebased[rev]]
444 descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
446 descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
445 destctx.description().split('\n', 1)[0])
447 destctx.description().split('\n', 1)[0])
446 msg = _('note: not rebasing %s, already in destination as %s\n')
448 msg = _('note: not rebasing %s, already in destination as %s\n')
447 ui.status(msg % (desc, descdest))
449 ui.status(msg % (desc, descdest))
448 elif self.state[rev] == revpruned:
450 elif self.state[rev] == revpruned:
449 msg = _('note: not rebasing %s, it has no successor\n')
451 msg = _('note: not rebasing %s, it has no successor\n')
450 ui.status(msg % desc)
452 ui.status(msg % desc)
451 else:
453 else:
452 ui.status(_('already rebased %s as %s\n') %
454 ui.status(_('already rebased %s as %s\n') %
453 (desc, repo[self.state[rev]]))
455 (desc, repo[self.state[rev]]))
454
456
455 ui.progress(_('rebasing'), None)
457 ui.progress(_('rebasing'), None)
456 ui.note(_('rebase merging completed\n'))
458 ui.note(_('rebase merging completed\n'))
457
459
458 def _finishrebase(self):
460 def _finishrebase(self):
459 repo, ui, opts = self.repo, self.ui, self.opts
461 repo, ui, opts = self.repo, self.ui, self.opts
460 if self.collapsef and not self.keepopen:
462 if self.collapsef and not self.keepopen:
461 p1, p2, _base = defineparents(repo, min(self.state),
463 p1, p2, _base = defineparents(repo, min(self.state),
462 self.dest, self.state,
464 self.dest, self.state,
463 self.destancestors,
465 self.destancestors,
464 self.obsoletenotrebased)
466 self.obsoletenotrebased)
465 editopt = opts.get('edit')
467 editopt = opts.get('edit')
466 editform = 'rebase.collapse'
468 editform = 'rebase.collapse'
467 if self.collapsemsg:
469 if self.collapsemsg:
468 commitmsg = self.collapsemsg
470 commitmsg = self.collapsemsg
469 else:
471 else:
470 commitmsg = 'Collapsed revision'
472 commitmsg = 'Collapsed revision'
471 for rebased in self.state:
473 for rebased in self.state:
472 if rebased not in self.skipped and\
474 if rebased not in self.skipped and\
473 self.state[rebased] > nullmerge:
475 self.state[rebased] > nullmerge:
474 commitmsg += '\n* %s' % repo[rebased].description()
476 commitmsg += '\n* %s' % repo[rebased].description()
475 editopt = True
477 editopt = True
476 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
478 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
477 revtoreuse = max(self.state)
479 revtoreuse = max(self.state)
478 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
480 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
479 try:
481 try:
480 newnode = concludenode(repo, revtoreuse, p1, self.external,
482 newnode = concludenode(repo, revtoreuse, p1, self.external,
481 commitmsg=commitmsg,
483 commitmsg=commitmsg,
482 extrafn=_makeextrafn(self.extrafns),
484 extrafn=_makeextrafn(self.extrafns),
483 editor=editor,
485 editor=editor,
484 keepbranches=self.keepbranchesf,
486 keepbranches=self.keepbranchesf,
485 date=self.date)
487 date=self.date)
486 dsguard.close()
488 dsguard.close()
487 release(dsguard)
489 release(dsguard)
488 except error.InterventionRequired:
490 except error.InterventionRequired:
489 dsguard.close()
491 dsguard.close()
490 release(dsguard)
492 release(dsguard)
491 raise
493 raise
492 except Exception:
494 except Exception:
493 release(dsguard)
495 release(dsguard)
494 raise
496 raise
495
497
496 if newnode is None:
498 if newnode is None:
497 newrev = self.dest
499 newrev = self.dest
498 else:
500 else:
499 newrev = repo[newnode].rev()
501 newrev = repo[newnode].rev()
500 for oldrev in self.state.iterkeys():
502 for oldrev in self.state.iterkeys():
501 if self.state[oldrev] > nullmerge:
503 if self.state[oldrev] > nullmerge:
502 self.state[oldrev] = newrev
504 self.state[oldrev] = newrev
503
505
504 if 'qtip' in repo.tags():
506 if 'qtip' in repo.tags():
505 updatemq(repo, self.state, self.skipped, **opts)
507 updatemq(repo, self.state, self.skipped, **opts)
506
508
507 if self.currentbookmarks:
509 if self.currentbookmarks:
508 # Nodeids are needed to reset bookmarks
510 # Nodeids are needed to reset bookmarks
509 nstate = {}
511 nstate = {}
510 for k, v in self.state.iteritems():
512 for k, v in self.state.iteritems():
511 if v > nullmerge:
513 if v > nullmerge and v != k:
512 nstate[repo[k].node()] = repo[v].node()
514 nstate[repo[k].node()] = repo[v].node()
513 elif v == revprecursor:
515 elif v == revprecursor:
514 succ = self.obsoletenotrebased[k]
516 succ = self.obsoletenotrebased[k]
515 nstate[repo[k].node()] = repo[succ].node()
517 nstate[repo[k].node()] = repo[succ].node()
516 # XXX this is the same as dest.node() for the non-continue path --
518 # XXX this is the same as dest.node() for the non-continue path --
517 # this should probably be cleaned up
519 # this should probably be cleaned up
518 destnode = repo[self.dest].node()
520 destnode = repo[self.dest].node()
519
521
520 # restore original working directory
522 # restore original working directory
521 # (we do this before stripping)
523 # (we do this before stripping)
522 newwd = self.state.get(self.originalwd, self.originalwd)
524 newwd = self.state.get(self.originalwd, self.originalwd)
523 if newwd == revprecursor:
525 if newwd == revprecursor:
524 newwd = self.obsoletenotrebased[self.originalwd]
526 newwd = self.obsoletenotrebased[self.originalwd]
525 elif newwd < 0:
527 elif newwd < 0:
526 # original directory is a parent of rebase set root or ignored
528 # original directory is a parent of rebase set root or ignored
527 newwd = self.originalwd
529 newwd = self.originalwd
528 if newwd not in [c.rev() for c in repo[None].parents()]:
530 if newwd not in [c.rev() for c in repo[None].parents()]:
529 ui.note(_("update back to initial working directory parent\n"))
531 ui.note(_("update back to initial working directory parent\n"))
530 hg.updaterepo(repo, newwd, False)
532 hg.updaterepo(repo, newwd, False)
531
533
532 if self.currentbookmarks:
534 if self.currentbookmarks:
533 with repo.transaction('bookmark') as tr:
535 with repo.transaction('bookmark') as tr:
534 updatebookmarks(repo, destnode, nstate,
536 updatebookmarks(repo, destnode, nstate,
535 self.currentbookmarks, tr)
537 self.currentbookmarks, tr)
536 if self.activebookmark not in repo._bookmarks:
538 if self.activebookmark not in repo._bookmarks:
537 # active bookmark was divergent one and has been deleted
539 # active bookmark was divergent one and has been deleted
538 self.activebookmark = None
540 self.activebookmark = None
539
541
540 if not self.keepf:
542 if not self.keepf:
541 collapsedas = None
543 collapsedas = None
542 if self.collapsef:
544 if self.collapsef:
543 collapsedas = newnode
545 collapsedas = newnode
544 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
546 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
545
547
546 clearstatus(repo)
548 clearstatus(repo)
547 clearcollapsemsg(repo)
549 clearcollapsemsg(repo)
548
550
549 ui.note(_("rebase completed\n"))
551 ui.note(_("rebase completed\n"))
550 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
552 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
551 if self.skipped:
553 if self.skipped:
552 skippedlen = len(self.skipped)
554 skippedlen = len(self.skipped)
553 ui.note(_("%d revisions have been skipped\n") % skippedlen)
555 ui.note(_("%d revisions have been skipped\n") % skippedlen)
554
556
555 if (self.activebookmark and
557 if (self.activebookmark and
556 repo['.'].node() == repo._bookmarks[self.activebookmark]):
558 repo['.'].node() == repo._bookmarks[self.activebookmark]):
557 bookmarks.activate(repo, self.activebookmark)
559 bookmarks.activate(repo, self.activebookmark)
558
560
559 @command('rebase',
561 @command('rebase',
560 [('s', 'source', '',
562 [('s', 'source', '',
561 _('rebase the specified changeset and descendants'), _('REV')),
563 _('rebase the specified changeset and descendants'), _('REV')),
562 ('b', 'base', '',
564 ('b', 'base', '',
563 _('rebase everything from branching point of specified changeset'),
565 _('rebase everything from branching point of specified changeset'),
564 _('REV')),
566 _('REV')),
565 ('r', 'rev', [],
567 ('r', 'rev', [],
566 _('rebase these revisions'),
568 _('rebase these revisions'),
567 _('REV')),
569 _('REV')),
568 ('d', 'dest', '',
570 ('d', 'dest', '',
569 _('rebase onto the specified changeset'), _('REV')),
571 _('rebase onto the specified changeset'), _('REV')),
570 ('', 'collapse', False, _('collapse the rebased changesets')),
572 ('', 'collapse', False, _('collapse the rebased changesets')),
571 ('m', 'message', '',
573 ('m', 'message', '',
572 _('use text as collapse commit message'), _('TEXT')),
574 _('use text as collapse commit message'), _('TEXT')),
573 ('e', 'edit', False, _('invoke editor on commit messages')),
575 ('e', 'edit', False, _('invoke editor on commit messages')),
574 ('l', 'logfile', '',
576 ('l', 'logfile', '',
575 _('read collapse commit message from file'), _('FILE')),
577 _('read collapse commit message from file'), _('FILE')),
576 ('k', 'keep', False, _('keep original changesets')),
578 ('k', 'keep', False, _('keep original changesets')),
577 ('', 'keepbranches', False, _('keep original branch names')),
579 ('', 'keepbranches', False, _('keep original branch names')),
578 ('D', 'detach', False, _('(DEPRECATED)')),
580 ('D', 'detach', False, _('(DEPRECATED)')),
579 ('i', 'interactive', False, _('(DEPRECATED)')),
581 ('i', 'interactive', False, _('(DEPRECATED)')),
580 ('t', 'tool', '', _('specify merge tool')),
582 ('t', 'tool', '', _('specify merge tool')),
581 ('c', 'continue', False, _('continue an interrupted rebase')),
583 ('c', 'continue', False, _('continue an interrupted rebase')),
582 ('a', 'abort', False, _('abort an interrupted rebase'))] +
584 ('a', 'abort', False, _('abort an interrupted rebase'))] +
583 templateopts,
585 templateopts,
584 _('[-s REV | -b REV] [-d REV] [OPTION]'))
586 _('[-s REV | -b REV] [-d REV] [OPTION]'))
585 def rebase(ui, repo, **opts):
587 def rebase(ui, repo, **opts):
586 """move changeset (and descendants) to a different branch
588 """move changeset (and descendants) to a different branch
587
589
588 Rebase uses repeated merging to graft changesets from one part of
590 Rebase uses repeated merging to graft changesets from one part of
589 history (the source) onto another (the destination). This can be
591 history (the source) onto another (the destination). This can be
590 useful for linearizing *local* changes relative to a master
592 useful for linearizing *local* changes relative to a master
591 development tree.
593 development tree.
592
594
593 Published commits cannot be rebased (see :hg:`help phases`).
595 Published commits cannot be rebased (see :hg:`help phases`).
594 To copy commits, see :hg:`help graft`.
596 To copy commits, see :hg:`help graft`.
595
597
596 If you don't specify a destination changeset (``-d/--dest``), rebase
598 If you don't specify a destination changeset (``-d/--dest``), rebase
597 will use the same logic as :hg:`merge` to pick a destination. if
599 will use the same logic as :hg:`merge` to pick a destination. if
598 the current branch contains exactly one other head, the other head
600 the current branch contains exactly one other head, the other head
599 is merged with by default. Otherwise, an explicit revision with
601 is merged with by default. Otherwise, an explicit revision with
600 which to merge with must be provided. (destination changeset is not
602 which to merge with must be provided. (destination changeset is not
601 modified by rebasing, but new changesets are added as its
603 modified by rebasing, but new changesets are added as its
602 descendants.)
604 descendants.)
603
605
604 Here are the ways to select changesets:
606 Here are the ways to select changesets:
605
607
606 1. Explicitly select them using ``--rev``.
608 1. Explicitly select them using ``--rev``.
607
609
608 2. Use ``--source`` to select a root changeset and include all of its
610 2. Use ``--source`` to select a root changeset and include all of its
609 descendants.
611 descendants.
610
612
611 3. Use ``--base`` to select a changeset; rebase will find ancestors
613 3. Use ``--base`` to select a changeset; rebase will find ancestors
612 and their descendants which are not also ancestors of the destination.
614 and their descendants which are not also ancestors of the destination.
613
615
614 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
616 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
615 rebase will use ``--base .`` as above.
617 rebase will use ``--base .`` as above.
616
618
617 Rebase will destroy original changesets unless you use ``--keep``.
619 Rebase will destroy original changesets unless you use ``--keep``.
618 It will also move your bookmarks (even if you do).
620 It will also move your bookmarks (even if you do).
619
621
620 Some changesets may be dropped if they do not contribute changes
622 Some changesets may be dropped if they do not contribute changes
621 (e.g. merges from the destination branch).
623 (e.g. merges from the destination branch).
622
624
623 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
625 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
624 a named branch with two heads. You will need to explicitly specify source
626 a named branch with two heads. You will need to explicitly specify source
625 and/or destination.
627 and/or destination.
626
628
627 If you need to use a tool to automate merge/conflict decisions, you
629 If you need to use a tool to automate merge/conflict decisions, you
628 can specify one with ``--tool``, see :hg:`help merge-tools`.
630 can specify one with ``--tool``, see :hg:`help merge-tools`.
629 As a caveat: the tool will not be used to mediate when a file was
631 As a caveat: the tool will not be used to mediate when a file was
630 deleted, there is no hook presently available for this.
632 deleted, there is no hook presently available for this.
631
633
632 If a rebase is interrupted to manually resolve a conflict, it can be
634 If a rebase is interrupted to manually resolve a conflict, it can be
633 continued with --continue/-c or aborted with --abort/-a.
635 continued with --continue/-c or aborted with --abort/-a.
634
636
635 .. container:: verbose
637 .. container:: verbose
636
638
637 Examples:
639 Examples:
638
640
639 - move "local changes" (current commit back to branching point)
641 - move "local changes" (current commit back to branching point)
640 to the current branch tip after a pull::
642 to the current branch tip after a pull::
641
643
642 hg rebase
644 hg rebase
643
645
644 - move a single changeset to the stable branch::
646 - move a single changeset to the stable branch::
645
647
646 hg rebase -r 5f493448 -d stable
648 hg rebase -r 5f493448 -d stable
647
649
648 - splice a commit and all its descendants onto another part of history::
650 - splice a commit and all its descendants onto another part of history::
649
651
650 hg rebase --source c0c3 --dest 4cf9
652 hg rebase --source c0c3 --dest 4cf9
651
653
652 - rebase everything on a branch marked by a bookmark onto the
654 - rebase everything on a branch marked by a bookmark onto the
653 default branch::
655 default branch::
654
656
655 hg rebase --base myfeature --dest default
657 hg rebase --base myfeature --dest default
656
658
657 - collapse a sequence of changes into a single commit::
659 - collapse a sequence of changes into a single commit::
658
660
659 hg rebase --collapse -r 1520:1525 -d .
661 hg rebase --collapse -r 1520:1525 -d .
660
662
661 - move a named branch while preserving its name::
663 - move a named branch while preserving its name::
662
664
663 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
665 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
664
666
665 Configuration Options:
667 Configuration Options:
666
668
667 You can make rebase require a destination if you set the following config
669 You can make rebase require a destination if you set the following config
668 option::
670 option::
669
671
670 [commands]
672 [commands]
671 rebase.requiredest = True
673 rebase.requiredest = True
672
674
673 Return Values:
675 Return Values:
674
676
675 Returns 0 on success, 1 if nothing to rebase or there are
677 Returns 0 on success, 1 if nothing to rebase or there are
676 unresolved conflicts.
678 unresolved conflicts.
677
679
678 """
680 """
679 rbsrt = rebaseruntime(repo, ui, opts)
681 rbsrt = rebaseruntime(repo, ui, opts)
680
682
681 lock = wlock = None
683 lock = wlock = None
682 try:
684 try:
683 wlock = repo.wlock()
685 wlock = repo.wlock()
684 lock = repo.lock()
686 lock = repo.lock()
685
687
686 # Validate input and define rebasing points
688 # Validate input and define rebasing points
687 destf = opts.get('dest', None)
689 destf = opts.get('dest', None)
688 srcf = opts.get('source', None)
690 srcf = opts.get('source', None)
689 basef = opts.get('base', None)
691 basef = opts.get('base', None)
690 revf = opts.get('rev', [])
692 revf = opts.get('rev', [])
691 # search default destination in this space
693 # search default destination in this space
692 # used in the 'hg pull --rebase' case, see issue 5214.
694 # used in the 'hg pull --rebase' case, see issue 5214.
693 destspace = opts.get('_destspace')
695 destspace = opts.get('_destspace')
694 contf = opts.get('continue')
696 contf = opts.get('continue')
695 abortf = opts.get('abort')
697 abortf = opts.get('abort')
696 if opts.get('interactive'):
698 if opts.get('interactive'):
697 try:
699 try:
698 if extensions.find('histedit'):
700 if extensions.find('histedit'):
699 enablehistedit = ''
701 enablehistedit = ''
700 except KeyError:
702 except KeyError:
701 enablehistedit = " --config extensions.histedit="
703 enablehistedit = " --config extensions.histedit="
702 help = "hg%s help -e histedit" % enablehistedit
704 help = "hg%s help -e histedit" % enablehistedit
703 msg = _("interactive history editing is supported by the "
705 msg = _("interactive history editing is supported by the "
704 "'histedit' extension (see \"%s\")") % help
706 "'histedit' extension (see \"%s\")") % help
705 raise error.Abort(msg)
707 raise error.Abort(msg)
706
708
707 if rbsrt.collapsemsg and not rbsrt.collapsef:
709 if rbsrt.collapsemsg and not rbsrt.collapsef:
708 raise error.Abort(
710 raise error.Abort(
709 _('message can only be specified with collapse'))
711 _('message can only be specified with collapse'))
710
712
711 if contf or abortf:
713 if contf or abortf:
712 if contf and abortf:
714 if contf and abortf:
713 raise error.Abort(_('cannot use both abort and continue'))
715 raise error.Abort(_('cannot use both abort and continue'))
714 if rbsrt.collapsef:
716 if rbsrt.collapsef:
715 raise error.Abort(
717 raise error.Abort(
716 _('cannot use collapse with continue or abort'))
718 _('cannot use collapse with continue or abort'))
717 if srcf or basef or destf:
719 if srcf or basef or destf:
718 raise error.Abort(
720 raise error.Abort(
719 _('abort and continue do not allow specifying revisions'))
721 _('abort and continue do not allow specifying revisions'))
720 if abortf and opts.get('tool', False):
722 if abortf and opts.get('tool', False):
721 ui.warn(_('tool option will be ignored\n'))
723 ui.warn(_('tool option will be ignored\n'))
722 if contf:
724 if contf:
723 ms = mergemod.mergestate.read(repo)
725 ms = mergemod.mergestate.read(repo)
724 mergeutil.checkunresolved(ms)
726 mergeutil.checkunresolved(ms)
725
727
726 retcode = rbsrt._prepareabortorcontinue(abortf)
728 retcode = rbsrt._prepareabortorcontinue(abortf)
727 if retcode is not None:
729 if retcode is not None:
728 return retcode
730 return retcode
729 else:
731 else:
730 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
732 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
731 destspace=destspace)
733 destspace=destspace)
732 retcode = rbsrt._preparenewrebase(dest, rebaseset)
734 retcode = rbsrt._preparenewrebase(dest, rebaseset)
733 if retcode is not None:
735 if retcode is not None:
734 return retcode
736 return retcode
735
737
736 with repo.transaction('rebase') as tr:
738 with repo.transaction('rebase') as tr:
737 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
739 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
738 try:
740 try:
739 rbsrt._performrebase(tr)
741 rbsrt._performrebase(tr)
740 dsguard.close()
742 dsguard.close()
741 release(dsguard)
743 release(dsguard)
742 except error.InterventionRequired:
744 except error.InterventionRequired:
743 dsguard.close()
745 dsguard.close()
744 release(dsguard)
746 release(dsguard)
745 tr.close()
747 tr.close()
746 raise
748 raise
747 except Exception:
749 except Exception:
748 release(dsguard)
750 release(dsguard)
749 raise
751 raise
750 rbsrt._finishrebase()
752 rbsrt._finishrebase()
751 finally:
753 finally:
752 release(lock, wlock)
754 release(lock, wlock)
753
755
754 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
756 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
755 destspace=None):
757 destspace=None):
756 """use revisions argument to define destination and rebase set
758 """use revisions argument to define destination and rebase set
757 """
759 """
758 if revf is None:
760 if revf is None:
759 revf = []
761 revf = []
760
762
761 # destspace is here to work around issues with `hg pull --rebase` see
763 # destspace is here to work around issues with `hg pull --rebase` see
762 # issue5214 for details
764 # issue5214 for details
763 if srcf and basef:
765 if srcf and basef:
764 raise error.Abort(_('cannot specify both a source and a base'))
766 raise error.Abort(_('cannot specify both a source and a base'))
765 if revf and basef:
767 if revf and basef:
766 raise error.Abort(_('cannot specify both a revision and a base'))
768 raise error.Abort(_('cannot specify both a revision and a base'))
767 if revf and srcf:
769 if revf and srcf:
768 raise error.Abort(_('cannot specify both a revision and a source'))
770 raise error.Abort(_('cannot specify both a revision and a source'))
769
771
770 cmdutil.checkunfinished(repo)
772 cmdutil.checkunfinished(repo)
771 cmdutil.bailifchanged(repo)
773 cmdutil.bailifchanged(repo)
772
774
773 if ui.configbool('commands', 'rebase.requiredest') and not destf:
775 if ui.configbool('commands', 'rebase.requiredest') and not destf:
774 raise error.Abort(_('you must specify a destination'),
776 raise error.Abort(_('you must specify a destination'),
775 hint=_('use: hg rebase -d REV'))
777 hint=_('use: hg rebase -d REV'))
776
778
777 if destf:
779 if destf:
778 dest = scmutil.revsingle(repo, destf)
780 dest = scmutil.revsingle(repo, destf)
779
781
780 if revf:
782 if revf:
781 rebaseset = scmutil.revrange(repo, revf)
783 rebaseset = scmutil.revrange(repo, revf)
782 if not rebaseset:
784 if not rebaseset:
783 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
785 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
784 return None, None
786 return None, None
785 elif srcf:
787 elif srcf:
786 src = scmutil.revrange(repo, [srcf])
788 src = scmutil.revrange(repo, [srcf])
787 if not src:
789 if not src:
788 ui.status(_('empty "source" revision set - nothing to rebase\n'))
790 ui.status(_('empty "source" revision set - nothing to rebase\n'))
789 return None, None
791 return None, None
790 rebaseset = repo.revs('(%ld)::', src)
792 rebaseset = repo.revs('(%ld)::', src)
791 assert rebaseset
793 assert rebaseset
792 else:
794 else:
793 base = scmutil.revrange(repo, [basef or '.'])
795 base = scmutil.revrange(repo, [basef or '.'])
794 if not base:
796 if not base:
795 ui.status(_('empty "base" revision set - '
797 ui.status(_('empty "base" revision set - '
796 "can't compute rebase set\n"))
798 "can't compute rebase set\n"))
797 return None, None
799 return None, None
798 if not destf:
800 if not destf:
799 dest = repo[_destrebase(repo, base, destspace=destspace)]
801 dest = repo[_destrebase(repo, base, destspace=destspace)]
800 destf = str(dest)
802 destf = str(dest)
801
803
802 roots = [] # selected children of branching points
804 roots = [] # selected children of branching points
803 bpbase = {} # {branchingpoint: [origbase]}
805 bpbase = {} # {branchingpoint: [origbase]}
804 for b in base: # group bases by branching points
806 for b in base: # group bases by branching points
805 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
807 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
806 bpbase[bp] = bpbase.get(bp, []) + [b]
808 bpbase[bp] = bpbase.get(bp, []) + [b]
807 if None in bpbase:
809 if None in bpbase:
808 # emulate the old behavior, showing "nothing to rebase" (a better
810 # emulate the old behavior, showing "nothing to rebase" (a better
809 # behavior may be abort with "cannot find branching point" error)
811 # behavior may be abort with "cannot find branching point" error)
810 bpbase.clear()
812 bpbase.clear()
811 for bp, bs in bpbase.iteritems(): # calculate roots
813 for bp, bs in bpbase.iteritems(): # calculate roots
812 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
814 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
813
815
814 rebaseset = repo.revs('%ld::', roots)
816 rebaseset = repo.revs('%ld::', roots)
815
817
816 if not rebaseset:
818 if not rebaseset:
817 # transform to list because smartsets are not comparable to
819 # transform to list because smartsets are not comparable to
818 # lists. This should be improved to honor laziness of
820 # lists. This should be improved to honor laziness of
819 # smartset.
821 # smartset.
820 if list(base) == [dest.rev()]:
822 if list(base) == [dest.rev()]:
821 if basef:
823 if basef:
822 ui.status(_('nothing to rebase - %s is both "base"'
824 ui.status(_('nothing to rebase - %s is both "base"'
823 ' and destination\n') % dest)
825 ' and destination\n') % dest)
824 else:
826 else:
825 ui.status(_('nothing to rebase - working directory '
827 ui.status(_('nothing to rebase - working directory '
826 'parent is also destination\n'))
828 'parent is also destination\n'))
827 elif not repo.revs('%ld - ::%d', base, dest):
829 elif not repo.revs('%ld - ::%d', base, dest):
828 if basef:
830 if basef:
829 ui.status(_('nothing to rebase - "base" %s is '
831 ui.status(_('nothing to rebase - "base" %s is '
830 'already an ancestor of destination '
832 'already an ancestor of destination '
831 '%s\n') %
833 '%s\n') %
832 ('+'.join(str(repo[r]) for r in base),
834 ('+'.join(str(repo[r]) for r in base),
833 dest))
835 dest))
834 else:
836 else:
835 ui.status(_('nothing to rebase - working '
837 ui.status(_('nothing to rebase - working '
836 'directory parent is already an '
838 'directory parent is already an '
837 'ancestor of destination %s\n') % dest)
839 'ancestor of destination %s\n') % dest)
838 else: # can it happen?
840 else: # can it happen?
839 ui.status(_('nothing to rebase from %s to %s\n') %
841 ui.status(_('nothing to rebase from %s to %s\n') %
840 ('+'.join(str(repo[r]) for r in base), dest))
842 ('+'.join(str(repo[r]) for r in base), dest))
841 return None, None
843 return None, None
842
844
843 if not destf:
845 if not destf:
844 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
846 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
845 destf = str(dest)
847 destf = str(dest)
846
848
847 return dest, rebaseset
849 return dest, rebaseset
848
850
849 def externalparent(repo, state, destancestors):
851 def externalparent(repo, state, destancestors):
850 """Return the revision that should be used as the second parent
852 """Return the revision that should be used as the second parent
851 when the revisions in state is collapsed on top of destancestors.
853 when the revisions in state is collapsed on top of destancestors.
852 Abort if there is more than one parent.
854 Abort if there is more than one parent.
853 """
855 """
854 parents = set()
856 parents = set()
855 source = min(state)
857 source = min(state)
856 for rev in state:
858 for rev in state:
857 if rev == source:
859 if rev == source:
858 continue
860 continue
859 for p in repo[rev].parents():
861 for p in repo[rev].parents():
860 if (p.rev() not in state
862 if (p.rev() not in state
861 and p.rev() not in destancestors):
863 and p.rev() not in destancestors):
862 parents.add(p.rev())
864 parents.add(p.rev())
863 if not parents:
865 if not parents:
864 return nullrev
866 return nullrev
865 if len(parents) == 1:
867 if len(parents) == 1:
866 return parents.pop()
868 return parents.pop()
867 raise error.Abort(_('unable to collapse on top of %s, there is more '
869 raise error.Abort(_('unable to collapse on top of %s, there is more '
868 'than one external parent: %s') %
870 'than one external parent: %s') %
869 (max(destancestors),
871 (max(destancestors),
870 ', '.join(str(p) for p in sorted(parents))))
872 ', '.join(str(p) for p in sorted(parents))))
871
873
872 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
874 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
873 keepbranches=False, date=None):
875 keepbranches=False, date=None):
874 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
876 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
875 but also store useful information in extra.
877 but also store useful information in extra.
876 Return node of committed revision.'''
878 Return node of committed revision.'''
877 repo.setparents(repo[p1].node(), repo[p2].node())
879 repo.setparents(repo[p1].node(), repo[p2].node())
878 ctx = repo[rev]
880 ctx = repo[rev]
879 if commitmsg is None:
881 if commitmsg is None:
880 commitmsg = ctx.description()
882 commitmsg = ctx.description()
881 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
883 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
882 extra = {'rebase_source': ctx.hex()}
884 extra = {'rebase_source': ctx.hex()}
883 if extrafn:
885 if extrafn:
884 extrafn(ctx, extra)
886 extrafn(ctx, extra)
885
887
886 destphase = max(ctx.phase(), phases.draft)
888 destphase = max(ctx.phase(), phases.draft)
887 overrides = {('phases', 'new-commit'): destphase}
889 overrides = {('phases', 'new-commit'): destphase}
888 with repo.ui.configoverride(overrides, 'rebase'):
890 with repo.ui.configoverride(overrides, 'rebase'):
889 if keepbranch:
891 if keepbranch:
890 repo.ui.setconfig('ui', 'allowemptycommit', True)
892 repo.ui.setconfig('ui', 'allowemptycommit', True)
891 # Commit might fail if unresolved files exist
893 # Commit might fail if unresolved files exist
892 if date is None:
894 if date is None:
893 date = ctx.date()
895 date = ctx.date()
894 newnode = repo.commit(text=commitmsg, user=ctx.user(),
896 newnode = repo.commit(text=commitmsg, user=ctx.user(),
895 date=date, extra=extra, editor=editor)
897 date=date, extra=extra, editor=editor)
896
898
897 repo.dirstate.setbranch(repo[newnode].branch())
899 repo.dirstate.setbranch(repo[newnode].branch())
898 return newnode
900 return newnode
899
901
900 def rebasenode(repo, rev, p1, base, state, collapse, dest):
902 def rebasenode(repo, rev, p1, base, state, collapse, dest):
901 'Rebase a single revision rev on top of p1 using base as merge ancestor'
903 'Rebase a single revision rev on top of p1 using base as merge ancestor'
902 # Merge phase
904 # Merge phase
903 # Update to destination and merge it with local
905 # Update to destination and merge it with local
904 if repo['.'].rev() != p1:
906 if repo['.'].rev() != p1:
905 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
907 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
906 mergemod.update(repo, p1, False, True)
908 mergemod.update(repo, p1, False, True)
907 else:
909 else:
908 repo.ui.debug(" already in destination\n")
910 repo.ui.debug(" already in destination\n")
909 repo.dirstate.write(repo.currenttransaction())
911 repo.dirstate.write(repo.currenttransaction())
910 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
912 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
911 if base is not None:
913 if base is not None:
912 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
914 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
913 # When collapsing in-place, the parent is the common ancestor, we
915 # When collapsing in-place, the parent is the common ancestor, we
914 # have to allow merging with it.
916 # have to allow merging with it.
915 stats = mergemod.update(repo, rev, True, True, base, collapse,
917 stats = mergemod.update(repo, rev, True, True, base, collapse,
916 labels=['dest', 'source'])
918 labels=['dest', 'source'])
917 if collapse:
919 if collapse:
918 copies.duplicatecopies(repo, rev, dest)
920 copies.duplicatecopies(repo, rev, dest)
919 else:
921 else:
920 # If we're not using --collapse, we need to
922 # If we're not using --collapse, we need to
921 # duplicate copies between the revision we're
923 # duplicate copies between the revision we're
922 # rebasing and its first parent, but *not*
924 # rebasing and its first parent, but *not*
923 # duplicate any copies that have already been
925 # duplicate any copies that have already been
924 # performed in the destination.
926 # performed in the destination.
925 p1rev = repo[rev].p1().rev()
927 p1rev = repo[rev].p1().rev()
926 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
928 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
927 return stats
929 return stats
928
930
929 def nearestrebased(repo, rev, state):
931 def nearestrebased(repo, rev, state):
930 """return the nearest ancestors of rev in the rebase result"""
932 """return the nearest ancestors of rev in the rebase result"""
931 rebased = [r for r in state if state[r] > nullmerge]
933 rebased = [r for r in state if state[r] > nullmerge]
932 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
934 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
933 if candidates:
935 if candidates:
934 return state[candidates.first()]
936 return state[candidates.first()]
935 else:
937 else:
936 return None
938 return None
937
939
938 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
940 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
939 """
941 """
940 Abort if rebase will create divergence or rebase is noop because of markers
942 Abort if rebase will create divergence or rebase is noop because of markers
941
943
942 `rebaseobsrevs`: set of obsolete revision in source
944 `rebaseobsrevs`: set of obsolete revision in source
943 `rebasesetrevs`: set of revisions to be rebased from source
945 `rebasesetrevs`: set of revisions to be rebased from source
944 `rebaseobsskipped`: set of revisions from source skipped because they have
946 `rebaseobsskipped`: set of revisions from source skipped because they have
945 successors in destination
947 successors in destination
946 """
948 """
947 # Obsolete node with successors not in dest leads to divergence
949 # Obsolete node with successors not in dest leads to divergence
948 divergenceok = ui.configbool('experimental',
950 divergenceok = ui.configbool('experimental',
949 'allowdivergence')
951 'allowdivergence')
950 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
952 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
951
953
952 if divergencebasecandidates and not divergenceok:
954 if divergencebasecandidates and not divergenceok:
953 divhashes = (str(repo[r])
955 divhashes = (str(repo[r])
954 for r in divergencebasecandidates)
956 for r in divergencebasecandidates)
955 msg = _("this rebase will cause "
957 msg = _("this rebase will cause "
956 "divergences from: %s")
958 "divergences from: %s")
957 h = _("to force the rebase please set "
959 h = _("to force the rebase please set "
958 "experimental.allowdivergence=True")
960 "experimental.allowdivergence=True")
959 raise error.Abort(msg % (",".join(divhashes),), hint=h)
961 raise error.Abort(msg % (",".join(divhashes),), hint=h)
960
962
961 def defineparents(repo, rev, dest, state, destancestors,
963 def defineparents(repo, rev, dest, state, destancestors,
962 obsoletenotrebased):
964 obsoletenotrebased):
963 'Return the new parent relationship of the revision that will be rebased'
965 'Return the new parent relationship of the revision that will be rebased'
964 parents = repo[rev].parents()
966 parents = repo[rev].parents()
965 p1 = p2 = nullrev
967 p1 = p2 = nullrev
966 rp1 = None
968 rp1 = None
967
969
968 p1n = parents[0].rev()
970 p1n = parents[0].rev()
969 if p1n in destancestors:
971 if p1n in destancestors:
970 p1 = dest
972 p1 = dest
971 elif p1n in state:
973 elif p1n in state:
972 if state[p1n] == nullmerge:
974 if state[p1n] == nullmerge:
973 p1 = dest
975 p1 = dest
974 elif state[p1n] in revskipped:
976 elif state[p1n] in revskipped:
975 p1 = nearestrebased(repo, p1n, state)
977 p1 = nearestrebased(repo, p1n, state)
976 if p1 is None:
978 if p1 is None:
977 p1 = dest
979 p1 = dest
978 else:
980 else:
979 p1 = state[p1n]
981 p1 = state[p1n]
980 else: # p1n external
982 else: # p1n external
981 p1 = dest
983 p1 = dest
982 p2 = p1n
984 p2 = p1n
983
985
984 if len(parents) == 2 and parents[1].rev() not in destancestors:
986 if len(parents) == 2 and parents[1].rev() not in destancestors:
985 p2n = parents[1].rev()
987 p2n = parents[1].rev()
986 # interesting second parent
988 # interesting second parent
987 if p2n in state:
989 if p2n in state:
988 if p1 == dest: # p1n in destancestors or external
990 if p1 == dest: # p1n in destancestors or external
989 p1 = state[p2n]
991 p1 = state[p2n]
990 if p1 == revprecursor:
992 if p1 == revprecursor:
991 rp1 = obsoletenotrebased[p2n]
993 rp1 = obsoletenotrebased[p2n]
992 elif state[p2n] in revskipped:
994 elif state[p2n] in revskipped:
993 p2 = nearestrebased(repo, p2n, state)
995 p2 = nearestrebased(repo, p2n, state)
994 if p2 is None:
996 if p2 is None:
995 # no ancestors rebased yet, detach
997 # no ancestors rebased yet, detach
996 p2 = dest
998 p2 = dest
997 else:
999 else:
998 p2 = state[p2n]
1000 p2 = state[p2n]
999 else: # p2n external
1001 else: # p2n external
1000 if p2 != nullrev: # p1n external too => rev is a merged revision
1002 if p2 != nullrev: # p1n external too => rev is a merged revision
1001 raise error.Abort(_('cannot use revision %d as base, result '
1003 raise error.Abort(_('cannot use revision %d as base, result '
1002 'would have 3 parents') % rev)
1004 'would have 3 parents') % rev)
1003 p2 = p2n
1005 p2 = p2n
1004 repo.ui.debug(" future parents are %d and %d\n" %
1006 repo.ui.debug(" future parents are %d and %d\n" %
1005 (repo[rp1 or p1].rev(), repo[p2].rev()))
1007 (repo[rp1 or p1].rev(), repo[p2].rev()))
1006
1008
1007 if not any(p.rev() in state for p in parents):
1009 if not any(p.rev() in state for p in parents):
1008 # Case (1) root changeset of a non-detaching rebase set.
1010 # Case (1) root changeset of a non-detaching rebase set.
1009 # Let the merge mechanism find the base itself.
1011 # Let the merge mechanism find the base itself.
1010 base = None
1012 base = None
1011 elif not repo[rev].p2():
1013 elif not repo[rev].p2():
1012 # Case (2) detaching the node with a single parent, use this parent
1014 # Case (2) detaching the node with a single parent, use this parent
1013 base = repo[rev].p1().rev()
1015 base = repo[rev].p1().rev()
1014 else:
1016 else:
1015 # Assuming there is a p1, this is the case where there also is a p2.
1017 # Assuming there is a p1, this is the case where there also is a p2.
1016 # We are thus rebasing a merge and need to pick the right merge base.
1018 # We are thus rebasing a merge and need to pick the right merge base.
1017 #
1019 #
1018 # Imagine we have:
1020 # Imagine we have:
1019 # - M: current rebase revision in this step
1021 # - M: current rebase revision in this step
1020 # - A: one parent of M
1022 # - A: one parent of M
1021 # - B: other parent of M
1023 # - B: other parent of M
1022 # - D: destination of this merge step (p1 var)
1024 # - D: destination of this merge step (p1 var)
1023 #
1025 #
1024 # Consider the case where D is a descendant of A or B and the other is
1026 # Consider the case where D is a descendant of A or B and the other is
1025 # 'outside'. In this case, the right merge base is the D ancestor.
1027 # 'outside'. In this case, the right merge base is the D ancestor.
1026 #
1028 #
1027 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1029 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1028 #
1030 #
1029 # If we pick B as the base, the merge involves:
1031 # If we pick B as the base, the merge involves:
1030 # - changes from B to M (actual changeset payload)
1032 # - changes from B to M (actual changeset payload)
1031 # - changes from B to D (induced by rebase) as D is a rebased
1033 # - changes from B to D (induced by rebase) as D is a rebased
1032 # version of B)
1034 # version of B)
1033 # Which exactly represent the rebase operation.
1035 # Which exactly represent the rebase operation.
1034 #
1036 #
1035 # If we pick A as the base, the merge involves:
1037 # If we pick A as the base, the merge involves:
1036 # - changes from A to M (actual changeset payload)
1038 # - changes from A to M (actual changeset payload)
1037 # - changes from A to D (with include changes between unrelated A and B
1039 # - changes from A to D (with include changes between unrelated A and B
1038 # plus changes induced by rebase)
1040 # plus changes induced by rebase)
1039 # Which does not represent anything sensible and creates a lot of
1041 # Which does not represent anything sensible and creates a lot of
1040 # conflicts. A is thus not the right choice - B is.
1042 # conflicts. A is thus not the right choice - B is.
1041 #
1043 #
1042 # Note: The base found in this 'proof' is only correct in the specified
1044 # Note: The base found in this 'proof' is only correct in the specified
1043 # case. This base does not make sense if is not D a descendant of A or B
1045 # case. This base does not make sense if is not D a descendant of A or B
1044 # or if the other is not parent 'outside' (especially not if the other
1046 # or if the other is not parent 'outside' (especially not if the other
1045 # parent has been rebased). The current implementation does not
1047 # parent has been rebased). The current implementation does not
1046 # make it feasible to consider different cases separately. In these
1048 # make it feasible to consider different cases separately. In these
1047 # other cases we currently just leave it to the user to correctly
1049 # other cases we currently just leave it to the user to correctly
1048 # resolve an impossible merge using a wrong ancestor.
1050 # resolve an impossible merge using a wrong ancestor.
1049 #
1051 #
1050 # xx, p1 could be -4, and both parents could probably be -4...
1052 # xx, p1 could be -4, and both parents could probably be -4...
1051 for p in repo[rev].parents():
1053 for p in repo[rev].parents():
1052 if state.get(p.rev()) == p1:
1054 if state.get(p.rev()) == p1:
1053 base = p.rev()
1055 base = p.rev()
1054 break
1056 break
1055 else: # fallback when base not found
1057 else: # fallback when base not found
1056 base = None
1058 base = None
1057
1059
1058 # Raise because this function is called wrong (see issue 4106)
1060 # Raise because this function is called wrong (see issue 4106)
1059 raise AssertionError('no base found to rebase on '
1061 raise AssertionError('no base found to rebase on '
1060 '(defineparents called wrong)')
1062 '(defineparents called wrong)')
1061 return rp1 or p1, p2, base
1063 return rp1 or p1, p2, base
1062
1064
1063 def isagitpatch(repo, patchname):
1065 def isagitpatch(repo, patchname):
1064 'Return true if the given patch is in git format'
1066 'Return true if the given patch is in git format'
1065 mqpatch = os.path.join(repo.mq.path, patchname)
1067 mqpatch = os.path.join(repo.mq.path, patchname)
1066 for line in patch.linereader(file(mqpatch, 'rb')):
1068 for line in patch.linereader(file(mqpatch, 'rb')):
1067 if line.startswith('diff --git'):
1069 if line.startswith('diff --git'):
1068 return True
1070 return True
1069 return False
1071 return False
1070
1072
1071 def updatemq(repo, state, skipped, **opts):
1073 def updatemq(repo, state, skipped, **opts):
1072 'Update rebased mq patches - finalize and then import them'
1074 'Update rebased mq patches - finalize and then import them'
1073 mqrebase = {}
1075 mqrebase = {}
1074 mq = repo.mq
1076 mq = repo.mq
1075 original_series = mq.fullseries[:]
1077 original_series = mq.fullseries[:]
1076 skippedpatches = set()
1078 skippedpatches = set()
1077
1079
1078 for p in mq.applied:
1080 for p in mq.applied:
1079 rev = repo[p.node].rev()
1081 rev = repo[p.node].rev()
1080 if rev in state:
1082 if rev in state:
1081 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1083 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1082 (rev, p.name))
1084 (rev, p.name))
1083 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1085 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1084 else:
1086 else:
1085 # Applied but not rebased, not sure this should happen
1087 # Applied but not rebased, not sure this should happen
1086 skippedpatches.add(p.name)
1088 skippedpatches.add(p.name)
1087
1089
1088 if mqrebase:
1090 if mqrebase:
1089 mq.finish(repo, mqrebase.keys())
1091 mq.finish(repo, mqrebase.keys())
1090
1092
1091 # We must start import from the newest revision
1093 # We must start import from the newest revision
1092 for rev in sorted(mqrebase, reverse=True):
1094 for rev in sorted(mqrebase, reverse=True):
1093 if rev not in skipped:
1095 if rev not in skipped:
1094 name, isgit = mqrebase[rev]
1096 name, isgit = mqrebase[rev]
1095 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1097 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1096 (name, state[rev], repo[state[rev]]))
1098 (name, state[rev], repo[state[rev]]))
1097 mq.qimport(repo, (), patchname=name, git=isgit,
1099 mq.qimport(repo, (), patchname=name, git=isgit,
1098 rev=[str(state[rev])])
1100 rev=[str(state[rev])])
1099 else:
1101 else:
1100 # Rebased and skipped
1102 # Rebased and skipped
1101 skippedpatches.add(mqrebase[rev][0])
1103 skippedpatches.add(mqrebase[rev][0])
1102
1104
1103 # Patches were either applied and rebased and imported in
1105 # Patches were either applied and rebased and imported in
1104 # order, applied and removed or unapplied. Discard the removed
1106 # order, applied and removed or unapplied. Discard the removed
1105 # ones while preserving the original series order and guards.
1107 # ones while preserving the original series order and guards.
1106 newseries = [s for s in original_series
1108 newseries = [s for s in original_series
1107 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1109 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1108 mq.fullseries[:] = newseries
1110 mq.fullseries[:] = newseries
1109 mq.seriesdirty = True
1111 mq.seriesdirty = True
1110 mq.savedirty()
1112 mq.savedirty()
1111
1113
1112 def updatebookmarks(repo, destnode, nstate, originalbookmarks, tr):
1114 def updatebookmarks(repo, destnode, nstate, originalbookmarks, tr):
1113 'Move bookmarks to their correct changesets, and delete divergent ones'
1115 'Move bookmarks to their correct changesets, and delete divergent ones'
1114 marks = repo._bookmarks
1116 marks = repo._bookmarks
1115 for k, v in originalbookmarks.iteritems():
1117 for k, v in originalbookmarks.iteritems():
1116 if v in nstate:
1118 if v in nstate:
1117 # update the bookmarks for revs that have moved
1119 # update the bookmarks for revs that have moved
1118 marks[k] = nstate[v]
1120 marks[k] = nstate[v]
1119 bookmarks.deletedivergent(repo, [destnode], k)
1121 bookmarks.deletedivergent(repo, [destnode], k)
1120 marks.recordchange(tr)
1122 marks.recordchange(tr)
1121
1123
1122 def storecollapsemsg(repo, collapsemsg):
1124 def storecollapsemsg(repo, collapsemsg):
1123 'Store the collapse message to allow recovery'
1125 'Store the collapse message to allow recovery'
1124 collapsemsg = collapsemsg or ''
1126 collapsemsg = collapsemsg or ''
1125 f = repo.vfs("last-message.txt", "w")
1127 f = repo.vfs("last-message.txt", "w")
1126 f.write("%s\n" % collapsemsg)
1128 f.write("%s\n" % collapsemsg)
1127 f.close()
1129 f.close()
1128
1130
1129 def clearcollapsemsg(repo):
1131 def clearcollapsemsg(repo):
1130 'Remove collapse message file'
1132 'Remove collapse message file'
1131 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1133 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1132
1134
1133 def restorecollapsemsg(repo, isabort):
1135 def restorecollapsemsg(repo, isabort):
1134 'Restore previously stored collapse message'
1136 'Restore previously stored collapse message'
1135 try:
1137 try:
1136 f = repo.vfs("last-message.txt")
1138 f = repo.vfs("last-message.txt")
1137 collapsemsg = f.readline().strip()
1139 collapsemsg = f.readline().strip()
1138 f.close()
1140 f.close()
1139 except IOError as err:
1141 except IOError as err:
1140 if err.errno != errno.ENOENT:
1142 if err.errno != errno.ENOENT:
1141 raise
1143 raise
1142 if isabort:
1144 if isabort:
1143 # Oh well, just abort like normal
1145 # Oh well, just abort like normal
1144 collapsemsg = ''
1146 collapsemsg = ''
1145 else:
1147 else:
1146 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1148 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1147 return collapsemsg
1149 return collapsemsg
1148
1150
1149 def clearstatus(repo):
1151 def clearstatus(repo):
1150 'Remove the status files'
1152 'Remove the status files'
1151 _clearrebasesetvisibiliy(repo)
1153 _clearrebasesetvisibiliy(repo)
1152 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1154 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1153
1155
1154 def needupdate(repo, state):
1156 def needupdate(repo, state):
1155 '''check whether we should `update --clean` away from a merge, or if
1157 '''check whether we should `update --clean` away from a merge, or if
1156 somehow the working dir got forcibly updated, e.g. by older hg'''
1158 somehow the working dir got forcibly updated, e.g. by older hg'''
1157 parents = [p.rev() for p in repo[None].parents()]
1159 parents = [p.rev() for p in repo[None].parents()]
1158
1160
1159 # Are we in a merge state at all?
1161 # Are we in a merge state at all?
1160 if len(parents) < 2:
1162 if len(parents) < 2:
1161 return False
1163 return False
1162
1164
1163 # We should be standing on the first as-of-yet unrebased commit.
1165 # We should be standing on the first as-of-yet unrebased commit.
1164 firstunrebased = min([old for old, new in state.iteritems()
1166 firstunrebased = min([old for old, new in state.iteritems()
1165 if new == nullrev])
1167 if new == nullrev])
1166 if firstunrebased in parents:
1168 if firstunrebased in parents:
1167 return True
1169 return True
1168
1170
1169 return False
1171 return False
1170
1172
1171 def abort(repo, originalwd, dest, state, activebookmark=None):
1173 def abort(repo, originalwd, dest, state, activebookmark=None):
1172 '''Restore the repository to its original state. Additional args:
1174 '''Restore the repository to its original state. Additional args:
1173
1175
1174 activebookmark: the name of the bookmark that should be active after the
1176 activebookmark: the name of the bookmark that should be active after the
1175 restore'''
1177 restore'''
1176
1178
1177 try:
1179 try:
1178 # If the first commits in the rebased set get skipped during the rebase,
1180 # If the first commits in the rebased set get skipped during the rebase,
1179 # their values within the state mapping will be the dest rev id. The
1181 # their values within the state mapping will be the dest rev id. The
1180 # dstates list must must not contain the dest rev (issue4896)
1182 # dstates list must must not contain the dest rev (issue4896)
1181 dstates = [s for s in state.values() if s >= 0 and s != dest]
1183 dstates = [s for s in state.values() if s >= 0 and s != dest]
1182 immutable = [d for d in dstates if not repo[d].mutable()]
1184 immutable = [d for d in dstates if not repo[d].mutable()]
1183 cleanup = True
1185 cleanup = True
1184 if immutable:
1186 if immutable:
1185 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1187 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1186 % ', '.join(str(repo[r]) for r in immutable),
1188 % ', '.join(str(repo[r]) for r in immutable),
1187 hint=_("see 'hg help phases' for details"))
1189 hint=_("see 'hg help phases' for details"))
1188 cleanup = False
1190 cleanup = False
1189
1191
1190 descendants = set()
1192 descendants = set()
1191 if dstates:
1193 if dstates:
1192 descendants = set(repo.changelog.descendants(dstates))
1194 descendants = set(repo.changelog.descendants(dstates))
1193 if descendants - set(dstates):
1195 if descendants - set(dstates):
1194 repo.ui.warn(_("warning: new changesets detected on destination "
1196 repo.ui.warn(_("warning: new changesets detected on destination "
1195 "branch, can't strip\n"))
1197 "branch, can't strip\n"))
1196 cleanup = False
1198 cleanup = False
1197
1199
1198 if cleanup:
1200 if cleanup:
1199 shouldupdate = False
1201 shouldupdate = False
1200 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1202 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1201 if rebased:
1203 if rebased:
1202 strippoints = [
1204 strippoints = [
1203 c.node() for c in repo.set('roots(%ld)', rebased)]
1205 c.node() for c in repo.set('roots(%ld)', rebased)]
1204
1206
1205 updateifonnodes = set(rebased)
1207 updateifonnodes = set(rebased)
1206 updateifonnodes.add(dest)
1208 updateifonnodes.add(dest)
1207 updateifonnodes.add(originalwd)
1209 updateifonnodes.add(originalwd)
1208 shouldupdate = repo['.'].rev() in updateifonnodes
1210 shouldupdate = repo['.'].rev() in updateifonnodes
1209
1211
1210 # Update away from the rebase if necessary
1212 # Update away from the rebase if necessary
1211 if shouldupdate or needupdate(repo, state):
1213 if shouldupdate or needupdate(repo, state):
1212 mergemod.update(repo, originalwd, False, True)
1214 mergemod.update(repo, originalwd, False, True)
1213
1215
1214 # Strip from the first rebased revision
1216 # Strip from the first rebased revision
1215 if rebased:
1217 if rebased:
1216 # no backup of rebased cset versions needed
1218 # no backup of rebased cset versions needed
1217 repair.strip(repo.ui, repo, strippoints)
1219 repair.strip(repo.ui, repo, strippoints)
1218
1220
1219 if activebookmark and activebookmark in repo._bookmarks:
1221 if activebookmark and activebookmark in repo._bookmarks:
1220 bookmarks.activate(repo, activebookmark)
1222 bookmarks.activate(repo, activebookmark)
1221
1223
1222 finally:
1224 finally:
1223 clearstatus(repo)
1225 clearstatus(repo)
1224 clearcollapsemsg(repo)
1226 clearcollapsemsg(repo)
1225 repo.ui.warn(_('rebase aborted\n'))
1227 repo.ui.warn(_('rebase aborted\n'))
1226 return 0
1228 return 0
1227
1229
1228 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1230 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1229 '''Define which revisions are going to be rebased and where
1231 '''Define which revisions are going to be rebased and where
1230
1232
1231 repo: repo
1233 repo: repo
1232 dest: context
1234 dest: context
1233 rebaseset: set of rev
1235 rebaseset: set of rev
1234 '''
1236 '''
1235 originalwd = repo['.'].rev()
1237 originalwd = repo['.'].rev()
1236 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1238 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1237
1239
1238 # This check isn't strictly necessary, since mq detects commits over an
1240 # This check isn't strictly necessary, since mq detects commits over an
1239 # applied patch. But it prevents messing up the working directory when
1241 # applied patch. But it prevents messing up the working directory when
1240 # a partially completed rebase is blocked by mq.
1242 # a partially completed rebase is blocked by mq.
1241 if 'qtip' in repo.tags() and (dest.node() in
1243 if 'qtip' in repo.tags() and (dest.node() in
1242 [s.node for s in repo.mq.applied]):
1244 [s.node for s in repo.mq.applied]):
1243 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1245 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1244
1246
1245 roots = list(repo.set('roots(%ld)', rebaseset))
1247 roots = list(repo.set('roots(%ld)', rebaseset))
1246 if not roots:
1248 if not roots:
1247 raise error.Abort(_('no matching revisions'))
1249 raise error.Abort(_('no matching revisions'))
1248 roots.sort()
1250 roots.sort()
1249 state = dict.fromkeys(rebaseset, revtodo)
1251 state = dict.fromkeys(rebaseset, revtodo)
1250 detachset = set()
1252 detachset = set()
1253 emptyrebase = True
1251 for root in roots:
1254 for root in roots:
1252 commonbase = root.ancestor(dest)
1255 commonbase = root.ancestor(dest)
1253 if commonbase == root:
1256 if commonbase == root:
1254 raise error.Abort(_('source is ancestor of destination'))
1257 raise error.Abort(_('source is ancestor of destination'))
1255 if commonbase == dest:
1258 if commonbase == dest:
1256 wctx = repo[None]
1259 wctx = repo[None]
1257 if dest == wctx.p1():
1260 if dest == wctx.p1():
1258 # when rebasing to '.', it will use the current wd branch name
1261 # when rebasing to '.', it will use the current wd branch name
1259 samebranch = root.branch() == wctx.branch()
1262 samebranch = root.branch() == wctx.branch()
1260 else:
1263 else:
1261 samebranch = root.branch() == dest.branch()
1264 samebranch = root.branch() == dest.branch()
1262 if not collapse and samebranch and root in dest.children():
1265 if not collapse and samebranch and root in dest.children():
1266 # mark the revision as done by setting its new revision
1267 # equal to its old (current) revisions
1268 state[root.rev()] = root.rev()
1263 repo.ui.debug('source is a child of destination\n')
1269 repo.ui.debug('source is a child of destination\n')
1264 return None
1270 continue
1265
1271
1272 emptyrebase = False
1266 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1273 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1267 # Rebase tries to turn <dest> into a parent of <root> while
1274 # Rebase tries to turn <dest> into a parent of <root> while
1268 # preserving the number of parents of rebased changesets:
1275 # preserving the number of parents of rebased changesets:
1269 #
1276 #
1270 # - A changeset with a single parent will always be rebased as a
1277 # - A changeset with a single parent will always be rebased as a
1271 # changeset with a single parent.
1278 # changeset with a single parent.
1272 #
1279 #
1273 # - A merge will be rebased as merge unless its parents are both
1280 # - A merge will be rebased as merge unless its parents are both
1274 # ancestors of <dest> or are themselves in the rebased set and
1281 # ancestors of <dest> or are themselves in the rebased set and
1275 # pruned while rebased.
1282 # pruned while rebased.
1276 #
1283 #
1277 # If one parent of <root> is an ancestor of <dest>, the rebased
1284 # If one parent of <root> is an ancestor of <dest>, the rebased
1278 # version of this parent will be <dest>. This is always true with
1285 # version of this parent will be <dest>. This is always true with
1279 # --base option.
1286 # --base option.
1280 #
1287 #
1281 # Otherwise, we need to *replace* the original parents with
1288 # Otherwise, we need to *replace* the original parents with
1282 # <dest>. This "detaches" the rebased set from its former location
1289 # <dest>. This "detaches" the rebased set from its former location
1283 # and rebases it onto <dest>. Changes introduced by ancestors of
1290 # and rebases it onto <dest>. Changes introduced by ancestors of
1284 # <root> not common with <dest> (the detachset, marked as
1291 # <root> not common with <dest> (the detachset, marked as
1285 # nullmerge) are "removed" from the rebased changesets.
1292 # nullmerge) are "removed" from the rebased changesets.
1286 #
1293 #
1287 # - If <root> has a single parent, set it to <dest>.
1294 # - If <root> has a single parent, set it to <dest>.
1288 #
1295 #
1289 # - If <root> is a merge, we cannot decide which parent to
1296 # - If <root> is a merge, we cannot decide which parent to
1290 # replace, the rebase operation is not clearly defined.
1297 # replace, the rebase operation is not clearly defined.
1291 #
1298 #
1292 # The table below sums up this behavior:
1299 # The table below sums up this behavior:
1293 #
1300 #
1294 # +------------------+----------------------+-------------------------+
1301 # +------------------+----------------------+-------------------------+
1295 # | | one parent | merge |
1302 # | | one parent | merge |
1296 # +------------------+----------------------+-------------------------+
1303 # +------------------+----------------------+-------------------------+
1297 # | parent in | new parent is <dest> | parents in ::<dest> are |
1304 # | parent in | new parent is <dest> | parents in ::<dest> are |
1298 # | ::<dest> | | remapped to <dest> |
1305 # | ::<dest> | | remapped to <dest> |
1299 # +------------------+----------------------+-------------------------+
1306 # +------------------+----------------------+-------------------------+
1300 # | unrelated source | new parent is <dest> | ambiguous, abort |
1307 # | unrelated source | new parent is <dest> | ambiguous, abort |
1301 # +------------------+----------------------+-------------------------+
1308 # +------------------+----------------------+-------------------------+
1302 #
1309 #
1303 # The actual abort is handled by `defineparents`
1310 # The actual abort is handled by `defineparents`
1304 if len(root.parents()) <= 1:
1311 if len(root.parents()) <= 1:
1305 # ancestors of <root> not ancestors of <dest>
1312 # ancestors of <root> not ancestors of <dest>
1306 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1313 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1307 [root.rev()]))
1314 [root.rev()]))
1315 if emptyrebase:
1316 return None
1317 for rev in sorted(state):
1318 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1319 # if all parents of this revision are done, then so is this revision
1320 if parents and all((state.get(p) == p for p in parents)):
1321 state[rev] = rev
1308 for r in detachset:
1322 for r in detachset:
1309 if r not in state:
1323 if r not in state:
1310 state[r] = nullmerge
1324 state[r] = nullmerge
1311 if len(roots) > 1:
1325 if len(roots) > 1:
1312 # If we have multiple roots, we may have "hole" in the rebase set.
1326 # If we have multiple roots, we may have "hole" in the rebase set.
1313 # Rebase roots that descend from those "hole" should not be detached as
1327 # Rebase roots that descend from those "hole" should not be detached as
1314 # other root are. We use the special `revignored` to inform rebase that
1328 # other root are. We use the special `revignored` to inform rebase that
1315 # the revision should be ignored but that `defineparents` should search
1329 # the revision should be ignored but that `defineparents` should search
1316 # a rebase destination that make sense regarding rebased topology.
1330 # a rebase destination that make sense regarding rebased topology.
1317 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1331 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1318 for ignored in set(rebasedomain) - set(rebaseset):
1332 for ignored in set(rebasedomain) - set(rebaseset):
1319 state[ignored] = revignored
1333 state[ignored] = revignored
1320 for r in obsoletenotrebased:
1334 for r in obsoletenotrebased:
1321 if obsoletenotrebased[r] is None:
1335 if obsoletenotrebased[r] is None:
1322 state[r] = revpruned
1336 state[r] = revpruned
1323 else:
1337 else:
1324 state[r] = revprecursor
1338 state[r] = revprecursor
1325 return originalwd, dest.rev(), state
1339 return originalwd, dest.rev(), state
1326
1340
1327 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1341 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1328 """dispose of rebased revision at the end of the rebase
1342 """dispose of rebased revision at the end of the rebase
1329
1343
1330 If `collapsedas` is not None, the rebase was a collapse whose result if the
1344 If `collapsedas` is not None, the rebase was a collapse whose result if the
1331 `collapsedas` node."""
1345 `collapsedas` node."""
1332 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1346 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1333 markers = []
1347 markers = []
1334 for rev, newrev in sorted(state.items()):
1348 for rev, newrev in sorted(state.items()):
1335 if newrev >= 0:
1349 if newrev >= 0 and newrev != rev:
1336 if rev in skipped:
1350 if rev in skipped:
1337 succs = ()
1351 succs = ()
1338 elif collapsedas is not None:
1352 elif collapsedas is not None:
1339 succs = (repo[collapsedas],)
1353 succs = (repo[collapsedas],)
1340 else:
1354 else:
1341 succs = (repo[newrev],)
1355 succs = (repo[newrev],)
1342 markers.append((repo[rev], succs))
1356 markers.append((repo[rev], succs))
1343 if markers:
1357 if markers:
1344 obsolete.createmarkers(repo, markers)
1358 obsolete.createmarkers(repo, markers)
1345 else:
1359 else:
1346 rebased = [rev for rev in state if state[rev] > nullmerge]
1360 rebased = [rev for rev in state
1361 if state[rev] > nullmerge and state[rev] != rev]
1347 if rebased:
1362 if rebased:
1348 stripped = []
1363 stripped = []
1349 for root in repo.set('roots(%ld)', rebased):
1364 for root in repo.set('roots(%ld)', rebased):
1350 if set(repo.changelog.descendants([root.rev()])) - set(state):
1365 if set(repo.changelog.descendants([root.rev()])) - set(state):
1351 ui.warn(_("warning: new changesets detected "
1366 ui.warn(_("warning: new changesets detected "
1352 "on source branch, not stripping\n"))
1367 "on source branch, not stripping\n"))
1353 else:
1368 else:
1354 stripped.append(root.node())
1369 stripped.append(root.node())
1355 if stripped:
1370 if stripped:
1356 # backup the old csets by default
1371 # backup the old csets by default
1357 repair.strip(ui, repo, stripped, "all")
1372 repair.strip(ui, repo, stripped, "all")
1358
1373
1359
1374
1360 def pullrebase(orig, ui, repo, *args, **opts):
1375 def pullrebase(orig, ui, repo, *args, **opts):
1361 'Call rebase after pull if the latter has been invoked with --rebase'
1376 'Call rebase after pull if the latter has been invoked with --rebase'
1362 ret = None
1377 ret = None
1363 if opts.get('rebase'):
1378 if opts.get('rebase'):
1364 if ui.configbool('commands', 'rebase.requiredest'):
1379 if ui.configbool('commands', 'rebase.requiredest'):
1365 msg = _('rebase destination required by configuration')
1380 msg = _('rebase destination required by configuration')
1366 hint = _('use hg pull followed by hg rebase -d DEST')
1381 hint = _('use hg pull followed by hg rebase -d DEST')
1367 raise error.Abort(msg, hint=hint)
1382 raise error.Abort(msg, hint=hint)
1368
1383
1369 wlock = lock = None
1384 wlock = lock = None
1370 try:
1385 try:
1371 wlock = repo.wlock()
1386 wlock = repo.wlock()
1372 lock = repo.lock()
1387 lock = repo.lock()
1373 if opts.get('update'):
1388 if opts.get('update'):
1374 del opts['update']
1389 del opts['update']
1375 ui.debug('--update and --rebase are not compatible, ignoring '
1390 ui.debug('--update and --rebase are not compatible, ignoring '
1376 'the update flag\n')
1391 'the update flag\n')
1377
1392
1378 cmdutil.checkunfinished(repo)
1393 cmdutil.checkunfinished(repo)
1379 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1394 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1380 'please commit or shelve your changes first'))
1395 'please commit or shelve your changes first'))
1381
1396
1382 revsprepull = len(repo)
1397 revsprepull = len(repo)
1383 origpostincoming = commands.postincoming
1398 origpostincoming = commands.postincoming
1384 def _dummy(*args, **kwargs):
1399 def _dummy(*args, **kwargs):
1385 pass
1400 pass
1386 commands.postincoming = _dummy
1401 commands.postincoming = _dummy
1387 try:
1402 try:
1388 ret = orig(ui, repo, *args, **opts)
1403 ret = orig(ui, repo, *args, **opts)
1389 finally:
1404 finally:
1390 commands.postincoming = origpostincoming
1405 commands.postincoming = origpostincoming
1391 revspostpull = len(repo)
1406 revspostpull = len(repo)
1392 if revspostpull > revsprepull:
1407 if revspostpull > revsprepull:
1393 # --rev option from pull conflict with rebase own --rev
1408 # --rev option from pull conflict with rebase own --rev
1394 # dropping it
1409 # dropping it
1395 if 'rev' in opts:
1410 if 'rev' in opts:
1396 del opts['rev']
1411 del opts['rev']
1397 # positional argument from pull conflicts with rebase's own
1412 # positional argument from pull conflicts with rebase's own
1398 # --source.
1413 # --source.
1399 if 'source' in opts:
1414 if 'source' in opts:
1400 del opts['source']
1415 del opts['source']
1401 # revsprepull is the len of the repo, not revnum of tip.
1416 # revsprepull is the len of the repo, not revnum of tip.
1402 destspace = list(repo.changelog.revs(start=revsprepull))
1417 destspace = list(repo.changelog.revs(start=revsprepull))
1403 opts['_destspace'] = destspace
1418 opts['_destspace'] = destspace
1404 try:
1419 try:
1405 rebase(ui, repo, **opts)
1420 rebase(ui, repo, **opts)
1406 except error.NoMergeDestAbort:
1421 except error.NoMergeDestAbort:
1407 # we can maybe update instead
1422 # we can maybe update instead
1408 rev, _a, _b = destutil.destupdate(repo)
1423 rev, _a, _b = destutil.destupdate(repo)
1409 if rev == repo['.'].rev():
1424 if rev == repo['.'].rev():
1410 ui.status(_('nothing to rebase\n'))
1425 ui.status(_('nothing to rebase\n'))
1411 else:
1426 else:
1412 ui.status(_('nothing to rebase - updating instead\n'))
1427 ui.status(_('nothing to rebase - updating instead\n'))
1413 # not passing argument to get the bare update behavior
1428 # not passing argument to get the bare update behavior
1414 # with warning and trumpets
1429 # with warning and trumpets
1415 commands.update(ui, repo)
1430 commands.update(ui, repo)
1416 finally:
1431 finally:
1417 release(lock, wlock)
1432 release(lock, wlock)
1418 else:
1433 else:
1419 if opts.get('tool'):
1434 if opts.get('tool'):
1420 raise error.Abort(_('--tool can only be used with --rebase'))
1435 raise error.Abort(_('--tool can only be used with --rebase'))
1421 ret = orig(ui, repo, *args, **opts)
1436 ret = orig(ui, repo, *args, **opts)
1422
1437
1423 return ret
1438 return ret
1424
1439
1425 def _setrebasesetvisibility(repo, revs):
1440 def _setrebasesetvisibility(repo, revs):
1426 """store the currently rebased set on the repo object
1441 """store the currently rebased set on the repo object
1427
1442
1428 This is used by another function to prevent rebased revision to because
1443 This is used by another function to prevent rebased revision to because
1429 hidden (see issue4504)"""
1444 hidden (see issue4504)"""
1430 repo = repo.unfiltered()
1445 repo = repo.unfiltered()
1431 repo._rebaseset = revs
1446 repo._rebaseset = revs
1432 # invalidate cache if visibility changes
1447 # invalidate cache if visibility changes
1433 hiddens = repo.filteredrevcache.get('visible', set())
1448 hiddens = repo.filteredrevcache.get('visible', set())
1434 if revs & hiddens:
1449 if revs & hiddens:
1435 repo.invalidatevolatilesets()
1450 repo.invalidatevolatilesets()
1436
1451
1437 def _clearrebasesetvisibiliy(repo):
1452 def _clearrebasesetvisibiliy(repo):
1438 """remove rebaseset data from the repo"""
1453 """remove rebaseset data from the repo"""
1439 repo = repo.unfiltered()
1454 repo = repo.unfiltered()
1440 if '_rebaseset' in vars(repo):
1455 if '_rebaseset' in vars(repo):
1441 del repo._rebaseset
1456 del repo._rebaseset
1442
1457
1443 def _rebasedvisible(orig, repo):
1458 def _rebasedvisible(orig, repo):
1444 """ensure rebased revs stay visible (see issue4504)"""
1459 """ensure rebased revs stay visible (see issue4504)"""
1445 blockers = orig(repo)
1460 blockers = orig(repo)
1446 blockers.update(getattr(repo, '_rebaseset', ()))
1461 blockers.update(getattr(repo, '_rebaseset', ()))
1447 return blockers
1462 return blockers
1448
1463
1449 def _filterobsoleterevs(repo, revs):
1464 def _filterobsoleterevs(repo, revs):
1450 """returns a set of the obsolete revisions in revs"""
1465 """returns a set of the obsolete revisions in revs"""
1451 return set(r for r in revs if repo[r].obsolete())
1466 return set(r for r in revs if repo[r].obsolete())
1452
1467
1453 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1468 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1454 """return a mapping obsolete => successor for all obsolete nodes to be
1469 """return a mapping obsolete => successor for all obsolete nodes to be
1455 rebased that have a successors in the destination
1470 rebased that have a successors in the destination
1456
1471
1457 obsolete => None entries in the mapping indicate nodes with no successor"""
1472 obsolete => None entries in the mapping indicate nodes with no successor"""
1458 obsoletenotrebased = {}
1473 obsoletenotrebased = {}
1459
1474
1460 # Build a mapping successor => obsolete nodes for the obsolete
1475 # Build a mapping successor => obsolete nodes for the obsolete
1461 # nodes to be rebased
1476 # nodes to be rebased
1462 allsuccessors = {}
1477 allsuccessors = {}
1463 cl = repo.changelog
1478 cl = repo.changelog
1464 for r in rebaseobsrevs:
1479 for r in rebaseobsrevs:
1465 node = cl.node(r)
1480 node = cl.node(r)
1466 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1481 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1467 try:
1482 try:
1468 allsuccessors[cl.rev(s)] = cl.rev(node)
1483 allsuccessors[cl.rev(s)] = cl.rev(node)
1469 except LookupError:
1484 except LookupError:
1470 pass
1485 pass
1471
1486
1472 if allsuccessors:
1487 if allsuccessors:
1473 # Look for successors of obsolete nodes to be rebased among
1488 # Look for successors of obsolete nodes to be rebased among
1474 # the ancestors of dest
1489 # the ancestors of dest
1475 ancs = cl.ancestors([repo[dest].rev()],
1490 ancs = cl.ancestors([repo[dest].rev()],
1476 stoprev=min(allsuccessors),
1491 stoprev=min(allsuccessors),
1477 inclusive=True)
1492 inclusive=True)
1478 for s in allsuccessors:
1493 for s in allsuccessors:
1479 if s in ancs:
1494 if s in ancs:
1480 obsoletenotrebased[allsuccessors[s]] = s
1495 obsoletenotrebased[allsuccessors[s]] = s
1481 elif (s == allsuccessors[s] and
1496 elif (s == allsuccessors[s] and
1482 allsuccessors.values().count(s) == 1):
1497 allsuccessors.values().count(s) == 1):
1483 # plain prune
1498 # plain prune
1484 obsoletenotrebased[s] = None
1499 obsoletenotrebased[s] = None
1485
1500
1486 return obsoletenotrebased
1501 return obsoletenotrebased
1487
1502
1488 def summaryhook(ui, repo):
1503 def summaryhook(ui, repo):
1489 if not repo.vfs.exists('rebasestate'):
1504 if not repo.vfs.exists('rebasestate'):
1490 return
1505 return
1491 try:
1506 try:
1492 rbsrt = rebaseruntime(repo, ui, {})
1507 rbsrt = rebaseruntime(repo, ui, {})
1493 rbsrt.restorestatus()
1508 rbsrt.restorestatus()
1494 state = rbsrt.state
1509 state = rbsrt.state
1495 except error.RepoLookupError:
1510 except error.RepoLookupError:
1496 # i18n: column positioning for "hg summary"
1511 # i18n: column positioning for "hg summary"
1497 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1512 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1498 ui.write(msg)
1513 ui.write(msg)
1499 return
1514 return
1500 numrebased = len([i for i in state.itervalues() if i >= 0])
1515 numrebased = len([i for i in state.itervalues() if i >= 0])
1501 # i18n: column positioning for "hg summary"
1516 # i18n: column positioning for "hg summary"
1502 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1517 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1503 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1518 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1504 ui.label(_('%d remaining'), 'rebase.remaining') %
1519 ui.label(_('%d remaining'), 'rebase.remaining') %
1505 (len(state) - numrebased)))
1520 (len(state) - numrebased)))
1506
1521
1507 def uisetup(ui):
1522 def uisetup(ui):
1508 #Replace pull with a decorator to provide --rebase option
1523 #Replace pull with a decorator to provide --rebase option
1509 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1524 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1510 entry[1].append(('', 'rebase', None,
1525 entry[1].append(('', 'rebase', None,
1511 _("rebase working directory to branch head")))
1526 _("rebase working directory to branch head")))
1512 entry[1].append(('t', 'tool', '',
1527 entry[1].append(('t', 'tool', '',
1513 _("specify merge tool for rebase")))
1528 _("specify merge tool for rebase")))
1514 cmdutil.summaryhooks.add('rebase', summaryhook)
1529 cmdutil.summaryhooks.add('rebase', summaryhook)
1515 cmdutil.unfinishedstates.append(
1530 cmdutil.unfinishedstates.append(
1516 ['rebasestate', False, False, _('rebase in progress'),
1531 ['rebasestate', False, False, _('rebase in progress'),
1517 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1532 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1518 cmdutil.afterresolvedstates.append(
1533 cmdutil.afterresolvedstates.append(
1519 ['rebasestate', _('hg rebase --continue')])
1534 ['rebasestate', _('hg rebase --continue')])
1520 # ensure rebased rev are not hidden
1535 # ensure rebased rev are not hidden
1521 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1536 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
@@ -1,393 +1,381 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > rebase=
3 > rebase=
4 > drawdag=$TESTDIR/drawdag.py
4 > drawdag=$TESTDIR/drawdag.py
5 >
5 >
6 > [phases]
6 > [phases]
7 > publish=False
7 > publish=False
8 >
8 >
9 > [alias]
9 > [alias]
10 > tglog = log -G --template "{rev}: {desc}"
10 > tglog = log -G --template "{rev}: {desc}"
11 > EOF
11 > EOF
12
12
13 $ rebasewithdag() {
13 $ rebasewithdag() {
14 > N=`$PYTHON -c "print($N+1)"`
14 > N=`$PYTHON -c "print($N+1)"`
15 > hg init repo$N && cd repo$N
15 > hg init repo$N && cd repo$N
16 > hg debugdrawdag
16 > hg debugdrawdag
17 > hg rebase "$@" > _rebasetmp
17 > hg rebase "$@" > _rebasetmp
18 > r=$?
18 > r=$?
19 > grep -v 'saved backup bundle' _rebasetmp
19 > grep -v 'saved backup bundle' _rebasetmp
20 > [ $r -eq 0 ] && hg tglog
20 > [ $r -eq 0 ] && hg tglog
21 > cd ..
21 > cd ..
22 > return $r
22 > return $r
23 > }
23 > }
24
24
25 Single branching point, without merge:
25 Single branching point, without merge:
26
26
27 $ rebasewithdag -b D -d Z <<'EOS'
27 $ rebasewithdag -b D -d Z <<'EOS'
28 > D E
28 > D E
29 > |/
29 > |/
30 > Z B C # C: branching point, E should be picked
30 > Z B C # C: branching point, E should be picked
31 > \|/ # B should not be picked
31 > \|/ # B should not be picked
32 > A
32 > A
33 > |
33 > |
34 > R
34 > R
35 > EOS
35 > EOS
36 rebasing 3:d6003a550c2c "C" (C)
36 rebasing 3:d6003a550c2c "C" (C)
37 rebasing 5:4526cf523425 "D" (D)
37 rebasing 5:4526cf523425 "D" (D)
38 rebasing 6:b296604d9846 "E" (E tip)
38 rebasing 6:b296604d9846 "E" (E tip)
39 o 6: E
39 o 6: E
40 |
40 |
41 | o 5: D
41 | o 5: D
42 |/
42 |/
43 o 4: C
43 o 4: C
44 |
44 |
45 o 3: Z
45 o 3: Z
46 |
46 |
47 | o 2: B
47 | o 2: B
48 |/
48 |/
49 o 1: A
49 o 1: A
50 |
50 |
51 o 0: R
51 o 0: R
52
52
53 Multiple branching points caused by selecting a single merge changeset:
53 Multiple branching points caused by selecting a single merge changeset:
54
54
55 $ rebasewithdag -b E -d Z <<'EOS'
55 $ rebasewithdag -b E -d Z <<'EOS'
56 > E
56 > E
57 > /|
57 > /|
58 > B C D # B, C: multiple branching points
58 > B C D # B, C: multiple branching points
59 > | |/ # D should not be picked
59 > | |/ # D should not be picked
60 > Z | /
60 > Z | /
61 > \|/
61 > \|/
62 > A
62 > A
63 > |
63 > |
64 > R
64 > R
65 > EOS
65 > EOS
66 rebasing 2:c1e6b162678d "B" (B)
66 rebasing 2:c1e6b162678d "B" (B)
67 rebasing 3:d6003a550c2c "C" (C)
67 rebasing 3:d6003a550c2c "C" (C)
68 rebasing 6:5251e0cb7302 "E" (E tip)
68 rebasing 6:5251e0cb7302 "E" (E tip)
69 o 6: E
69 o 6: E
70 |\
70 |\
71 | o 5: C
71 | o 5: C
72 | |
72 | |
73 o | 4: B
73 o | 4: B
74 |/
74 |/
75 o 3: Z
75 o 3: Z
76 |
76 |
77 | o 2: D
77 | o 2: D
78 |/
78 |/
79 o 1: A
79 o 1: A
80 |
80 |
81 o 0: R
81 o 0: R
82
82
83 Rebase should not extend the "--base" revset using "descendants":
83 Rebase should not extend the "--base" revset using "descendants":
84
84
85 $ rebasewithdag -b B -d Z <<'EOS'
85 $ rebasewithdag -b B -d Z <<'EOS'
86 > E
86 > E
87 > /|
87 > /|
88 > Z B C # descendants(B) = B+E. With E, C will be included incorrectly
88 > Z B C # descendants(B) = B+E. With E, C will be included incorrectly
89 > \|/
89 > \|/
90 > A
90 > A
91 > |
91 > |
92 > R
92 > R
93 > EOS
93 > EOS
94 rebasing 2:c1e6b162678d "B" (B)
94 rebasing 2:c1e6b162678d "B" (B)
95 rebasing 5:5251e0cb7302 "E" (E tip)
95 rebasing 5:5251e0cb7302 "E" (E tip)
96 o 5: E
96 o 5: E
97 |\
97 |\
98 | o 4: B
98 | o 4: B
99 | |
99 | |
100 | o 3: Z
100 | o 3: Z
101 | |
101 | |
102 o | 2: C
102 o | 2: C
103 |/
103 |/
104 o 1: A
104 o 1: A
105 |
105 |
106 o 0: R
106 o 0: R
107
107
108 Rebase should not simplify the "--base" revset using "roots":
108 Rebase should not simplify the "--base" revset using "roots":
109
109
110 $ rebasewithdag -b B+E -d Z <<'EOS'
110 $ rebasewithdag -b B+E -d Z <<'EOS'
111 > E
111 > E
112 > /|
112 > /|
113 > Z B C # roots(B+E) = B. Without E, C will be missed incorrectly
113 > Z B C # roots(B+E) = B. Without E, C will be missed incorrectly
114 > \|/
114 > \|/
115 > A
115 > A
116 > |
116 > |
117 > R
117 > R
118 > EOS
118 > EOS
119 rebasing 2:c1e6b162678d "B" (B)
119 rebasing 2:c1e6b162678d "B" (B)
120 rebasing 3:d6003a550c2c "C" (C)
120 rebasing 3:d6003a550c2c "C" (C)
121 rebasing 5:5251e0cb7302 "E" (E tip)
121 rebasing 5:5251e0cb7302 "E" (E tip)
122 o 5: E
122 o 5: E
123 |\
123 |\
124 | o 4: C
124 | o 4: C
125 | |
125 | |
126 o | 3: B
126 o | 3: B
127 |/
127 |/
128 o 2: Z
128 o 2: Z
129 |
129 |
130 o 1: A
130 o 1: A
131 |
131 |
132 o 0: R
132 o 0: R
133
133
134 The destination is one of the two branching points of a merge:
134 The destination is one of the two branching points of a merge:
135
135
136 $ rebasewithdag -b F -d Z <<'EOS'
136 $ rebasewithdag -b F -d Z <<'EOS'
137 > F
137 > F
138 > / \
138 > / \
139 > E D
139 > E D
140 > / /
140 > / /
141 > Z C
141 > Z C
142 > \ /
142 > \ /
143 > B
143 > B
144 > |
144 > |
145 > A
145 > A
146 > EOS
146 > EOS
147 nothing to rebase
147 nothing to rebase
148 [1]
148 [1]
149
149
150 Multiple branching points caused by multiple bases (issue5420):
150 Multiple branching points caused by multiple bases (issue5420):
151
151
152 $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
152 $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
153 > Z E2
153 > Z E2
154 > | /
154 > | /
155 > F E1 C2
155 > F E1 C2
156 > |/ /
156 > |/ /
157 > E C1 B2
157 > E C1 B2
158 > |/ /
158 > |/ /
159 > C B1
159 > C B1
160 > |/
160 > |/
161 > B
161 > B
162 > |
162 > |
163 > A
163 > A
164 > |
164 > |
165 > R
165 > R
166 > EOS
166 > EOS
167 rebasing 3:a113dbaa660a "B1" (B1)
167 rebasing 3:a113dbaa660a "B1" (B1)
168 rebasing 5:06ce7b1cc8c2 "B2" (B2)
168 rebasing 5:06ce7b1cc8c2 "B2" (B2)
169 rebasing 6:0ac98cce32d3 "C1" (C1)
169 rebasing 6:0ac98cce32d3 "C1" (C1)
170 rebasing 8:781512f5e33d "C2" (C2)
170 rebasing 8:781512f5e33d "C2" (C2)
171 rebasing 9:428d8c18f641 "E1" (E1)
171 rebasing 9:428d8c18f641 "E1" (E1)
172 rebasing 11:e1bf82f6b6df "E2" (E2)
172 rebasing 11:e1bf82f6b6df "E2" (E2)
173 o 12: E2
173 o 12: E2
174 |
174 |
175 o 11: E1
175 o 11: E1
176 |
176 |
177 | o 10: C2
177 | o 10: C2
178 | |
178 | |
179 | o 9: C1
179 | o 9: C1
180 |/
180 |/
181 | o 8: B2
181 | o 8: B2
182 | |
182 | |
183 | o 7: B1
183 | o 7: B1
184 |/
184 |/
185 o 6: Z
185 o 6: Z
186 |
186 |
187 o 5: F
187 o 5: F
188 |
188 |
189 o 4: E
189 o 4: E
190 |
190 |
191 o 3: C
191 o 3: C
192 |
192 |
193 o 2: B
193 o 2: B
194 |
194 |
195 o 1: A
195 o 1: A
196 |
196 |
197 o 0: R
197 o 0: R
198
198
199 Multiple branching points with multiple merges:
199 Multiple branching points with multiple merges:
200
200
201 $ rebasewithdag -b G+P -d Z <<'EOS'
201 $ rebasewithdag -b G+P -d Z <<'EOS'
202 > G H P
202 > G H P
203 > |\ /| |\
203 > |\ /| |\
204 > F E D M N
204 > F E D M N
205 > \|/| /| |\
205 > \|/| /| |\
206 > Z C B I J K L
206 > Z C B I J K L
207 > \|/ |/ |/
207 > \|/ |/ |/
208 > A A A
208 > A A A
209 > EOS
209 > EOS
210 rebasing 2:dc0947a82db8 "C" (C)
210 rebasing 2:dc0947a82db8 "C" (C)
211 rebasing 8:215e7b0814e1 "D" (D)
211 rebasing 8:215e7b0814e1 "D" (D)
212 rebasing 9:03ca77807e91 "E" (E)
212 rebasing 9:03ca77807e91 "E" (E)
213 rebasing 10:afc707c82df0 "F" (F)
213 rebasing 10:afc707c82df0 "F" (F)
214 rebasing 13:018caa673317 "G" (G)
214 rebasing 13:018caa673317 "G" (G)
215 rebasing 14:4f710fbd68cb "H" (H)
215 rebasing 14:4f710fbd68cb "H" (H)
216 rebasing 3:08ebfeb61bac "I" (I)
216 rebasing 3:08ebfeb61bac "I" (I)
217 rebasing 4:a0a5005cec67 "J" (J)
217 rebasing 4:a0a5005cec67 "J" (J)
218 rebasing 5:83780307a7e8 "K" (K)
218 rebasing 5:83780307a7e8 "K" (K)
219 rebasing 6:e131637a1cb6 "L" (L)
219 rebasing 6:e131637a1cb6 "L" (L)
220 rebasing 11:d6fe3d11d95d "M" (M)
220 rebasing 11:d6fe3d11d95d "M" (M)
221 rebasing 12:fa1e02269063 "N" (N)
221 rebasing 12:fa1e02269063 "N" (N)
222 rebasing 15:448b1a498430 "P" (P tip)
222 rebasing 15:448b1a498430 "P" (P tip)
223 o 15: P
223 o 15: P
224 |\
224 |\
225 | o 14: N
225 | o 14: N
226 | |\
226 | |\
227 o \ \ 13: M
227 o \ \ 13: M
228 |\ \ \
228 |\ \ \
229 | | | o 12: L
229 | | | o 12: L
230 | | | |
230 | | | |
231 | | o | 11: K
231 | | o | 11: K
232 | | |/
232 | | |/
233 | o / 10: J
233 | o / 10: J
234 | |/
234 | |/
235 o / 9: I
235 o / 9: I
236 |/
236 |/
237 | o 8: H
237 | o 8: H
238 | |\
238 | |\
239 | | | o 7: G
239 | | | o 7: G
240 | | |/|
240 | | |/|
241 | | | o 6: F
241 | | | o 6: F
242 | | | |
242 | | | |
243 | | o | 5: E
243 | | o | 5: E
244 | | |/
244 | | |/
245 | o | 4: D
245 | o | 4: D
246 | |\|
246 | |\|
247 +---o 3: C
247 +---o 3: C
248 | |
248 | |
249 o | 2: Z
249 o | 2: Z
250 | |
250 | |
251 | o 1: B
251 | o 1: B
252 |/
252 |/
253 o 0: A
253 o 0: A
254
254
255 Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
255 Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
256
256
257 $ rebasewithdag -b A3+B3 -d Z <<'EOF'
257 $ rebasewithdag -b A3+B3 -d Z <<'EOF'
258 > Z C1 A3 B3
258 > Z C1 A3 B3
259 > | / / \ / \
259 > | / / \ / \
260 > M3 C0 A1 A2 B1 B2
260 > M3 C0 A1 A2 B1 B2
261 > | / | | | |
261 > | / | | | |
262 > M2 M1 C1 C1 M3
262 > M2 M1 C1 C1 M3
263 > |
263 > |
264 > M1
264 > M1
265 > |
265 > |
266 > M0
266 > M0
267 > EOF
267 > EOF
268 rebasing 4:8817fae53c94 "C0" (C0)
268 rebasing 4:8817fae53c94 "C0" (C0)
269 rebasing 6:06ca5dfe3b5b "B2" (B2)
269 rebasing 6:06ca5dfe3b5b "B2" (B2)
270 rebasing 7:73508237b032 "C1" (C1)
270 rebasing 7:73508237b032 "C1" (C1)
271 rebasing 9:fdb955e2faed "A2" (A2)
271 rebasing 9:fdb955e2faed "A2" (A2)
272 rebasing 11:1b2f368c3cb5 "A3" (A3)
272 rebasing 11:1b2f368c3cb5 "A3" (A3)
273 rebasing 10:0a33b0519128 "B1" (B1)
273 rebasing 10:0a33b0519128 "B1" (B1)
274 rebasing 12:bd6a37b5b67a "B3" (B3 tip)
274 rebasing 12:bd6a37b5b67a "B3" (B3 tip)
275 o 12: B3
275 o 12: B3
276 |\
276 |\
277 | o 11: B1
277 | o 11: B1
278 | |
278 | |
279 | | o 10: A3
279 | | o 10: A3
280 | | |\
280 | | |\
281 | +---o 9: A2
281 | +---o 9: A2
282 | | |
282 | | |
283 | o | 8: C1
283 | o | 8: C1
284 | | |
284 | | |
285 o | | 7: B2
285 o | | 7: B2
286 | | |
286 | | |
287 | o | 6: C0
287 | o | 6: C0
288 |/ /
288 |/ /
289 o | 5: Z
289 o | 5: Z
290 | |
290 | |
291 o | 4: M3
291 o | 4: M3
292 | |
292 | |
293 o | 3: M2
293 o | 3: M2
294 | |
294 | |
295 | o 2: A1
295 | o 2: A1
296 |/
296 |/
297 o 1: M1
297 o 1: M1
298 |
298 |
299 o 0: M0
299 o 0: M0
300
300
301 Mixed rebasable and non-rebasable bases (unresolved, issue5422):
302
303 $ rebasewithdag -b C+D -d B <<'EOS'
304 > D
305 > /
306 > B C
307 > |/
308 > A
309 > EOS
310 nothing to rebase
311 [1]
312
313 Disconnected graph:
301 Disconnected graph:
314
302
315 $ rebasewithdag -b B -d Z <<'EOS'
303 $ rebasewithdag -b B -d Z <<'EOS'
316 > B
304 > B
317 > |
305 > |
318 > Z A
306 > Z A
319 > EOS
307 > EOS
320 nothing to rebase from 112478962961 to 48b9aae0607f
308 nothing to rebase from 112478962961 to 48b9aae0607f
321 [1]
309 [1]
322
310
323 Multiple roots. Roots are ancestors of dest:
311 Multiple roots. Roots are ancestors of dest:
324
312
325 $ rebasewithdag -b B+D -d Z <<'EOF'
313 $ rebasewithdag -b B+D -d Z <<'EOF'
326 > D Z B
314 > D Z B
327 > \|\|
315 > \|\|
328 > C A
316 > C A
329 > EOF
317 > EOF
330 rebasing 2:112478962961 "B" (B)
318 rebasing 2:112478962961 "B" (B)
331 rebasing 3:b70f76719894 "D" (D)
319 rebasing 3:b70f76719894 "D" (D)
332 o 4: D
320 o 4: D
333 |
321 |
334 | o 3: B
322 | o 3: B
335 |/
323 |/
336 o 2: Z
324 o 2: Z
337 |\
325 |\
338 | o 1: C
326 | o 1: C
339 |
327 |
340 o 0: A
328 o 0: A
341
329
342 Multiple roots. One root is not an ancestor of dest:
330 Multiple roots. One root is not an ancestor of dest:
343
331
344 $ rebasewithdag -b B+D -d Z <<'EOF'
332 $ rebasewithdag -b B+D -d Z <<'EOF'
345 > Z B D
333 > Z B D
346 > \|\|
334 > \|\|
347 > A C
335 > A C
348 > EOF
336 > EOF
349 nothing to rebase from 86d01f49c0d9+b70f76719894 to 262e37e34f63
337 nothing to rebase from 86d01f49c0d9+b70f76719894 to 262e37e34f63
350 [1]
338 [1]
351
339
352 Multiple roots. One root is not an ancestor of dest. Select using a merge:
340 Multiple roots. One root is not an ancestor of dest. Select using a merge:
353
341
354 $ rebasewithdag -b E -d Z <<'EOF'
342 $ rebasewithdag -b E -d Z <<'EOF'
355 > E
343 > E
356 > |\
344 > |\
357 > Z B D
345 > Z B D
358 > \|\|
346 > \|\|
359 > A C
347 > A C
360 > EOF
348 > EOF
361 rebasing 2:86d01f49c0d9 "B" (B)
349 rebasing 2:86d01f49c0d9 "B" (B)
362 rebasing 5:539a0ff83ea9 "E" (E tip)
350 rebasing 5:539a0ff83ea9 "E" (E tip)
363 o 5: E
351 o 5: E
364 |\
352 |\
365 | o 4: B
353 | o 4: B
366 | |\
354 | |\
367 | | o 3: Z
355 | | o 3: Z
368 | | |
356 | | |
369 o | | 2: D
357 o | | 2: D
370 |/ /
358 |/ /
371 o / 1: C
359 o / 1: C
372 /
360 /
373 o 0: A
361 o 0: A
374
362
375 Multiple roots. Two children share two parents while dest has only one parent:
363 Multiple roots. Two children share two parents while dest has only one parent:
376
364
377 $ rebasewithdag -b B+D -d Z <<'EOF'
365 $ rebasewithdag -b B+D -d Z <<'EOF'
378 > Z B D
366 > Z B D
379 > \|\|\
367 > \|\|\
380 > A C A
368 > A C A
381 > EOF
369 > EOF
382 rebasing 2:86d01f49c0d9 "B" (B)
370 rebasing 2:86d01f49c0d9 "B" (B)
383 rebasing 3:b7df2ca01aa8 "D" (D)
371 rebasing 3:b7df2ca01aa8 "D" (D)
384 o 4: D
372 o 4: D
385 |\
373 |\
386 +---o 3: B
374 +---o 3: B
387 | |/
375 | |/
388 | o 2: Z
376 | o 2: Z
389 | |
377 | |
390 o | 1: C
378 o | 1: C
391 /
379 /
392 o 0: A
380 o 0: A
393
381
General Comments 0
You need to be logged in to leave comments. Login now