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