##// END OF EJS Templates
rebase: support multiple roots for rebaseset...
Pierre-Yves David -
r18424:100fdc84 default
parent child Browse files
Show More
@@ -1,733 +1,737 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 http://mercurial.selenic.com/wiki/RebaseExtension
15 15 '''
16 16
17 17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 18 from mercurial import extensions, patch, scmutil, phases, obsolete
19 19 from mercurial.commands import templateopts
20 20 from mercurial.node import nullrev
21 21 from mercurial.lock import release
22 22 from mercurial.i18n import _
23 23 import os, errno
24 24
25 25 nullmerge = -2
26 26
27 27 cmdtable = {}
28 28 command = cmdutil.command(cmdtable)
29 29 testedwith = 'internal'
30 30
31 31 @command('rebase',
32 32 [('s', 'source', '',
33 33 _('rebase from the specified changeset'), _('REV')),
34 34 ('b', 'base', '',
35 35 _('rebase from the base of the specified changeset '
36 36 '(up to greatest common ancestor of base and dest)'),
37 37 _('REV')),
38 38 ('r', 'rev', [],
39 39 _('rebase these revisions'),
40 40 _('REV')),
41 41 ('d', 'dest', '',
42 42 _('rebase onto the specified changeset'), _('REV')),
43 43 ('', 'collapse', False, _('collapse the rebased changesets')),
44 44 ('m', 'message', '',
45 45 _('use text as collapse commit message'), _('TEXT')),
46 46 ('e', 'edit', False, _('invoke editor on commit messages')),
47 47 ('l', 'logfile', '',
48 48 _('read collapse commit message from file'), _('FILE')),
49 49 ('', 'keep', False, _('keep original changesets')),
50 50 ('', 'keepbranches', False, _('keep original branch names')),
51 51 ('D', 'detach', False, _('(DEPRECATED)')),
52 52 ('t', 'tool', '', _('specify merge tool')),
53 53 ('c', 'continue', False, _('continue an interrupted rebase')),
54 54 ('a', 'abort', False, _('abort an interrupted rebase'))] +
55 55 templateopts,
56 56 _('[-s REV | -b REV] [-d REV] [OPTION]'))
57 57 def rebase(ui, repo, **opts):
58 58 """move changeset (and descendants) to a different branch
59 59
60 60 Rebase uses repeated merging to graft changesets from one part of
61 61 history (the source) onto another (the destination). This can be
62 62 useful for linearizing *local* changes relative to a master
63 63 development tree.
64 64
65 65 You should not rebase changesets that have already been shared
66 66 with others. Doing so will force everybody else to perform the
67 67 same rebase or they will end up with duplicated changesets after
68 68 pulling in your rebased changesets.
69 69
70 70 If you don't specify a destination changeset (``-d/--dest``),
71 71 rebase uses the tipmost head of the current named branch as the
72 72 destination. (The destination changeset is not modified by
73 73 rebasing, but new changesets are added as its descendants.)
74 74
75 75 You can specify which changesets to rebase in two ways: as a
76 76 "source" changeset or as a "base" changeset. Both are shorthand
77 77 for a topologically related set of changesets (the "source
78 78 branch"). If you specify source (``-s/--source``), rebase will
79 79 rebase that changeset and all of its descendants onto dest. If you
80 80 specify base (``-b/--base``), rebase will select ancestors of base
81 81 back to but not including the common ancestor with dest. Thus,
82 82 ``-b`` is less precise but more convenient than ``-s``: you can
83 83 specify any changeset in the source branch, and rebase will select
84 84 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
85 85 uses the parent of the working directory as the base.
86 86
87 87 By default, rebase recreates the changesets in the source branch
88 88 as descendants of dest and then destroys the originals. Use
89 89 ``--keep`` to preserve the original source changesets. Some
90 90 changesets in the source branch (e.g. merges from the destination
91 91 branch) may be dropped if they no longer contribute any change.
92 92
93 93 One result of the rules for selecting the destination changeset
94 94 and source branch is that, unlike ``merge``, rebase will do
95 95 nothing if you are at the latest (tipmost) head of a named branch
96 96 with two heads. You need to explicitly specify source and/or
97 97 destination (or ``update`` to the other head, if it's the head of
98 98 the intended source branch).
99 99
100 100 If a rebase is interrupted to manually resolve a merge, it can be
101 101 continued with --continue/-c or aborted with --abort/-a.
102 102
103 103 Returns 0 on success, 1 if nothing to rebase.
104 104 """
105 105 originalwd = target = None
106 106 external = nullrev
107 107 state = {}
108 108 skipped = set()
109 109 targetancestors = set()
110 110
111 111 editor = None
112 112 if opts.get('edit'):
113 113 editor = cmdutil.commitforceeditor
114 114
115 115 lock = wlock = None
116 116 try:
117 117 wlock = repo.wlock()
118 118 lock = repo.lock()
119 119
120 120 # Validate input and define rebasing points
121 121 destf = opts.get('dest', None)
122 122 srcf = opts.get('source', None)
123 123 basef = opts.get('base', None)
124 124 revf = opts.get('rev', [])
125 125 contf = opts.get('continue')
126 126 abortf = opts.get('abort')
127 127 collapsef = opts.get('collapse', False)
128 128 collapsemsg = cmdutil.logmessage(ui, opts)
129 129 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion
130 130 keepf = opts.get('keep', False)
131 131 keepbranchesf = opts.get('keepbranches', False)
132 132 # keepopen is not meant for use on the command line, but by
133 133 # other extensions
134 134 keepopen = opts.get('keepopen', False)
135 135
136 136 if collapsemsg and not collapsef:
137 137 raise util.Abort(
138 138 _('message can only be specified with collapse'))
139 139
140 140 if contf or abortf:
141 141 if contf and abortf:
142 142 raise util.Abort(_('cannot use both abort and continue'))
143 143 if collapsef:
144 144 raise util.Abort(
145 145 _('cannot use collapse with continue or abort'))
146 146 if srcf or basef or destf:
147 147 raise util.Abort(
148 148 _('abort and continue do not allow specifying revisions'))
149 149 if opts.get('tool', False):
150 150 ui.warn(_('tool option will be ignored\n'))
151 151
152 152 (originalwd, target, state, skipped, collapsef, keepf,
153 153 keepbranchesf, external) = restorestatus(repo)
154 154 if abortf:
155 155 return abort(repo, originalwd, target, state)
156 156 else:
157 157 if srcf and basef:
158 158 raise util.Abort(_('cannot specify both a '
159 159 'source and a base'))
160 160 if revf and basef:
161 161 raise util.Abort(_('cannot specify both a '
162 162 'revision and a base'))
163 163 if revf and srcf:
164 164 raise util.Abort(_('cannot specify both a '
165 165 'revision and a source'))
166 166
167 167 cmdutil.bailifchanged(repo)
168 168
169 169 if not destf:
170 170 # Destination defaults to the latest revision in the
171 171 # current branch
172 172 branch = repo[None].branch()
173 173 dest = repo[branch]
174 174 else:
175 175 dest = scmutil.revsingle(repo, destf)
176 176
177 177 if revf:
178 178 rebaseset = repo.revs('%lr', revf)
179 179 elif srcf:
180 180 src = scmutil.revrange(repo, [srcf])
181 181 rebaseset = repo.revs('(%ld)::', src)
182 182 else:
183 183 base = scmutil.revrange(repo, [basef or '.'])
184 184 rebaseset = repo.revs(
185 185 '(children(ancestor(%ld, %d)) and ::(%ld))::',
186 186 base, dest, base)
187 187 if rebaseset:
188 188 root = min(rebaseset)
189 189 else:
190 190 root = None
191 191
192 192 if not rebaseset:
193 193 repo.ui.debug('base is ancestor of destination\n')
194 194 result = None
195 195 elif (not (keepf or obsolete._enabled)
196 196 and repo.revs('first(children(%ld) - %ld)',
197 197 rebaseset, rebaseset)):
198 198 raise util.Abort(
199 199 _("can't remove original changesets with"
200 200 " unrebased descendants"),
201 201 hint=_('use --keep to keep original changesets'))
202 202 elif not keepf and not repo[root].mutable():
203 203 raise util.Abort(_("can't rebase immutable changeset %s")
204 204 % repo[root],
205 205 hint=_('see hg help phases for details'))
206 206 else:
207 207 result = buildstate(repo, dest, rebaseset, collapsef)
208 208
209 209 if not result:
210 210 # Empty state built, nothing to rebase
211 211 ui.status(_('nothing to rebase\n'))
212 212 return 1
213 213 else:
214 214 originalwd, target, state = result
215 215 if collapsef:
216 216 targetancestors = repo.changelog.ancestors([target],
217 217 inclusive=True)
218 218 external = checkexternal(repo, state, targetancestors)
219 219
220 220 if keepbranchesf:
221 221 assert not extrafn, 'cannot use both keepbranches and extrafn'
222 222 def extrafn(ctx, extra):
223 223 extra['branch'] = ctx.branch()
224 224 if collapsef:
225 225 branches = set()
226 226 for rev in state:
227 227 branches.add(repo[rev].branch())
228 228 if len(branches) > 1:
229 229 raise util.Abort(_('cannot collapse multiple named '
230 230 'branches'))
231 231
232 232
233 233 # Rebase
234 234 if not targetancestors:
235 235 targetancestors = repo.changelog.ancestors([target], inclusive=True)
236 236
237 237 # Keep track of the current bookmarks in order to reset them later
238 238 currentbookmarks = repo._bookmarks.copy()
239 239 activebookmark = repo._bookmarkcurrent
240 240 if activebookmark:
241 241 bookmarks.unsetcurrent(repo)
242 242
243 243 sortedstate = sorted(state)
244 244 total = len(sortedstate)
245 245 pos = 0
246 246 for rev in sortedstate:
247 247 pos += 1
248 248 if state[rev] == -1:
249 249 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
250 250 _('changesets'), total)
251 251 storestatus(repo, originalwd, target, state, collapsef, keepf,
252 252 keepbranchesf, external)
253 253 p1, p2 = defineparents(repo, rev, target, state,
254 254 targetancestors)
255 255 if len(repo.parents()) == 2:
256 256 repo.ui.debug('resuming interrupted rebase\n')
257 257 else:
258 258 try:
259 259 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''))
260 260 stats = rebasenode(repo, rev, p1, state, collapsef)
261 261 if stats and stats[3] > 0:
262 262 raise util.Abort(_('unresolved conflicts (see hg '
263 263 'resolve, then hg rebase --continue)'))
264 264 finally:
265 265 ui.setconfig('ui', 'forcemerge', '')
266 266 cmdutil.duplicatecopies(repo, rev, target)
267 267 if not collapsef:
268 268 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
269 269 editor=editor)
270 270 else:
271 271 # Skip commit if we are collapsing
272 272 repo.setparents(repo[p1].node())
273 273 newrev = None
274 274 # Update the state
275 275 if newrev is not None:
276 276 state[rev] = repo[newrev].rev()
277 277 else:
278 278 if not collapsef:
279 279 ui.note(_('no changes, revision %d skipped\n') % rev)
280 280 ui.debug('next revision set to %s\n' % p1)
281 281 skipped.add(rev)
282 282 state[rev] = p1
283 283
284 284 ui.progress(_('rebasing'), None)
285 285 ui.note(_('rebase merging completed\n'))
286 286
287 287 if collapsef and not keepopen:
288 288 p1, p2 = defineparents(repo, min(state), target,
289 289 state, targetancestors)
290 290 if collapsemsg:
291 291 commitmsg = collapsemsg
292 292 else:
293 293 commitmsg = 'Collapsed revision'
294 294 for rebased in state:
295 295 if rebased not in skipped and state[rebased] != nullmerge:
296 296 commitmsg += '\n* %s' % repo[rebased].description()
297 297 commitmsg = ui.edit(commitmsg, repo.ui.username())
298 298 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
299 299 extrafn=extrafn, editor=editor)
300 300
301 301 if 'qtip' in repo.tags():
302 302 updatemq(repo, state, skipped, **opts)
303 303
304 304 if currentbookmarks:
305 305 # Nodeids are needed to reset bookmarks
306 306 nstate = {}
307 307 for k, v in state.iteritems():
308 308 if v != nullmerge:
309 309 nstate[repo[k].node()] = repo[v].node()
310 310
311 311 if not keepf:
312 312 collapsedas = None
313 313 if collapsef:
314 314 collapsedas = newrev
315 315 clearrebased(ui, repo, state, collapsedas)
316 316
317 317 if currentbookmarks:
318 318 updatebookmarks(repo, nstate, currentbookmarks, **opts)
319 319
320 320 clearstatus(repo)
321 321 ui.note(_("rebase completed\n"))
322 322 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
323 323 if skipped:
324 324 ui.note(_("%d revisions have been skipped\n") % len(skipped))
325 325
326 326 if (activebookmark and
327 327 repo['tip'].node() == repo._bookmarks[activebookmark]):
328 328 bookmarks.setcurrent(repo, activebookmark)
329 329
330 330 finally:
331 331 release(lock, wlock)
332 332
333 333 def checkexternal(repo, state, targetancestors):
334 334 """Check whether one or more external revisions need to be taken in
335 335 consideration. In the latter case, abort.
336 336 """
337 337 external = nullrev
338 338 source = min(state)
339 339 for rev in state:
340 340 if rev == source:
341 341 continue
342 342 # Check externals and fail if there are more than one
343 343 for p in repo[rev].parents():
344 344 if (p.rev() not in state
345 345 and p.rev() not in targetancestors):
346 346 if external != nullrev:
347 347 raise util.Abort(_('unable to collapse, there is more '
348 348 'than one external parent'))
349 349 external = p.rev()
350 350 return external
351 351
352 352 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
353 353 'Commit the changes and store useful information in extra'
354 354 try:
355 355 repo.setparents(repo[p1].node(), repo[p2].node())
356 356 ctx = repo[rev]
357 357 if commitmsg is None:
358 358 commitmsg = ctx.description()
359 359 extra = {'rebase_source': ctx.hex()}
360 360 if extrafn:
361 361 extrafn(ctx, extra)
362 362 # Commit might fail if unresolved files exist
363 363 newrev = repo.commit(text=commitmsg, user=ctx.user(),
364 364 date=ctx.date(), extra=extra, editor=editor)
365 365 repo.dirstate.setbranch(repo[newrev].branch())
366 366 targetphase = max(ctx.phase(), phases.draft)
367 367 # retractboundary doesn't overwrite upper phase inherited from parent
368 368 newnode = repo[newrev].node()
369 369 if newnode:
370 370 phases.retractboundary(repo, targetphase, [newnode])
371 371 return newrev
372 372 except util.Abort:
373 373 # Invalidate the previous setparents
374 374 repo.dirstate.invalidate()
375 375 raise
376 376
377 377 def rebasenode(repo, rev, p1, state, collapse):
378 378 'Rebase a single revision'
379 379 # Merge phase
380 380 # Update to target and merge it with local
381 381 if repo['.'].rev() != repo[p1].rev():
382 382 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
383 383 merge.update(repo, p1, False, True, False)
384 384 else:
385 385 repo.ui.debug(" already in target\n")
386 386 repo.dirstate.write()
387 387 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
388 388 base = None
389 389 if repo[rev].rev() != repo[min(state)].rev():
390 390 base = repo[rev].p1().node()
391 391 # When collapsing in-place, the parent is the common ancestor, we
392 392 # have to allow merging with it.
393 393 return merge.update(repo, rev, True, True, False, base, collapse)
394 394
395 395 def defineparents(repo, rev, target, state, targetancestors):
396 396 'Return the new parent relationship of the revision that will be rebased'
397 397 parents = repo[rev].parents()
398 398 p1 = p2 = nullrev
399 399
400 400 P1n = parents[0].rev()
401 401 if P1n in targetancestors:
402 402 p1 = target
403 403 elif P1n in state:
404 404 if state[P1n] == nullmerge:
405 405 p1 = target
406 406 else:
407 407 p1 = state[P1n]
408 408 else: # P1n external
409 409 p1 = target
410 410 p2 = P1n
411 411
412 412 if len(parents) == 2 and parents[1].rev() not in targetancestors:
413 413 P2n = parents[1].rev()
414 414 # interesting second parent
415 415 if P2n in state:
416 416 if p1 == target: # P1n in targetancestors or external
417 417 p1 = state[P2n]
418 418 else:
419 419 p2 = state[P2n]
420 420 else: # P2n external
421 421 if p2 != nullrev: # P1n external too => rev is a merged revision
422 422 raise util.Abort(_('cannot use revision %d as base, result '
423 423 'would have 3 parents') % rev)
424 424 p2 = P2n
425 425 repo.ui.debug(" future parents are %d and %d\n" %
426 426 (repo[p1].rev(), repo[p2].rev()))
427 427 return p1, p2
428 428
429 429 def isagitpatch(repo, patchname):
430 430 'Return true if the given patch is in git format'
431 431 mqpatch = os.path.join(repo.mq.path, patchname)
432 432 for line in patch.linereader(file(mqpatch, 'rb')):
433 433 if line.startswith('diff --git'):
434 434 return True
435 435 return False
436 436
437 437 def updatemq(repo, state, skipped, **opts):
438 438 'Update rebased mq patches - finalize and then import them'
439 439 mqrebase = {}
440 440 mq = repo.mq
441 441 original_series = mq.fullseries[:]
442 442 skippedpatches = set()
443 443
444 444 for p in mq.applied:
445 445 rev = repo[p.node].rev()
446 446 if rev in state:
447 447 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
448 448 (rev, p.name))
449 449 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
450 450 else:
451 451 # Applied but not rebased, not sure this should happen
452 452 skippedpatches.add(p.name)
453 453
454 454 if mqrebase:
455 455 mq.finish(repo, mqrebase.keys())
456 456
457 457 # We must start import from the newest revision
458 458 for rev in sorted(mqrebase, reverse=True):
459 459 if rev not in skipped:
460 460 name, isgit = mqrebase[rev]
461 461 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
462 462 mq.qimport(repo, (), patchname=name, git=isgit,
463 463 rev=[str(state[rev])])
464 464 else:
465 465 # Rebased and skipped
466 466 skippedpatches.add(mqrebase[rev][0])
467 467
468 468 # Patches were either applied and rebased and imported in
469 469 # order, applied and removed or unapplied. Discard the removed
470 470 # ones while preserving the original series order and guards.
471 471 newseries = [s for s in original_series
472 472 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
473 473 mq.fullseries[:] = newseries
474 474 mq.seriesdirty = True
475 475 mq.savedirty()
476 476
477 477 def updatebookmarks(repo, nstate, originalbookmarks, **opts):
478 478 'Move bookmarks to their correct changesets'
479 479 marks = repo._bookmarks
480 480 for k, v in originalbookmarks.iteritems():
481 481 if v in nstate:
482 482 if nstate[v] != nullmerge:
483 483 # update the bookmarks for revs that have moved
484 484 marks[k] = nstate[v]
485 485
486 486 marks.write()
487 487
488 488 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
489 489 external):
490 490 'Store the current status to allow recovery'
491 491 f = repo.opener("rebasestate", "w")
492 492 f.write(repo[originalwd].hex() + '\n')
493 493 f.write(repo[target].hex() + '\n')
494 494 f.write(repo[external].hex() + '\n')
495 495 f.write('%d\n' % int(collapse))
496 496 f.write('%d\n' % int(keep))
497 497 f.write('%d\n' % int(keepbranches))
498 498 for d, v in state.iteritems():
499 499 oldrev = repo[d].hex()
500 500 if v != nullmerge:
501 501 newrev = repo[v].hex()
502 502 else:
503 503 newrev = v
504 504 f.write("%s:%s\n" % (oldrev, newrev))
505 505 f.close()
506 506 repo.ui.debug('rebase status stored\n')
507 507
508 508 def clearstatus(repo):
509 509 'Remove the status files'
510 510 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
511 511
512 512 def restorestatus(repo):
513 513 'Restore a previously stored status'
514 514 try:
515 515 target = None
516 516 collapse = False
517 517 external = nullrev
518 518 state = {}
519 519 f = repo.opener("rebasestate")
520 520 for i, l in enumerate(f.read().splitlines()):
521 521 if i == 0:
522 522 originalwd = repo[l].rev()
523 523 elif i == 1:
524 524 target = repo[l].rev()
525 525 elif i == 2:
526 526 external = repo[l].rev()
527 527 elif i == 3:
528 528 collapse = bool(int(l))
529 529 elif i == 4:
530 530 keep = bool(int(l))
531 531 elif i == 5:
532 532 keepbranches = bool(int(l))
533 533 else:
534 534 oldrev, newrev = l.split(':')
535 535 if newrev != str(nullmerge):
536 536 state[repo[oldrev].rev()] = repo[newrev].rev()
537 537 else:
538 538 state[repo[oldrev].rev()] = int(newrev)
539 539 skipped = set()
540 540 # recompute the set of skipped revs
541 541 if not collapse:
542 542 seen = set([target])
543 543 for old, new in sorted(state.items()):
544 544 if new != nullrev and new in seen:
545 545 skipped.add(old)
546 546 seen.add(new)
547 547 repo.ui.debug('computed skipped revs: %s\n' % skipped)
548 548 repo.ui.debug('rebase status resumed\n')
549 549 return (originalwd, target, state, skipped,
550 550 collapse, keep, keepbranches, external)
551 551 except IOError, err:
552 552 if err.errno != errno.ENOENT:
553 553 raise
554 554 raise util.Abort(_('no rebase in progress'))
555 555
556 556 def abort(repo, originalwd, target, state):
557 557 'Restore the repository to its original state'
558 558 dstates = [s for s in state.values() if s != nullrev]
559 559 immutable = [d for d in dstates if not repo[d].mutable()]
560 560 if immutable:
561 561 raise util.Abort(_("can't abort rebase due to immutable changesets %s")
562 562 % ', '.join(str(repo[r]) for r in immutable),
563 563 hint=_('see hg help phases for details'))
564 564
565 565 descendants = set()
566 566 if dstates:
567 567 descendants = set(repo.changelog.descendants(dstates))
568 568 if descendants - set(dstates):
569 569 repo.ui.warn(_("warning: new changesets detected on target branch, "
570 570 "can't abort\n"))
571 571 return -1
572 572 else:
573 573 # Strip from the first rebased revision
574 574 merge.update(repo, repo[originalwd].rev(), False, True, False)
575 575 rebased = filter(lambda x: x > -1 and x != target, state.values())
576 576 if rebased:
577 strippoint = min(rebased)
577 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
578 578 # no backup of rebased cset versions needed
579 repair.strip(repo.ui, repo, repo[strippoint].node())
579 repair.strip(repo.ui, repo, strippoints)
580 580 clearstatus(repo)
581 581 repo.ui.warn(_('rebase aborted\n'))
582 582 return 0
583 583
584 584 def buildstate(repo, dest, rebaseset, collapse):
585 585 '''Define which revisions are going to be rebased and where
586 586
587 587 repo: repo
588 588 dest: context
589 589 rebaseset: set of rev
590 590 '''
591 591
592 592 # This check isn't strictly necessary, since mq detects commits over an
593 593 # applied patch. But it prevents messing up the working directory when
594 594 # a partially completed rebase is blocked by mq.
595 595 if 'qtip' in repo.tags() and (dest.node() in
596 596 [s.node for s in repo.mq.applied]):
597 597 raise util.Abort(_('cannot rebase onto an applied mq patch'))
598 598
599 599 roots = list(repo.set('roots(%ld)', rebaseset))
600 600 if not roots:
601 601 raise util.Abort(_('no matching revisions'))
602 if len(roots) > 1:
603 raise util.Abort(_("can't rebase multiple roots"))
604 root = roots[0]
605
606 commonbase = root.ancestor(dest)
607 if commonbase == root:
608 raise util.Abort(_('source is ancestor of destination'))
609 if commonbase == dest:
610 samebranch = root.branch() == dest.branch()
611 if not collapse and samebranch and root in dest.children():
612 repo.ui.debug('source is a child of destination\n')
613 return None
602 roots.sort()
603 state = {}
604 detachset = set()
605 for root in roots:
606 commonbase = root.ancestor(dest)
607 if commonbase == root:
608 raise util.Abort(_('source is ancestor of destination'))
609 if commonbase == dest:
610 samebranch = root.branch() == dest.branch()
611 if not collapse and samebranch and root in dest.children():
612 repo.ui.debug('source is a child of destination\n')
613 return None
614 614
615 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
616 state = dict.fromkeys(rebaseset, nullrev)
617 # Rebase tries to turn <dest> into a parent of <root> while
618 # preserving the number of parents of rebased changesets:
619 #
620 # - A changeset with a single parent will always be rebased as a
621 # changeset with a single parent.
622 #
623 # - A merge will be rebased as merge unless its parents are both
624 # ancestors of <dest> or are themselves in the rebased set and
625 # pruned while rebased.
626 #
627 # If one parent of <root> is an ancestor of <dest>, the rebased
628 # version of this parent will be <dest>. This is always true with
629 # --base option.
630 #
631 # Otherwise, we need to *replace* the original parents with
632 # <dest>. This "detaches" the rebased set from its former location
633 # and rebases it onto <dest>. Changes introduced by ancestors of
634 # <root> not common with <dest> (the detachset, marked as
635 # nullmerge) are "removed" from the rebased changesets.
636 #
637 # - If <root> has a single parent, set it to <dest>.
638 #
639 # - If <root> is a merge, we cannot decide which parent to
640 # replace, the rebase operation is not clearly defined.
641 #
642 # The table below sums up this behavior:
643 #
644 # +--------------------+----------------------+-------------------------+
645 # | | one parent | merge |
646 # +--------------------+----------------------+-------------------------+
647 # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are |
648 # | | | remapped to <dest> |
649 # +--------------------+----------------------+-------------------------+
650 # | unrelated source | new parent is <dest> | ambiguous, abort |
651 # +--------------------+----------------------+-------------------------+
652 #
653 # The actual abort is handled by `defineparents`
654 if len(root.parents()) <= 1:
655 # ancestors of <root> not ancestors of <dest>
656 detachset = repo.changelog.findmissingrevs([commonbase.rev()],
657 [root.rev()])
658 state.update(dict.fromkeys(detachset, nullmerge))
659 # detachset can have root, and we definitely want to rebase that
660 state[root.rev()] = nullrev
615 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, roots))
616 state.update(dict.fromkeys(rebaseset, nullrev))
617 # Rebase tries to turn <dest> into a parent of <root> while
618 # preserving the number of parents of rebased changesets:
619 #
620 # - A changeset with a single parent will always be rebased as a
621 # changeset with a single parent.
622 #
623 # - A merge will be rebased as merge unless its parents are both
624 # ancestors of <dest> or are themselves in the rebased set and
625 # pruned while rebased.
626 #
627 # If one parent of <root> is an ancestor of <dest>, the rebased
628 # version of this parent will be <dest>. This is always true with
629 # --base option.
630 #
631 # Otherwise, we need to *replace* the original parents with
632 # <dest>. This "detaches" the rebased set from its former location
633 # and rebases it onto <dest>. Changes introduced by ancestors of
634 # <root> not common with <dest> (the detachset, marked as
635 # nullmerge) are "removed" from the rebased changesets.
636 #
637 # - If <root> has a single parent, set it to <dest>.
638 #
639 # - If <root> is a merge, we cannot decide which parent to
640 # replace, the rebase operation is not clearly defined.
641 #
642 # The table below sums up this behavior:
643 #
644 # +------------------+----------------------+-------------------------+
645 # | | one parent | merge |
646 # +------------------+----------------------+-------------------------+
647 # | parent in | new parent is <dest> | parents in ::<dest> are |
648 # | ::<dest> | | remapped to <dest> |
649 # +------------------+----------------------+-------------------------+
650 # | unrelated source | new parent is <dest> | ambiguous, abort |
651 # +------------------+----------------------+-------------------------+
652 #
653 # The actual abort is handled by `defineparents`
654 if len(root.parents()) <= 1:
655 # ancestors of <root> not ancestors of <dest>
656 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
657 [root.rev()]))
658 for r in detachset:
659 if r not in state:
660 state[r] = nullmerge
661 661 return repo['.'].rev(), dest.rev(), state
662 662
663 663 def clearrebased(ui, repo, state, collapsedas=None):
664 664 """dispose of rebased revision at the end of the rebase
665 665
666 666 If `collapsedas` is not None, the rebase was a collapse whose result if the
667 667 `collapsedas` node."""
668 668 if obsolete._enabled:
669 669 markers = []
670 670 for rev, newrev in sorted(state.items()):
671 671 if newrev >= 0:
672 672 if collapsedas is not None:
673 673 newrev = collapsedas
674 674 markers.append((repo[rev], (repo[newrev],)))
675 675 if markers:
676 676 obsolete.createmarkers(repo, markers)
677 677 else:
678 678 rebased = [rev for rev in state if state[rev] != nullmerge]
679 679 if rebased:
680 if set(repo.changelog.descendants([min(rebased)])) - set(state):
681 ui.warn(_("warning: new changesets detected "
682 "on source branch, not stripping\n"))
683 else:
680 stripped = []
681 for root in repo.set('roots(%ld)', rebased):
682 if set(repo.changelog.descendants([root.rev()])) - set(state):
683 ui.warn(_("warning: new changesets detected "
684 "on source branch, not stripping\n"))
685 else:
686 stripped.append(root.node())
687 if stripped:
684 688 # backup the old csets by default
685 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
689 repair.strip(ui, repo, stripped, "all")
686 690
687 691
688 692 def pullrebase(orig, ui, repo, *args, **opts):
689 693 'Call rebase after pull if the latter has been invoked with --rebase'
690 694 if opts.get('rebase'):
691 695 if opts.get('update'):
692 696 del opts['update']
693 697 ui.debug('--update and --rebase are not compatible, ignoring '
694 698 'the update flag\n')
695 699
696 700 movemarkfrom = repo['.'].node()
697 701 cmdutil.bailifchanged(repo)
698 702 revsprepull = len(repo)
699 703 origpostincoming = commands.postincoming
700 704 def _dummy(*args, **kwargs):
701 705 pass
702 706 commands.postincoming = _dummy
703 707 try:
704 708 orig(ui, repo, *args, **opts)
705 709 finally:
706 710 commands.postincoming = origpostincoming
707 711 revspostpull = len(repo)
708 712 if revspostpull > revsprepull:
709 713 # --rev option from pull conflict with rebase own --rev
710 714 # dropping it
711 715 if 'rev' in opts:
712 716 del opts['rev']
713 717 rebase(ui, repo, **opts)
714 718 branch = repo[None].branch()
715 719 dest = repo[branch].rev()
716 720 if dest != repo['.'].rev():
717 721 # there was nothing to rebase we force an update
718 722 hg.update(repo, dest)
719 723 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
720 724 ui.status(_("updating bookmark %s\n")
721 725 % repo._bookmarkcurrent)
722 726 else:
723 727 if opts.get('tool'):
724 728 raise util.Abort(_('--tool can only be used with --rebase'))
725 729 orig(ui, repo, *args, **opts)
726 730
727 731 def uisetup(ui):
728 732 'Replace pull with a decorator to provide --rebase option'
729 733 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
730 734 entry[1].append(('', 'rebase', None,
731 735 _("rebase working directory to branch head")))
732 736 entry[1].append(('t', 'tool', '',
733 737 _("specify merge tool for rebase")))
@@ -1,308 +1,331 b''
1 1 ==========================
2 2 Test rebase with obsolete
3 3 ==========================
4 4
5 5 Enable obsolete
6 6
7 7 $ cat > ${TESTTMP}/obs.py << EOF
8 8 > import mercurial.obsolete
9 9 > mercurial.obsolete._enabled = True
10 10 > EOF
11 11 $ cat >> $HGRCPATH << EOF
12 12 > [ui]
13 13 > logtemplate= {rev}:{node|short} {desc|firstline}
14 14 > [phases]
15 15 > publish=False
16 16 > [extensions]'
17 17 > rebase=
18 18 >
19 19 > obs=${TESTTMP}/obs.py
20 20 > EOF
21 21
22 22 Setup rebase canonical repo
23 23
24 24 $ hg init base
25 25 $ cd base
26 26 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
27 27 adding changesets
28 28 adding manifests
29 29 adding file changes
30 30 added 8 changesets with 7 changes to 7 files (+2 heads)
31 31 (run 'hg heads' to see heads, 'hg merge' to merge)
32 32 $ hg up tip
33 33 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 34 $ hg log -G
35 35 @ 7:02de42196ebe H
36 36 |
37 37 | o 6:eea13746799a G
38 38 |/|
39 39 o | 5:24b6387c8c8c F
40 40 | |
41 41 | o 4:9520eea781bc E
42 42 |/
43 43 | o 3:32af7686d403 D
44 44 | |
45 45 | o 2:5fddd98957c8 C
46 46 | |
47 47 | o 1:42ccdea3bb16 B
48 48 |/
49 49 o 0:cd010b8cd998 A
50 50
51 51 $ cd ..
52 52
53 53 simple rebase
54 54 ---------------------------------
55 55
56 56 $ hg clone base simple
57 57 updating to branch default
58 58 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 59 $ cd simple
60 60 $ hg up 32af7686d403
61 61 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
62 62 $ hg rebase -d eea13746799a
63 63 $ hg log -G
64 64 @ 10:8eeb3c33ad33 D
65 65 |
66 66 o 9:2327fea05063 C
67 67 |
68 68 o 8:e4e5be0395b2 B
69 69 |
70 70 | o 7:02de42196ebe H
71 71 | |
72 72 o | 6:eea13746799a G
73 73 |\|
74 74 | o 5:24b6387c8c8c F
75 75 | |
76 76 o | 4:9520eea781bc E
77 77 |/
78 78 o 0:cd010b8cd998 A
79 79
80 80 $ hg log --hidden -G
81 81 @ 10:8eeb3c33ad33 D
82 82 |
83 83 o 9:2327fea05063 C
84 84 |
85 85 o 8:e4e5be0395b2 B
86 86 |
87 87 | o 7:02de42196ebe H
88 88 | |
89 89 o | 6:eea13746799a G
90 90 |\|
91 91 | o 5:24b6387c8c8c F
92 92 | |
93 93 o | 4:9520eea781bc E
94 94 |/
95 95 | x 3:32af7686d403 D
96 96 | |
97 97 | x 2:5fddd98957c8 C
98 98 | |
99 99 | x 1:42ccdea3bb16 B
100 100 |/
101 101 o 0:cd010b8cd998 A
102 102
103 103 $ hg debugobsolete
104 104 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 {'date': '*', 'user': 'test'} (glob)
105 105 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 {'date': '*', 'user': 'test'} (glob)
106 106 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 {'date': '*', 'user': 'test'} (glob)
107 107
108 108
109 109 $ cd ..
110 110
111 111 empty changeset
112 112 ---------------------------------
113 113
114 114 $ hg clone base empty
115 115 updating to branch default
116 116 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 $ cd empty
118 118 $ hg up eea13746799a
119 119 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
120 120
121 121 We make a copy of both the first changeset in the rebased and some other in the
122 122 set.
123 123
124 124 $ hg graft 42ccdea3bb16 32af7686d403
125 125 grafting revision 1
126 126 grafting revision 3
127 127 $ hg rebase -s 42ccdea3bb16 -d .
128 128 $ hg log -G
129 129 @ 10:5ae4c968c6ac C
130 130 |
131 131 o 9:08483444fef9 D
132 132 |
133 133 o 8:8877864f1edb B
134 134 |
135 135 | o 7:02de42196ebe H
136 136 | |
137 137 o | 6:eea13746799a G
138 138 |\|
139 139 | o 5:24b6387c8c8c F
140 140 | |
141 141 o | 4:9520eea781bc E
142 142 |/
143 143 o 0:cd010b8cd998 A
144 144
145 145 $ hg log --hidden -G
146 146 @ 10:5ae4c968c6ac C
147 147 |
148 148 o 9:08483444fef9 D
149 149 |
150 150 o 8:8877864f1edb B
151 151 |
152 152 | o 7:02de42196ebe H
153 153 | |
154 154 o | 6:eea13746799a G
155 155 |\|
156 156 | o 5:24b6387c8c8c F
157 157 | |
158 158 o | 4:9520eea781bc E
159 159 |/
160 160 | x 3:32af7686d403 D
161 161 | |
162 162 | x 2:5fddd98957c8 C
163 163 | |
164 164 | x 1:42ccdea3bb16 B
165 165 |/
166 166 o 0:cd010b8cd998 A
167 167
168 168 $ hg debugobsolete
169 169 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 08483444fef91d6224f6655ee586a65d263ad34c 0 {'date': '*', 'user': 'test'} (glob)
170 170 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
171 171 32af7686d403cf45b5d95f2d70cebea587ac806a 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
172 172
173 173
174 174 $ cd ..
175 175
176 176 collapse rebase
177 177 ---------------------------------
178 178
179 179 $ hg clone base collapse
180 180 updating to branch default
181 181 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
182 182 $ cd collapse
183 183 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
184 184 $ hg log -G
185 185 @ 8:4dc2197e807b Collapsed revision
186 186 |
187 187 | o 7:02de42196ebe H
188 188 | |
189 189 o | 6:eea13746799a G
190 190 |\|
191 191 | o 5:24b6387c8c8c F
192 192 | |
193 193 o | 4:9520eea781bc E
194 194 |/
195 195 o 0:cd010b8cd998 A
196 196
197 197 $ hg log --hidden -G
198 198 @ 8:4dc2197e807b Collapsed revision
199 199 |
200 200 | o 7:02de42196ebe H
201 201 | |
202 202 o | 6:eea13746799a G
203 203 |\|
204 204 | o 5:24b6387c8c8c F
205 205 | |
206 206 o | 4:9520eea781bc E
207 207 |/
208 208 | x 3:32af7686d403 D
209 209 | |
210 210 | x 2:5fddd98957c8 C
211 211 | |
212 212 | x 1:42ccdea3bb16 B
213 213 |/
214 214 o 0:cd010b8cd998 A
215 215
216 216 $ hg id --debug
217 217 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
218 218 $ hg debugobsolete
219 219 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
220 220 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
221 221 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
222 222
223 223 $ cd ..
224 224
225 225 Rebase set has hidden descendants
226 226 ---------------------------------
227 227
228 228 We rebase a changeset which has a hidden changeset. The hidden changeset must
229 229 not be rebased.
230 230
231 231 $ hg clone base hidden
232 232 updating to branch default
233 233 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 234 $ cd hidden
235 235 $ hg rebase -s 5fddd98957c8 -d eea13746799a
236 236 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
237 237 $ hg log -G
238 238 @ 10:7c6027df6a99 B
239 239 |
240 240 | o 9:cf44d2f5a9f4 D
241 241 | |
242 242 | o 8:e273c5e7d2d2 C
243 243 | |
244 244 o | 7:02de42196ebe H
245 245 | |
246 246 | o 6:eea13746799a G
247 247 |/|
248 248 o | 5:24b6387c8c8c F
249 249 | |
250 250 | o 4:9520eea781bc E
251 251 |/
252 252 o 0:cd010b8cd998 A
253 253
254 254 $ hg log --hidden -G
255 255 @ 10:7c6027df6a99 B
256 256 |
257 257 | o 9:cf44d2f5a9f4 D
258 258 | |
259 259 | o 8:e273c5e7d2d2 C
260 260 | |
261 261 o | 7:02de42196ebe H
262 262 | |
263 263 | o 6:eea13746799a G
264 264 |/|
265 265 o | 5:24b6387c8c8c F
266 266 | |
267 267 | o 4:9520eea781bc E
268 268 |/
269 269 | x 3:32af7686d403 D
270 270 | |
271 271 | x 2:5fddd98957c8 C
272 272 | |
273 273 | x 1:42ccdea3bb16 B
274 274 |/
275 275 o 0:cd010b8cd998 A
276 276
277 277 $ hg debugobsolete
278 278 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 {'date': '*', 'user': 'test'} (glob)
279 279 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 {'date': '*', 'user': 'test'} (glob)
280 280 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 {'date': '*', 'user': 'test'} (glob)
281 281
282 282 Test that rewriting leaving instability behind is allowed
283 283 ---------------------------------------------------------------------
284 284
285 285 $ hg log -r 'children(8)'
286 286 9:cf44d2f5a9f4 D (no-eol)
287 287 $ hg rebase -r 8
288 288 $ hg log -G
289 289 @ 11:0d8f238b634c C
290 290 |
291 291 o 10:7c6027df6a99 B
292 292 |
293 293 | o 9:cf44d2f5a9f4 D
294 294 | |
295 295 | x 8:e273c5e7d2d2 C
296 296 | |
297 297 o | 7:02de42196ebe H
298 298 | |
299 299 | o 6:eea13746799a G
300 300 |/|
301 301 o | 5:24b6387c8c8c F
302 302 | |
303 303 | o 4:9520eea781bc E
304 304 |/
305 305 o 0:cd010b8cd998 A
306 306
307 307
308 308
309 Test multiple root handling
310 ------------------------------------
311
312 $ hg rebase --dest 4 --rev '7+11+9'
313 $ hg log -G
314 @ 14:00891d85fcfc C
315 |
316 | o 13:102b4c1d889b D
317 |/
318 | o 12:bfe264faf697 H
319 |/
320 | o 10:7c6027df6a99 B
321 | |
322 | x 7:02de42196ebe H
323 | |
324 +---o 6:eea13746799a G
325 | |/
326 | o 5:24b6387c8c8c F
327 | |
328 o | 4:9520eea781bc E
329 |/
330 o 0:cd010b8cd998 A
331
@@ -1,547 +1,649 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > graphlog=
4 4 > rebase=
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
11 11 > EOF
12 12
13 13
14 14 $ hg init a
15 15 $ cd a
16 16 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
17 17 adding changesets
18 18 adding manifests
19 19 adding file changes
20 20 added 8 changesets with 7 changes to 7 files (+2 heads)
21 21 (run 'hg heads' to see heads, 'hg merge' to merge)
22 22 $ hg up tip
23 23 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
24 24 $ cd ..
25 25
26 26
27 27 Rebasing
28 28 D onto H - simple rebase:
29 29
30 30 $ hg clone -q -u . a a1
31 31 $ cd a1
32 32
33 33 $ hg tglog
34 34 @ 7: 'H'
35 35 |
36 36 | o 6: 'G'
37 37 |/|
38 38 o | 5: 'F'
39 39 | |
40 40 | o 4: 'E'
41 41 |/
42 42 | o 3: 'D'
43 43 | |
44 44 | o 2: 'C'
45 45 | |
46 46 | o 1: 'B'
47 47 |/
48 48 o 0: 'A'
49 49
50 50
51 51 $ hg rebase -s 3 -d 7
52 52 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
53 53
54 54 $ hg tglog
55 55 @ 7: 'D'
56 56 |
57 57 o 6: 'H'
58 58 |
59 59 | o 5: 'G'
60 60 |/|
61 61 o | 4: 'F'
62 62 | |
63 63 | o 3: 'E'
64 64 |/
65 65 | o 2: 'C'
66 66 | |
67 67 | o 1: 'B'
68 68 |/
69 69 o 0: 'A'
70 70
71 71 $ cd ..
72 72
73 73
74 74 D onto F - intermediate point:
75 75
76 76 $ hg clone -q -u . a a2
77 77 $ cd a2
78 78
79 79 $ hg rebase -s 3 -d 5
80 80 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
81 81
82 82 $ hg tglog
83 83 @ 7: 'D'
84 84 |
85 85 | o 6: 'H'
86 86 |/
87 87 | o 5: 'G'
88 88 |/|
89 89 o | 4: 'F'
90 90 | |
91 91 | o 3: 'E'
92 92 |/
93 93 | o 2: 'C'
94 94 | |
95 95 | o 1: 'B'
96 96 |/
97 97 o 0: 'A'
98 98
99 99 $ cd ..
100 100
101 101
102 102 E onto H - skip of G:
103 103
104 104 $ hg clone -q -u . a a3
105 105 $ cd a3
106 106
107 107 $ hg rebase -s 4 -d 7
108 108 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
109 109
110 110 $ hg tglog
111 111 @ 6: 'E'
112 112 |
113 113 o 5: 'H'
114 114 |
115 115 o 4: 'F'
116 116 |
117 117 | o 3: 'D'
118 118 | |
119 119 | o 2: 'C'
120 120 | |
121 121 | o 1: 'B'
122 122 |/
123 123 o 0: 'A'
124 124
125 125 $ cd ..
126 126
127 127
128 128 F onto E - rebase of a branching point (skip G):
129 129
130 130 $ hg clone -q -u . a a4
131 131 $ cd a4
132 132
133 133 $ hg rebase -s 5 -d 4
134 134 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob)
135 135
136 136 $ hg tglog
137 137 @ 6: 'H'
138 138 |
139 139 o 5: 'F'
140 140 |
141 141 o 4: 'E'
142 142 |
143 143 | o 3: 'D'
144 144 | |
145 145 | o 2: 'C'
146 146 | |
147 147 | o 1: 'B'
148 148 |/
149 149 o 0: 'A'
150 150
151 151 $ cd ..
152 152
153 153
154 154 G onto H - merged revision having a parent in ancestors of target:
155 155
156 156 $ hg clone -q -u . a a5
157 157 $ cd a5
158 158
159 159 $ hg rebase -s 6 -d 7
160 160 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob)
161 161
162 162 $ hg tglog
163 163 @ 7: 'G'
164 164 |\
165 165 | o 6: 'H'
166 166 | |
167 167 | o 5: 'F'
168 168 | |
169 169 o | 4: 'E'
170 170 |/
171 171 | o 3: 'D'
172 172 | |
173 173 | o 2: 'C'
174 174 | |
175 175 | o 1: 'B'
176 176 |/
177 177 o 0: 'A'
178 178
179 179 $ cd ..
180 180
181 181
182 182 F onto B - G maintains E as parent:
183 183
184 184 $ hg clone -q -u . a a6
185 185 $ cd a6
186 186
187 187 $ hg rebase -s 5 -d 1
188 188 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob)
189 189
190 190 $ hg tglog
191 191 @ 7: 'H'
192 192 |
193 193 | o 6: 'G'
194 194 |/|
195 195 o | 5: 'F'
196 196 | |
197 197 | o 4: 'E'
198 198 | |
199 199 | | o 3: 'D'
200 200 | | |
201 201 +---o 2: 'C'
202 202 | |
203 203 o | 1: 'B'
204 204 |/
205 205 o 0: 'A'
206 206
207 207 $ cd ..
208 208
209 209
210 210 These will fail (using --source):
211 211
212 212 G onto F - rebase onto an ancestor:
213 213
214 214 $ hg clone -q -u . a a7
215 215 $ cd a7
216 216
217 217 $ hg rebase -s 6 -d 5
218 218 nothing to rebase
219 219 [1]
220 220
221 221 F onto G - rebase onto a descendant:
222 222
223 223 $ hg rebase -s 5 -d 6
224 224 abort: source is ancestor of destination
225 225 [255]
226 226
227 227 G onto B - merge revision with both parents not in ancestors of target:
228 228
229 229 $ hg rebase -s 6 -d 1
230 230 abort: cannot use revision 6 as base, result would have 3 parents
231 231 [255]
232 232
233 233
234 234 These will abort gracefully (using --base):
235 235
236 236 G onto G - rebase onto same changeset:
237 237
238 238 $ hg rebase -b 6 -d 6
239 239 nothing to rebase
240 240 [1]
241 241
242 242 G onto F - rebase onto an ancestor:
243 243
244 244 $ hg rebase -b 6 -d 5
245 245 nothing to rebase
246 246 [1]
247 247
248 248 F onto G - rebase onto a descendant:
249 249
250 250 $ hg rebase -b 5 -d 6
251 251 nothing to rebase
252 252 [1]
253 253
254 254 C onto A - rebase onto an ancestor:
255 255
256 256 $ hg rebase -d 0 -s 2
257 257 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-backup.hg (glob)
258 258 $ hg tglog
259 259 @ 7: 'D'
260 260 |
261 261 o 6: 'C'
262 262 |
263 263 | o 5: 'H'
264 264 | |
265 265 | | o 4: 'G'
266 266 | |/|
267 267 | o | 3: 'F'
268 268 |/ /
269 269 | o 2: 'E'
270 270 |/
271 271 | o 1: 'B'
272 272 |/
273 273 o 0: 'A'
274 274
275 275
276 276 Check rebasing public changeset
277 277
278 278 $ hg pull --config phases.publish=True -q -r 6 . # update phase of 6
279 279 $ hg rebase -d 5 -b 6
280 280 abort: can't rebase immutable changeset e1c4361dd923
281 281 (see hg help phases for details)
282 282 [255]
283 283
284 284 $ hg rebase -d 5 -b 6 --keep
285 285
286 286 Check rebasing mutable changeset
287 287 Source phase greater or equal to destination phase: new changeset get the phase of source:
288 288 $ hg rebase -s9 -d0
289 289 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2b23e52411f4-backup.hg (glob)
290 290 $ hg log --template "{phase}\n" -r 9
291 291 draft
292 292 $ hg rebase -s9 -d1
293 293 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2cb10d0cfc6c-backup.hg (glob)
294 294 $ hg log --template "{phase}\n" -r 9
295 295 draft
296 296 $ hg phase --force --secret 9
297 297 $ hg rebase -s9 -d0
298 298 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/c5b12b67163a-backup.hg (glob)
299 299 $ hg log --template "{phase}\n" -r 9
300 300 secret
301 301 $ hg rebase -s9 -d1
302 302 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/2a0524f868ac-backup.hg (glob)
303 303 $ hg log --template "{phase}\n" -r 9
304 304 secret
305 305 Source phase lower than destination phase: new changeset get the phase of destination:
306 306 $ hg rebase -s8 -d9
307 307 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/6d4f22462821-backup.hg (glob)
308 308 $ hg log --template "{phase}\n" -r 'rev(9)'
309 309 secret
310 310
311 311 $ cd ..
312 312
313 313 Test for revset
314 314
315 315 We need a bit different graph
316 316 All destination are B
317 317
318 318 $ hg init ah
319 319 $ cd ah
320 320 $ hg unbundle "$TESTDIR/bundles/rebase-revset.hg"
321 321 adding changesets
322 322 adding manifests
323 323 adding file changes
324 324 added 9 changesets with 9 changes to 9 files (+2 heads)
325 325 (run 'hg heads' to see heads, 'hg merge' to merge)
326 326 $ hg tglog
327 327 o 8: 'I'
328 328 |
329 329 o 7: 'H'
330 330 |
331 331 o 6: 'G'
332 332 |
333 333 | o 5: 'F'
334 334 | |
335 335 | o 4: 'E'
336 336 |/
337 337 o 3: 'D'
338 338 |
339 339 o 2: 'C'
340 340 |
341 341 | o 1: 'B'
342 342 |/
343 343 o 0: 'A'
344 344
345 345 $ cd ..
346 346
347 347
348 348 Simple case with keep:
349 349
350 350 Source on have two descendant heads but ask for one
351 351
352 352 $ hg clone -q -u . ah ah1
353 353 $ cd ah1
354 354 $ hg rebase -r '2::8' -d 1
355 355 abort: can't remove original changesets with unrebased descendants
356 356 (use --keep to keep original changesets)
357 357 [255]
358 358 $ hg rebase -r '2::8' -d 1 --keep
359 359 $ hg tglog
360 360 @ 13: 'I'
361 361 |
362 362 o 12: 'H'
363 363 |
364 364 o 11: 'G'
365 365 |
366 366 o 10: 'D'
367 367 |
368 368 o 9: 'C'
369 369 |
370 370 | o 8: 'I'
371 371 | |
372 372 | o 7: 'H'
373 373 | |
374 374 | o 6: 'G'
375 375 | |
376 376 | | o 5: 'F'
377 377 | | |
378 378 | | o 4: 'E'
379 379 | |/
380 380 | o 3: 'D'
381 381 | |
382 382 | o 2: 'C'
383 383 | |
384 384 o | 1: 'B'
385 385 |/
386 386 o 0: 'A'
387 387
388 388
389 389 $ cd ..
390 390
391 391 Base on have one descendant heads we ask for but common ancestor have two
392 392
393 393 $ hg clone -q -u . ah ah2
394 394 $ cd ah2
395 395 $ hg rebase -r '3::8' -d 1
396 396 abort: can't remove original changesets with unrebased descendants
397 397 (use --keep to keep original changesets)
398 398 [255]
399 399 $ hg rebase -r '3::8' -d 1 --keep
400 400 $ hg tglog
401 401 @ 12: 'I'
402 402 |
403 403 o 11: 'H'
404 404 |
405 405 o 10: 'G'
406 406 |
407 407 o 9: 'D'
408 408 |
409 409 | o 8: 'I'
410 410 | |
411 411 | o 7: 'H'
412 412 | |
413 413 | o 6: 'G'
414 414 | |
415 415 | | o 5: 'F'
416 416 | | |
417 417 | | o 4: 'E'
418 418 | |/
419 419 | o 3: 'D'
420 420 | |
421 421 | o 2: 'C'
422 422 | |
423 423 o | 1: 'B'
424 424 |/
425 425 o 0: 'A'
426 426
427 427
428 428 $ cd ..
429 429
430 430 rebase subset
431 431
432 432 $ hg clone -q -u . ah ah3
433 433 $ cd ah3
434 434 $ hg rebase -r '3::7' -d 1
435 435 abort: can't remove original changesets with unrebased descendants
436 436 (use --keep to keep original changesets)
437 437 [255]
438 438 $ hg rebase -r '3::7' -d 1 --keep
439 439 $ hg tglog
440 440 @ 11: 'H'
441 441 |
442 442 o 10: 'G'
443 443 |
444 444 o 9: 'D'
445 445 |
446 446 | o 8: 'I'
447 447 | |
448 448 | o 7: 'H'
449 449 | |
450 450 | o 6: 'G'
451 451 | |
452 452 | | o 5: 'F'
453 453 | | |
454 454 | | o 4: 'E'
455 455 | |/
456 456 | o 3: 'D'
457 457 | |
458 458 | o 2: 'C'
459 459 | |
460 460 o | 1: 'B'
461 461 |/
462 462 o 0: 'A'
463 463
464 464
465 465 $ cd ..
466 466
467 467 rebase subset with multiple head
468 468
469 469 $ hg clone -q -u . ah ah4
470 470 $ cd ah4
471 471 $ hg rebase -r '3::(7+5)' -d 1
472 472 abort: can't remove original changesets with unrebased descendants
473 473 (use --keep to keep original changesets)
474 474 [255]
475 475 $ hg rebase -r '3::(7+5)' -d 1 --keep
476 476 $ hg tglog
477 477 @ 13: 'H'
478 478 |
479 479 o 12: 'G'
480 480 |
481 481 | o 11: 'F'
482 482 | |
483 483 | o 10: 'E'
484 484 |/
485 485 o 9: 'D'
486 486 |
487 487 | o 8: 'I'
488 488 | |
489 489 | o 7: 'H'
490 490 | |
491 491 | o 6: 'G'
492 492 | |
493 493 | | o 5: 'F'
494 494 | | |
495 495 | | o 4: 'E'
496 496 | |/
497 497 | o 3: 'D'
498 498 | |
499 499 | o 2: 'C'
500 500 | |
501 501 o | 1: 'B'
502 502 |/
503 503 o 0: 'A'
504 504
505 505
506 506 $ cd ..
507 507
508 508 More advanced tests
509 509
510 510 rebase on ancestor with revset
511 511
512 512 $ hg clone -q -u . ah ah5
513 513 $ cd ah5
514 514 $ hg rebase -r '6::' -d 2
515 515 saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-backup.hg (glob)
516 516 $ hg tglog
517 517 @ 8: 'I'
518 518 |
519 519 o 7: 'H'
520 520 |
521 521 o 6: 'G'
522 522 |
523 523 | o 5: 'F'
524 524 | |
525 525 | o 4: 'E'
526 526 | |
527 527 | o 3: 'D'
528 528 |/
529 529 o 2: 'C'
530 530 |
531 531 | o 1: 'B'
532 532 |/
533 533 o 0: 'A'
534 534
535 535 $ cd ..
536 536
537 537
538 538 rebase with multiple root.
539 539 We rebase E and G on B
540 540 We would expect heads are I, F if it was supported
541 541
542 542 $ hg clone -q -u . ah ah6
543 543 $ cd ah6
544 544 $ hg rebase -r '(4+6)::' -d 1
545 abort: can't rebase multiple roots
546 [255]
545 saved backup bundle to $TESTTMP/ah6/.hg/strip-backup/3d8a618087a7-backup.hg (glob)
546 $ hg tglog
547 @ 8: 'I'
548 |
549 o 7: 'H'
550 |
551 o 6: 'G'
552 |
553 | o 5: 'F'
554 | |
555 | o 4: 'E'
556 |/
557 | o 3: 'D'
558 | |
559 | o 2: 'C'
560 | |
561 o | 1: 'B'
562 |/
563 o 0: 'A'
564
547 565 $ cd ..
566
567 More complexe rebase with multiple roots
568 each root have a different common ancestor with the destination and this is a detach
569
570 (setup)
571
572 $ hg clone -q -u . a a8
573 $ cd a8
574 $ echo I > I
575 $ hg add I
576 $ hg commit -m I
577 $ hg up 4
578 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
579 $ echo I > J
580 $ hg add J
581 $ hg commit -m J
582 created new head
583 $ echo I > K
584 $ hg add K
585 $ hg commit -m K
586 $ hg tglog
587 @ 10: 'K'
588 |
589 o 9: 'J'
590 |
591 | o 8: 'I'
592 | |
593 | o 7: 'H'
594 | |
595 +---o 6: 'G'
596 | |/
597 | o 5: 'F'
598 | |
599 o | 4: 'E'
600 |/
601 | o 3: 'D'
602 | |
603 | o 2: 'C'
604 | |
605 | o 1: 'B'
606 |/
607 o 0: 'A'
608
609 (actual test)
610
611 $ hg rebase --dest 'desc(G)' --rev 'desc(K) + desc(I)'
612 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/23a4ace37988-backup.hg (glob)
613 $ hg log --rev 'children(desc(G))'
614 changeset: 9:adb617877056
615 parent: 6:eea13746799a
616 user: test
617 date: Thu Jan 01 00:00:00 1970 +0000
618 summary: I
619
620 changeset: 10:882431a34a0e
621 tag: tip
622 parent: 6:eea13746799a
623 user: test
624 date: Thu Jan 01 00:00:00 1970 +0000
625 summary: K
626
627 $ hg tglog
628 @ 10: 'K'
629 |
630 | o 9: 'I'
631 |/
632 | o 8: 'J'
633 | |
634 | | o 7: 'H'
635 | | |
636 o---+ 6: 'G'
637 |/ /
638 | o 5: 'F'
639 | |
640 o | 4: 'E'
641 |/
642 | o 3: 'D'
643 | |
644 | o 2: 'C'
645 | |
646 | o 1: 'B'
647 |/
648 o 0: 'A'
649
General Comments 0
You need to be logged in to leave comments. Login now