##// END OF EJS Templates
rebase: ensure rebase revision remains visible (issue4504)...
Pierre-Yves David -
r23970:8a544fb6 stable
parent child Browse files
Show More
@@ -1,1074 +1,1105 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, error
19 from mercurial import copies
19 from mercurial import copies, repoview
20 20 from mercurial.commands import templateopts
21 21 from mercurial.node import nullrev, nullid, hex, short
22 22 from mercurial.lock import release
23 23 from mercurial.i18n import _
24 24 import os, errno
25 25
26 26 revtodo = -1
27 27 nullmerge = -2
28 28 revignored = -3
29 29
30 30 cmdtable = {}
31 31 command = cmdutil.command(cmdtable)
32 32 testedwith = 'internal'
33 33
34 34 def _savegraft(ctx, extra):
35 35 s = ctx.extra().get('source', None)
36 36 if s is not None:
37 37 extra['source'] = s
38 38
39 39 def _savebranch(ctx, extra):
40 40 extra['branch'] = ctx.branch()
41 41
42 42 def _makeextrafn(copiers):
43 43 """make an extrafn out of the given copy-functions.
44 44
45 45 A copy function takes a context and an extra dict, and mutates the
46 46 extra dict as needed based on the given context.
47 47 """
48 48 def extrafn(ctx, extra):
49 49 for c in copiers:
50 50 c(ctx, extra)
51 51 return extrafn
52 52
53 53 @command('rebase',
54 54 [('s', 'source', '',
55 55 _('rebase the specified changeset and descendants'), _('REV')),
56 56 ('b', 'base', '',
57 57 _('rebase everything from branching point of specified changeset'),
58 58 _('REV')),
59 59 ('r', 'rev', [],
60 60 _('rebase these revisions'),
61 61 _('REV')),
62 62 ('d', 'dest', '',
63 63 _('rebase onto the specified changeset'), _('REV')),
64 64 ('', 'collapse', False, _('collapse the rebased changesets')),
65 65 ('m', 'message', '',
66 66 _('use text as collapse commit message'), _('TEXT')),
67 67 ('e', 'edit', False, _('invoke editor on commit messages')),
68 68 ('l', 'logfile', '',
69 69 _('read collapse commit message from file'), _('FILE')),
70 70 ('', 'keep', False, _('keep original changesets')),
71 71 ('', 'keepbranches', False, _('keep original branch names')),
72 72 ('D', 'detach', False, _('(DEPRECATED)')),
73 73 ('i', 'interactive', False, _('(DEPRECATED)')),
74 74 ('t', 'tool', '', _('specify merge tool')),
75 75 ('c', 'continue', False, _('continue an interrupted rebase')),
76 76 ('a', 'abort', False, _('abort an interrupted rebase'))] +
77 77 templateopts,
78 78 _('[-s REV | -b REV] [-d REV] [OPTION]'))
79 79 def rebase(ui, repo, **opts):
80 80 """move changeset (and descendants) to a different branch
81 81
82 82 Rebase uses repeated merging to graft changesets from one part of
83 83 history (the source) onto another (the destination). This can be
84 84 useful for linearizing *local* changes relative to a master
85 85 development tree.
86 86
87 87 You should not rebase changesets that have already been shared
88 88 with others. Doing so will force everybody else to perform the
89 89 same rebase or they will end up with duplicated changesets after
90 90 pulling in your rebased changesets.
91 91
92 92 In its default configuration, Mercurial will prevent you from
93 93 rebasing published changes. See :hg:`help phases` for details.
94 94
95 95 If you don't specify a destination changeset (``-d/--dest``),
96 96 rebase uses the current branch tip as the destination. (The
97 97 destination changeset is not modified by rebasing, but new
98 98 changesets are added as its descendants.)
99 99
100 100 You can specify which changesets to rebase in two ways: as a
101 101 "source" changeset or as a "base" changeset. Both are shorthand
102 102 for a topologically related set of changesets (the "source
103 103 branch"). If you specify source (``-s/--source``), rebase will
104 104 rebase that changeset and all of its descendants onto dest. If you
105 105 specify base (``-b/--base``), rebase will select ancestors of base
106 106 back to but not including the common ancestor with dest. Thus,
107 107 ``-b`` is less precise but more convenient than ``-s``: you can
108 108 specify any changeset in the source branch, and rebase will select
109 109 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
110 110 uses the parent of the working directory as the base.
111 111
112 112 For advanced usage, a third way is available through the ``--rev``
113 113 option. It allows you to specify an arbitrary set of changesets to
114 114 rebase. Descendants of revs you specify with this option are not
115 115 automatically included in the rebase.
116 116
117 117 By default, rebase recreates the changesets in the source branch
118 118 as descendants of dest and then destroys the originals. Use
119 119 ``--keep`` to preserve the original source changesets. Some
120 120 changesets in the source branch (e.g. merges from the destination
121 121 branch) may be dropped if they no longer contribute any change.
122 122
123 123 One result of the rules for selecting the destination changeset
124 124 and source branch is that, unlike ``merge``, rebase will do
125 125 nothing if you are at the branch tip of a named branch
126 126 with two heads. You need to explicitly specify source and/or
127 127 destination (or ``update`` to the other head, if it's the head of
128 128 the intended source branch).
129 129
130 130 If a rebase is interrupted to manually resolve a merge, it can be
131 131 continued with --continue/-c or aborted with --abort/-a.
132 132
133 133 .. container:: verbose
134 134
135 135 Examples:
136 136
137 137 - move "local changes" (current commit back to branching point)
138 138 to the current branch tip after a pull::
139 139
140 140 hg rebase
141 141
142 142 - move a single changeset to the stable branch::
143 143
144 144 hg rebase -r 5f493448 -d stable
145 145
146 146 - splice a commit and all its descendants onto another part of history::
147 147
148 148 hg rebase --source c0c3 --dest 4cf9
149 149
150 150 - rebase everything on a branch marked by a bookmark onto the
151 151 default branch::
152 152
153 153 hg rebase --base myfeature --dest default
154 154
155 155 - collapse a sequence of changes into a single commit::
156 156
157 157 hg rebase --collapse -r 1520:1525 -d .
158 158
159 159 - move a named branch while preserving its name::
160 160
161 161 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
162 162
163 163 Returns 0 on success, 1 if nothing to rebase or there are
164 164 unresolved conflicts.
165 165
166 166 """
167 167 originalwd = target = None
168 168 activebookmark = None
169 169 external = nullrev
170 170 state = {}
171 171 skipped = set()
172 172 targetancestors = set()
173 173
174 174
175 175 lock = wlock = None
176 176 try:
177 177 wlock = repo.wlock()
178 178 lock = repo.lock()
179 179
180 180 # Validate input and define rebasing points
181 181 destf = opts.get('dest', None)
182 182 srcf = opts.get('source', None)
183 183 basef = opts.get('base', None)
184 184 revf = opts.get('rev', [])
185 185 contf = opts.get('continue')
186 186 abortf = opts.get('abort')
187 187 collapsef = opts.get('collapse', False)
188 188 collapsemsg = cmdutil.logmessage(ui, opts)
189 189 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
190 190 extrafns = [_savegraft]
191 191 if e:
192 192 extrafns = [e]
193 193 keepf = opts.get('keep', False)
194 194 keepbranchesf = opts.get('keepbranches', False)
195 195 # keepopen is not meant for use on the command line, but by
196 196 # other extensions
197 197 keepopen = opts.get('keepopen', False)
198 198
199 199 if opts.get('interactive'):
200 200 msg = _("interactive history editing is supported by the "
201 201 "'histedit' extension (see \"hg help histedit\")")
202 202 raise util.Abort(msg)
203 203
204 204 if collapsemsg and not collapsef:
205 205 raise util.Abort(
206 206 _('message can only be specified with collapse'))
207 207
208 208 if contf or abortf:
209 209 if contf and abortf:
210 210 raise util.Abort(_('cannot use both abort and continue'))
211 211 if collapsef:
212 212 raise util.Abort(
213 213 _('cannot use collapse with continue or abort'))
214 214 if srcf or basef or destf:
215 215 raise util.Abort(
216 216 _('abort and continue do not allow specifying revisions'))
217 217 if opts.get('tool', False):
218 218 ui.warn(_('tool option will be ignored\n'))
219 219
220 220 try:
221 221 (originalwd, target, state, skipped, collapsef, keepf,
222 222 keepbranchesf, external, activebookmark) = restorestatus(repo)
223 223 except error.RepoLookupError:
224 224 if abortf:
225 225 clearstatus(repo)
226 226 repo.ui.warn(_('rebase aborted (no revision is removed,'
227 227 ' only broken state is cleared)\n'))
228 228 return 0
229 229 else:
230 230 msg = _('cannot continue inconsistent rebase')
231 231 hint = _('use "hg rebase --abort" to clear broken state')
232 232 raise util.Abort(msg, hint=hint)
233 233 if abortf:
234 234 return abort(repo, originalwd, target, state)
235 235 else:
236 236 if srcf and basef:
237 237 raise util.Abort(_('cannot specify both a '
238 238 'source and a base'))
239 239 if revf and basef:
240 240 raise util.Abort(_('cannot specify both a '
241 241 'revision and a base'))
242 242 if revf and srcf:
243 243 raise util.Abort(_('cannot specify both a '
244 244 'revision and a source'))
245 245
246 246 cmdutil.checkunfinished(repo)
247 247 cmdutil.bailifchanged(repo)
248 248
249 249 if not destf:
250 250 # Destination defaults to the latest revision in the
251 251 # current branch
252 252 branch = repo[None].branch()
253 253 dest = repo[branch]
254 254 else:
255 255 dest = scmutil.revsingle(repo, destf)
256 256
257 257 if revf:
258 258 rebaseset = scmutil.revrange(repo, revf)
259 259 if not rebaseset:
260 260 ui.status(_('empty "rev" revision set - '
261 261 'nothing to rebase\n'))
262 262 return 1
263 263 elif srcf:
264 264 src = scmutil.revrange(repo, [srcf])
265 265 if not src:
266 266 ui.status(_('empty "source" revision set - '
267 267 'nothing to rebase\n'))
268 268 return 1
269 269 rebaseset = repo.revs('(%ld)::', src)
270 270 assert rebaseset
271 271 else:
272 272 base = scmutil.revrange(repo, [basef or '.'])
273 273 if not base:
274 274 ui.status(_('empty "base" revision set - '
275 275 "can't compute rebase set\n"))
276 276 return 1
277 277 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
278 278 if commonanc is not None:
279 279 rebaseset = repo.revs('(%d::(%ld) - %d)::',
280 280 commonanc, base, commonanc)
281 281 else:
282 282 rebaseset = []
283 283
284 284 if not rebaseset:
285 285 # transform to list because smartsets are not comparable to
286 286 # lists. This should be improved to honor laziness of
287 287 # smartset.
288 288 if list(base) == [dest.rev()]:
289 289 if basef:
290 290 ui.status(_('nothing to rebase - %s is both "base"'
291 291 ' and destination\n') % dest)
292 292 else:
293 293 ui.status(_('nothing to rebase - working directory '
294 294 'parent is also destination\n'))
295 295 elif not repo.revs('%ld - ::%d', base, dest):
296 296 if basef:
297 297 ui.status(_('nothing to rebase - "base" %s is '
298 298 'already an ancestor of destination '
299 299 '%s\n') %
300 300 ('+'.join(str(repo[r]) for r in base),
301 301 dest))
302 302 else:
303 303 ui.status(_('nothing to rebase - working '
304 304 'directory parent is already an '
305 305 'ancestor of destination %s\n') % dest)
306 306 else: # can it happen?
307 307 ui.status(_('nothing to rebase from %s to %s\n') %
308 308 ('+'.join(str(repo[r]) for r in base), dest))
309 309 return 1
310 310
311 311 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
312 312 if (not (keepf or allowunstable)
313 313 and repo.revs('first(children(%ld) - %ld)',
314 314 rebaseset, rebaseset)):
315 315 raise util.Abort(
316 316 _("can't remove original changesets with"
317 317 " unrebased descendants"),
318 318 hint=_('use --keep to keep original changesets'))
319 319
320 320 result = buildstate(repo, dest, rebaseset, collapsef)
321 321 if not result:
322 322 # Empty state built, nothing to rebase
323 323 ui.status(_('nothing to rebase\n'))
324 324 return 1
325 325
326 326 root = min(rebaseset)
327 327 if not keepf and not repo[root].mutable():
328 328 raise util.Abort(_("can't rebase immutable changeset %s")
329 329 % repo[root],
330 330 hint=_('see "hg help phases" for details'))
331 331
332 332 originalwd, target, state = result
333 333 if collapsef:
334 334 targetancestors = repo.changelog.ancestors([target],
335 335 inclusive=True)
336 336 external = externalparent(repo, state, targetancestors)
337 337
338 338 if dest.closesbranch() and not keepbranchesf:
339 339 ui.status(_('reopening closed branch head %s\n') % dest)
340 340
341 341 if keepbranchesf:
342 342 # insert _savebranch at the start of extrafns so if
343 343 # there's a user-provided extrafn it can clobber branch if
344 344 # desired
345 345 extrafns.insert(0, _savebranch)
346 346 if collapsef:
347 347 branches = set()
348 348 for rev in state:
349 349 branches.add(repo[rev].branch())
350 350 if len(branches) > 1:
351 351 raise util.Abort(_('cannot collapse multiple named '
352 352 'branches'))
353 353
354 354 # Rebase
355 355 if not targetancestors:
356 356 targetancestors = repo.changelog.ancestors([target], inclusive=True)
357 357
358 358 # Keep track of the current bookmarks in order to reset them later
359 359 currentbookmarks = repo._bookmarks.copy()
360 360 activebookmark = activebookmark or repo._bookmarkcurrent
361 361 if activebookmark:
362 362 bookmarks.unsetcurrent(repo)
363 363
364 364 extrafn = _makeextrafn(extrafns)
365 365
366 366 sortedstate = sorted(state)
367 367 total = len(sortedstate)
368 368 pos = 0
369 369 for rev in sortedstate:
370 370 ctx = repo[rev]
371 371 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
372 372 ctx.description().split('\n', 1)[0])
373 373 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
374 374 if names:
375 375 desc += ' (%s)' % ' '.join(names)
376 376 pos += 1
377 377 if state[rev] == revtodo:
378 378 ui.status(_('rebasing %s\n') % desc)
379 379 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
380 380 _('changesets'), total)
381 381 p1, p2, base = defineparents(repo, rev, target, state,
382 382 targetancestors)
383 383 storestatus(repo, originalwd, target, state, collapsef, keepf,
384 384 keepbranchesf, external, activebookmark)
385 385 if len(repo.parents()) == 2:
386 386 repo.ui.debug('resuming interrupted rebase\n')
387 387 else:
388 388 try:
389 389 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
390 390 'rebase')
391 391 stats = rebasenode(repo, rev, p1, base, state,
392 392 collapsef, target)
393 393 if stats and stats[3] > 0:
394 394 raise error.InterventionRequired(
395 395 _('unresolved conflicts (see hg '
396 396 'resolve, then hg rebase --continue)'))
397 397 finally:
398 398 ui.setconfig('ui', 'forcemerge', '', 'rebase')
399 399 if not collapsef:
400 400 merging = p2 != nullrev
401 401 editform = cmdutil.mergeeditform(merging, 'rebase')
402 402 editor = cmdutil.getcommiteditor(editform=editform, **opts)
403 403 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
404 404 editor=editor)
405 405 else:
406 406 # Skip commit if we are collapsing
407 407 repo.dirstate.beginparentchange()
408 408 repo.setparents(repo[p1].node())
409 409 repo.dirstate.endparentchange()
410 410 newnode = None
411 411 # Update the state
412 412 if newnode is not None:
413 413 state[rev] = repo[newnode].rev()
414 414 ui.debug('rebased as %s\n' % short(newnode))
415 415 else:
416 416 ui.warn(_('note: rebase of %d:%s created no changes '
417 417 'to commit\n') % (rev, ctx))
418 418 if not collapsef:
419 419 skipped.add(rev)
420 420 state[rev] = p1
421 421 ui.debug('next revision set to %s\n' % p1)
422 422 elif state[rev] == nullmerge:
423 423 ui.debug('ignoring null merge rebase of %s\n' % rev)
424 424 elif state[rev] == revignored:
425 425 ui.status(_('not rebasing ignored %s\n') % desc)
426 426 else:
427 427 ui.status(_('already rebased %s as %s\n') %
428 428 (desc, repo[state[rev]]))
429 429
430 430 ui.progress(_('rebasing'), None)
431 431 ui.note(_('rebase merging completed\n'))
432 432
433 433 if collapsef and not keepopen:
434 434 p1, p2, _base = defineparents(repo, min(state), target,
435 435 state, targetancestors)
436 436 editopt = opts.get('edit')
437 437 editform = 'rebase.collapse'
438 438 if collapsemsg:
439 439 commitmsg = collapsemsg
440 440 else:
441 441 commitmsg = 'Collapsed revision'
442 442 for rebased in state:
443 443 if rebased not in skipped and state[rebased] > nullmerge:
444 444 commitmsg += '\n* %s' % repo[rebased].description()
445 445 editopt = True
446 446 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
447 447 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
448 448 extrafn=extrafn, editor=editor)
449 449 if newnode is None:
450 450 newrev = target
451 451 else:
452 452 newrev = repo[newnode].rev()
453 453 for oldrev in state.iterkeys():
454 454 if state[oldrev] > nullmerge:
455 455 state[oldrev] = newrev
456 456
457 457 if 'qtip' in repo.tags():
458 458 updatemq(repo, state, skipped, **opts)
459 459
460 460 if currentbookmarks:
461 461 # Nodeids are needed to reset bookmarks
462 462 nstate = {}
463 463 for k, v in state.iteritems():
464 464 if v > nullmerge:
465 465 nstate[repo[k].node()] = repo[v].node()
466 466 # XXX this is the same as dest.node() for the non-continue path --
467 467 # this should probably be cleaned up
468 468 targetnode = repo[target].node()
469 469
470 470 # restore original working directory
471 471 # (we do this before stripping)
472 472 newwd = state.get(originalwd, originalwd)
473 473 if newwd < 0:
474 474 # original directory is a parent of rebase set root or ignored
475 475 newwd = originalwd
476 476 if newwd not in [c.rev() for c in repo[None].parents()]:
477 477 ui.note(_("update back to initial working directory parent\n"))
478 478 hg.updaterepo(repo, newwd, False)
479 479
480 480 if not keepf:
481 481 collapsedas = None
482 482 if collapsef:
483 483 collapsedas = newnode
484 484 clearrebased(ui, repo, state, skipped, collapsedas)
485 485
486 486 if currentbookmarks:
487 487 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
488 488 if activebookmark not in repo._bookmarks:
489 489 # active bookmark was divergent one and has been deleted
490 490 activebookmark = None
491 491
492 492 clearstatus(repo)
493 493 ui.note(_("rebase completed\n"))
494 494 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
495 495 if skipped:
496 496 ui.note(_("%d revisions have been skipped\n") % len(skipped))
497 497
498 498 if (activebookmark and
499 499 repo['.'].node() == repo._bookmarks[activebookmark]):
500 500 bookmarks.setcurrent(repo, activebookmark)
501 501
502 502 finally:
503 503 release(lock, wlock)
504 504
505 505 def externalparent(repo, state, targetancestors):
506 506 """Return the revision that should be used as the second parent
507 507 when the revisions in state is collapsed on top of targetancestors.
508 508 Abort if there is more than one parent.
509 509 """
510 510 parents = set()
511 511 source = min(state)
512 512 for rev in state:
513 513 if rev == source:
514 514 continue
515 515 for p in repo[rev].parents():
516 516 if (p.rev() not in state
517 517 and p.rev() not in targetancestors):
518 518 parents.add(p.rev())
519 519 if not parents:
520 520 return nullrev
521 521 if len(parents) == 1:
522 522 return parents.pop()
523 523 raise util.Abort(_('unable to collapse on top of %s, there is more '
524 524 'than one external parent: %s') %
525 525 (max(targetancestors),
526 526 ', '.join(str(p) for p in sorted(parents))))
527 527
528 528 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
529 529 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
530 530 but also store useful information in extra.
531 531 Return node of committed revision.'''
532 532 try:
533 533 repo.dirstate.beginparentchange()
534 534 repo.setparents(repo[p1].node(), repo[p2].node())
535 535 repo.dirstate.endparentchange()
536 536 ctx = repo[rev]
537 537 if commitmsg is None:
538 538 commitmsg = ctx.description()
539 539 extra = {'rebase_source': ctx.hex()}
540 540 if extrafn:
541 541 extrafn(ctx, extra)
542 542
543 543 backup = repo.ui.backupconfig('phases', 'new-commit')
544 544 try:
545 545 targetphase = max(ctx.phase(), phases.draft)
546 546 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
547 547 # Commit might fail if unresolved files exist
548 548 newnode = repo.commit(text=commitmsg, user=ctx.user(),
549 549 date=ctx.date(), extra=extra, editor=editor)
550 550 finally:
551 551 repo.ui.restoreconfig(backup)
552 552
553 553 repo.dirstate.setbranch(repo[newnode].branch())
554 554 return newnode
555 555 except util.Abort:
556 556 # Invalidate the previous setparents
557 557 repo.dirstate.invalidate()
558 558 raise
559 559
560 560 def rebasenode(repo, rev, p1, base, state, collapse, target):
561 561 'Rebase a single revision rev on top of p1 using base as merge ancestor'
562 562 # Merge phase
563 563 # Update to target and merge it with local
564 564 if repo['.'].rev() != p1:
565 565 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
566 566 merge.update(repo, p1, False, True, False)
567 567 else:
568 568 repo.ui.debug(" already in target\n")
569 569 repo.dirstate.write()
570 570 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
571 571 if base is not None:
572 572 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
573 573 # When collapsing in-place, the parent is the common ancestor, we
574 574 # have to allow merging with it.
575 575 stats = merge.update(repo, rev, True, True, False, base, collapse,
576 576 labels=['dest', 'source'])
577 577 if collapse:
578 578 copies.duplicatecopies(repo, rev, target)
579 579 else:
580 580 # If we're not using --collapse, we need to
581 581 # duplicate copies between the revision we're
582 582 # rebasing and its first parent, but *not*
583 583 # duplicate any copies that have already been
584 584 # performed in the destination.
585 585 p1rev = repo[rev].p1().rev()
586 586 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
587 587 return stats
588 588
589 589 def nearestrebased(repo, rev, state):
590 590 """return the nearest ancestors of rev in the rebase result"""
591 591 rebased = [r for r in state if state[r] > nullmerge]
592 592 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
593 593 if candidates:
594 594 return state[candidates.first()]
595 595 else:
596 596 return None
597 597
598 598 def defineparents(repo, rev, target, state, targetancestors):
599 599 'Return the new parent relationship of the revision that will be rebased'
600 600 parents = repo[rev].parents()
601 601 p1 = p2 = nullrev
602 602
603 603 p1n = parents[0].rev()
604 604 if p1n in targetancestors:
605 605 p1 = target
606 606 elif p1n in state:
607 607 if state[p1n] == nullmerge:
608 608 p1 = target
609 609 elif state[p1n] == revignored:
610 610 p1 = nearestrebased(repo, p1n, state)
611 611 if p1 is None:
612 612 p1 = target
613 613 else:
614 614 p1 = state[p1n]
615 615 else: # p1n external
616 616 p1 = target
617 617 p2 = p1n
618 618
619 619 if len(parents) == 2 and parents[1].rev() not in targetancestors:
620 620 p2n = parents[1].rev()
621 621 # interesting second parent
622 622 if p2n in state:
623 623 if p1 == target: # p1n in targetancestors or external
624 624 p1 = state[p2n]
625 625 elif state[p2n] == revignored:
626 626 p2 = nearestrebased(repo, p2n, state)
627 627 if p2 is None:
628 628 # no ancestors rebased yet, detach
629 629 p2 = target
630 630 else:
631 631 p2 = state[p2n]
632 632 else: # p2n external
633 633 if p2 != nullrev: # p1n external too => rev is a merged revision
634 634 raise util.Abort(_('cannot use revision %d as base, result '
635 635 'would have 3 parents') % rev)
636 636 p2 = p2n
637 637 repo.ui.debug(" future parents are %d and %d\n" %
638 638 (repo[p1].rev(), repo[p2].rev()))
639 639
640 640 if rev == min(state):
641 641 # Case (1) initial changeset of a non-detaching rebase.
642 642 # Let the merge mechanism find the base itself.
643 643 base = None
644 644 elif not repo[rev].p2():
645 645 # Case (2) detaching the node with a single parent, use this parent
646 646 base = repo[rev].p1().rev()
647 647 else:
648 648 # Assuming there is a p1, this is the case where there also is a p2.
649 649 # We are thus rebasing a merge and need to pick the right merge base.
650 650 #
651 651 # Imagine we have:
652 652 # - M: current rebase revision in this step
653 653 # - A: one parent of M
654 654 # - B: other parent of M
655 655 # - D: destination of this merge step (p1 var)
656 656 #
657 657 # Consider the case where D is a descendant of A or B and the other is
658 658 # 'outside'. In this case, the right merge base is the D ancestor.
659 659 #
660 660 # An informal proof, assuming A is 'outside' and B is the D ancestor:
661 661 #
662 662 # If we pick B as the base, the merge involves:
663 663 # - changes from B to M (actual changeset payload)
664 664 # - changes from B to D (induced by rebase) as D is a rebased
665 665 # version of B)
666 666 # Which exactly represent the rebase operation.
667 667 #
668 668 # If we pick A as the base, the merge involves:
669 669 # - changes from A to M (actual changeset payload)
670 670 # - changes from A to D (with include changes between unrelated A and B
671 671 # plus changes induced by rebase)
672 672 # Which does not represent anything sensible and creates a lot of
673 673 # conflicts. A is thus not the right choice - B is.
674 674 #
675 675 # Note: The base found in this 'proof' is only correct in the specified
676 676 # case. This base does not make sense if is not D a descendant of A or B
677 677 # or if the other is not parent 'outside' (especially not if the other
678 678 # parent has been rebased). The current implementation does not
679 679 # make it feasible to consider different cases separately. In these
680 680 # other cases we currently just leave it to the user to correctly
681 681 # resolve an impossible merge using a wrong ancestor.
682 682 for p in repo[rev].parents():
683 683 if state.get(p.rev()) == p1:
684 684 base = p.rev()
685 685 break
686 686 else: # fallback when base not found
687 687 base = None
688 688
689 689 # Raise because this function is called wrong (see issue 4106)
690 690 raise AssertionError('no base found to rebase on '
691 691 '(defineparents called wrong)')
692 692 return p1, p2, base
693 693
694 694 def isagitpatch(repo, patchname):
695 695 'Return true if the given patch is in git format'
696 696 mqpatch = os.path.join(repo.mq.path, patchname)
697 697 for line in patch.linereader(file(mqpatch, 'rb')):
698 698 if line.startswith('diff --git'):
699 699 return True
700 700 return False
701 701
702 702 def updatemq(repo, state, skipped, **opts):
703 703 'Update rebased mq patches - finalize and then import them'
704 704 mqrebase = {}
705 705 mq = repo.mq
706 706 original_series = mq.fullseries[:]
707 707 skippedpatches = set()
708 708
709 709 for p in mq.applied:
710 710 rev = repo[p.node].rev()
711 711 if rev in state:
712 712 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
713 713 (rev, p.name))
714 714 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
715 715 else:
716 716 # Applied but not rebased, not sure this should happen
717 717 skippedpatches.add(p.name)
718 718
719 719 if mqrebase:
720 720 mq.finish(repo, mqrebase.keys())
721 721
722 722 # We must start import from the newest revision
723 723 for rev in sorted(mqrebase, reverse=True):
724 724 if rev not in skipped:
725 725 name, isgit = mqrebase[rev]
726 726 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
727 727 (name, state[rev], repo[state[rev]]))
728 728 mq.qimport(repo, (), patchname=name, git=isgit,
729 729 rev=[str(state[rev])])
730 730 else:
731 731 # Rebased and skipped
732 732 skippedpatches.add(mqrebase[rev][0])
733 733
734 734 # Patches were either applied and rebased and imported in
735 735 # order, applied and removed or unapplied. Discard the removed
736 736 # ones while preserving the original series order and guards.
737 737 newseries = [s for s in original_series
738 738 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
739 739 mq.fullseries[:] = newseries
740 740 mq.seriesdirty = True
741 741 mq.savedirty()
742 742
743 743 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
744 744 'Move bookmarks to their correct changesets, and delete divergent ones'
745 745 marks = repo._bookmarks
746 746 for k, v in originalbookmarks.iteritems():
747 747 if v in nstate:
748 748 # update the bookmarks for revs that have moved
749 749 marks[k] = nstate[v]
750 750 bookmarks.deletedivergent(repo, [targetnode], k)
751 751
752 752 marks.write()
753 753
754 754 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
755 755 external, activebookmark):
756 756 'Store the current status to allow recovery'
757 757 f = repo.vfs("rebasestate", "w")
758 758 f.write(repo[originalwd].hex() + '\n')
759 759 f.write(repo[target].hex() + '\n')
760 760 f.write(repo[external].hex() + '\n')
761 761 f.write('%d\n' % int(collapse))
762 762 f.write('%d\n' % int(keep))
763 763 f.write('%d\n' % int(keepbranches))
764 764 f.write('%s\n' % (activebookmark or ''))
765 765 for d, v in state.iteritems():
766 766 oldrev = repo[d].hex()
767 767 if v >= 0:
768 768 newrev = repo[v].hex()
769 769 elif v == revtodo:
770 770 # To maintain format compatibility, we have to use nullid.
771 771 # Please do remove this special case when upgrading the format.
772 772 newrev = hex(nullid)
773 773 else:
774 774 newrev = v
775 775 f.write("%s:%s\n" % (oldrev, newrev))
776 776 f.close()
777 777 repo.ui.debug('rebase status stored\n')
778 778
779 779 def clearstatus(repo):
780 780 'Remove the status files'
781 _clearrebasesetvisibiliy(repo)
781 782 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
782 783
783 784 def restorestatus(repo):
784 785 'Restore a previously stored status'
785 786 try:
786 787 keepbranches = None
787 788 target = None
788 789 collapse = False
789 790 external = nullrev
790 791 activebookmark = None
791 792 state = {}
792 793 f = repo.vfs("rebasestate")
793 794 for i, l in enumerate(f.read().splitlines()):
794 795 if i == 0:
795 796 originalwd = repo[l].rev()
796 797 elif i == 1:
797 798 target = repo[l].rev()
798 799 elif i == 2:
799 800 external = repo[l].rev()
800 801 elif i == 3:
801 802 collapse = bool(int(l))
802 803 elif i == 4:
803 804 keep = bool(int(l))
804 805 elif i == 5:
805 806 keepbranches = bool(int(l))
806 807 elif i == 6 and not (len(l) == 81 and ':' in l):
807 808 # line 6 is a recent addition, so for backwards compatibility
808 809 # check that the line doesn't look like the oldrev:newrev lines
809 810 activebookmark = l
810 811 else:
811 812 oldrev, newrev = l.split(':')
812 813 if newrev in (str(nullmerge), str(revignored)):
813 814 state[repo[oldrev].rev()] = int(newrev)
814 815 elif newrev == nullid:
815 816 state[repo[oldrev].rev()] = revtodo
816 817 # Legacy compat special case
817 818 else:
818 819 state[repo[oldrev].rev()] = repo[newrev].rev()
819 820
820 821 if keepbranches is None:
821 822 raise util.Abort(_('.hg/rebasestate is incomplete'))
822 823
823 824 skipped = set()
824 825 # recompute the set of skipped revs
825 826 if not collapse:
826 827 seen = set([target])
827 828 for old, new in sorted(state.items()):
828 829 if new != revtodo and new in seen:
829 830 skipped.add(old)
830 831 seen.add(new)
831 832 repo.ui.debug('computed skipped revs: %s\n' %
832 833 (' '.join(str(r) for r in sorted(skipped)) or None))
833 834 repo.ui.debug('rebase status resumed\n')
835 _setrebasesetvisibility(repo, state.keys())
834 836 return (originalwd, target, state, skipped,
835 837 collapse, keep, keepbranches, external, activebookmark)
836 838 except IOError, err:
837 839 if err.errno != errno.ENOENT:
838 840 raise
839 841 raise util.Abort(_('no rebase in progress'))
840 842
841 843 def inrebase(repo, originalwd, state):
842 844 '''check whether the working dir is in an interrupted rebase'''
843 845 parents = [p.rev() for p in repo.parents()]
844 846 if originalwd in parents:
845 847 return True
846 848
847 849 for newrev in state.itervalues():
848 850 if newrev in parents:
849 851 return True
850 852
851 853 return False
852 854
853 855 def abort(repo, originalwd, target, state):
854 856 'Restore the repository to its original state'
855 857 dstates = [s for s in state.values() if s >= 0]
856 858 immutable = [d for d in dstates if not repo[d].mutable()]
857 859 cleanup = True
858 860 if immutable:
859 861 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
860 862 % ', '.join(str(repo[r]) for r in immutable),
861 863 hint=_('see "hg help phases" for details'))
862 864 cleanup = False
863 865
864 866 descendants = set()
865 867 if dstates:
866 868 descendants = set(repo.changelog.descendants(dstates))
867 869 if descendants - set(dstates):
868 870 repo.ui.warn(_("warning: new changesets detected on target branch, "
869 871 "can't strip\n"))
870 872 cleanup = False
871 873
872 874 if cleanup:
873 875 # Update away from the rebase if necessary
874 876 if inrebase(repo, originalwd, state):
875 877 merge.update(repo, originalwd, False, True, False)
876 878
877 879 # Strip from the first rebased revision
878 880 rebased = filter(lambda x: x >= 0 and x != target, state.values())
879 881 if rebased:
880 882 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
881 883 # no backup of rebased cset versions needed
882 884 repair.strip(repo.ui, repo, strippoints)
883 885
884 886 clearstatus(repo)
885 887 repo.ui.warn(_('rebase aborted\n'))
886 888 return 0
887 889
888 890 def buildstate(repo, dest, rebaseset, collapse):
889 891 '''Define which revisions are going to be rebased and where
890 892
891 893 repo: repo
892 894 dest: context
893 895 rebaseset: set of rev
894 896 '''
897 _setrebasesetvisibility(repo, rebaseset)
895 898
896 899 # This check isn't strictly necessary, since mq detects commits over an
897 900 # applied patch. But it prevents messing up the working directory when
898 901 # a partially completed rebase is blocked by mq.
899 902 if 'qtip' in repo.tags() and (dest.node() in
900 903 [s.node for s in repo.mq.applied]):
901 904 raise util.Abort(_('cannot rebase onto an applied mq patch'))
902 905
903 906 roots = list(repo.set('roots(%ld)', rebaseset))
904 907 if not roots:
905 908 raise util.Abort(_('no matching revisions'))
906 909 roots.sort()
907 910 state = {}
908 911 detachset = set()
909 912 for root in roots:
910 913 commonbase = root.ancestor(dest)
911 914 if commonbase == root:
912 915 raise util.Abort(_('source is ancestor of destination'))
913 916 if commonbase == dest:
914 917 samebranch = root.branch() == dest.branch()
915 918 if not collapse and samebranch and root in dest.children():
916 919 repo.ui.debug('source is a child of destination\n')
917 920 return None
918 921
919 922 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
920 923 state.update(dict.fromkeys(rebaseset, revtodo))
921 924 # Rebase tries to turn <dest> into a parent of <root> while
922 925 # preserving the number of parents of rebased changesets:
923 926 #
924 927 # - A changeset with a single parent will always be rebased as a
925 928 # changeset with a single parent.
926 929 #
927 930 # - A merge will be rebased as merge unless its parents are both
928 931 # ancestors of <dest> or are themselves in the rebased set and
929 932 # pruned while rebased.
930 933 #
931 934 # If one parent of <root> is an ancestor of <dest>, the rebased
932 935 # version of this parent will be <dest>. This is always true with
933 936 # --base option.
934 937 #
935 938 # Otherwise, we need to *replace* the original parents with
936 939 # <dest>. This "detaches" the rebased set from its former location
937 940 # and rebases it onto <dest>. Changes introduced by ancestors of
938 941 # <root> not common with <dest> (the detachset, marked as
939 942 # nullmerge) are "removed" from the rebased changesets.
940 943 #
941 944 # - If <root> has a single parent, set it to <dest>.
942 945 #
943 946 # - If <root> is a merge, we cannot decide which parent to
944 947 # replace, the rebase operation is not clearly defined.
945 948 #
946 949 # The table below sums up this behavior:
947 950 #
948 951 # +------------------+----------------------+-------------------------+
949 952 # | | one parent | merge |
950 953 # +------------------+----------------------+-------------------------+
951 954 # | parent in | new parent is <dest> | parents in ::<dest> are |
952 955 # | ::<dest> | | remapped to <dest> |
953 956 # +------------------+----------------------+-------------------------+
954 957 # | unrelated source | new parent is <dest> | ambiguous, abort |
955 958 # +------------------+----------------------+-------------------------+
956 959 #
957 960 # The actual abort is handled by `defineparents`
958 961 if len(root.parents()) <= 1:
959 962 # ancestors of <root> not ancestors of <dest>
960 963 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
961 964 [root.rev()]))
962 965 for r in detachset:
963 966 if r not in state:
964 967 state[r] = nullmerge
965 968 if len(roots) > 1:
966 969 # If we have multiple roots, we may have "hole" in the rebase set.
967 970 # Rebase roots that descend from those "hole" should not be detached as
968 971 # other root are. We use the special `revignored` to inform rebase that
969 972 # the revision should be ignored but that `defineparents` should search
970 973 # a rebase destination that make sense regarding rebased topology.
971 974 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
972 975 for ignored in set(rebasedomain) - set(rebaseset):
973 976 state[ignored] = revignored
974 977 return repo['.'].rev(), dest.rev(), state
975 978
976 979 def clearrebased(ui, repo, state, skipped, collapsedas=None):
977 980 """dispose of rebased revision at the end of the rebase
978 981
979 982 If `collapsedas` is not None, the rebase was a collapse whose result if the
980 983 `collapsedas` node."""
981 984 if obsolete.isenabled(repo, obsolete.createmarkersopt):
982 985 markers = []
983 986 for rev, newrev in sorted(state.items()):
984 987 if newrev >= 0:
985 988 if rev in skipped:
986 989 succs = ()
987 990 elif collapsedas is not None:
988 991 succs = (repo[collapsedas],)
989 992 else:
990 993 succs = (repo[newrev],)
991 994 markers.append((repo[rev], succs))
992 995 if markers:
993 996 obsolete.createmarkers(repo, markers)
994 997 else:
995 998 rebased = [rev for rev in state if state[rev] > nullmerge]
996 999 if rebased:
997 1000 stripped = []
998 1001 for root in repo.set('roots(%ld)', rebased):
999 1002 if set(repo.changelog.descendants([root.rev()])) - set(state):
1000 1003 ui.warn(_("warning: new changesets detected "
1001 1004 "on source branch, not stripping\n"))
1002 1005 else:
1003 1006 stripped.append(root.node())
1004 1007 if stripped:
1005 1008 # backup the old csets by default
1006 1009 repair.strip(ui, repo, stripped, "all")
1007 1010
1008 1011
1009 1012 def pullrebase(orig, ui, repo, *args, **opts):
1010 1013 'Call rebase after pull if the latter has been invoked with --rebase'
1011 1014 if opts.get('rebase'):
1012 1015 if opts.get('update'):
1013 1016 del opts['update']
1014 1017 ui.debug('--update and --rebase are not compatible, ignoring '
1015 1018 'the update flag\n')
1016 1019
1017 1020 movemarkfrom = repo['.'].node()
1018 1021 revsprepull = len(repo)
1019 1022 origpostincoming = commands.postincoming
1020 1023 def _dummy(*args, **kwargs):
1021 1024 pass
1022 1025 commands.postincoming = _dummy
1023 1026 try:
1024 1027 orig(ui, repo, *args, **opts)
1025 1028 finally:
1026 1029 commands.postincoming = origpostincoming
1027 1030 revspostpull = len(repo)
1028 1031 if revspostpull > revsprepull:
1029 1032 # --rev option from pull conflict with rebase own --rev
1030 1033 # dropping it
1031 1034 if 'rev' in opts:
1032 1035 del opts['rev']
1033 1036 rebase(ui, repo, **opts)
1034 1037 branch = repo[None].branch()
1035 1038 dest = repo[branch].rev()
1036 1039 if dest != repo['.'].rev():
1037 1040 # there was nothing to rebase we force an update
1038 1041 hg.update(repo, dest)
1039 1042 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1040 1043 ui.status(_("updating bookmark %s\n")
1041 1044 % repo._bookmarkcurrent)
1042 1045 else:
1043 1046 if opts.get('tool'):
1044 1047 raise util.Abort(_('--tool can only be used with --rebase'))
1045 1048 orig(ui, repo, *args, **opts)
1046 1049
1050 def _setrebasesetvisibility(repo, revs):
1051 """store the currently rebased set on the repo object
1052
1053 This is used by another function to prevent rebased revision to because
1054 hidden (see issue4505)"""
1055 repo = repo.unfiltered()
1056 revs = set(revs)
1057 repo._rebaseset = revs
1058 # invalidate cache if visibility changes
1059 hiddens = repo.filteredrevcache.get('visible', set())
1060 if revs & hiddens:
1061 repo.invalidatevolatilesets()
1062
1063 def _clearrebasesetvisibiliy(repo):
1064 """remove rebaseset data from the repo"""
1065 repo = repo.unfiltered()
1066 if '_rebaseset' in vars(repo):
1067 del repo._rebaseset
1068
1069 def _rebasedvisible(orig, repo):
1070 """ensure rebased revs stay visible (see issue4505)"""
1071 blockers = orig(repo)
1072 blockers.update(getattr(repo, '_rebaseset', ()))
1073 return blockers
1074
1047 1075 def summaryhook(ui, repo):
1048 1076 if not os.path.exists(repo.join('rebasestate')):
1049 1077 return
1050 1078 try:
1051 1079 state = restorestatus(repo)[2]
1052 1080 except error.RepoLookupError:
1053 1081 # i18n: column positioning for "hg summary"
1054 1082 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1055 1083 ui.write(msg)
1056 1084 return
1057 1085 numrebased = len([i for i in state.itervalues() if i >= 0])
1058 1086 # i18n: column positioning for "hg summary"
1059 1087 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1060 1088 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1061 1089 ui.label(_('%d remaining'), 'rebase.remaining') %
1062 1090 (len(state) - numrebased)))
1063 1091
1064 1092 def uisetup(ui):
1065 'Replace pull with a decorator to provide --rebase option'
1093 #Replace pull with a decorator to provide --rebase option
1066 1094 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1067 1095 entry[1].append(('', 'rebase', None,
1068 1096 _("rebase working directory to branch head")))
1069 1097 entry[1].append(('t', 'tool', '',
1070 1098 _("specify merge tool for rebase")))
1071 1099 cmdutil.summaryhooks.add('rebase', summaryhook)
1072 1100 cmdutil.unfinishedstates.append(
1073 1101 ['rebasestate', False, False, _('rebase in progress'),
1074 1102 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1103 # ensure rebased rev are not hidden
1104 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
1105
@@ -1,500 +1,542 b''
1 1 ==========================
2 2 Test rebase with obsolete
3 3 ==========================
4 4
5 5 Enable obsolete
6 6
7 7 $ cat >> $HGRCPATH << EOF
8 8 > [ui]
9 9 > logtemplate= {rev}:{node|short} {desc|firstline}
10 10 > [experimental]
11 11 > evolution=createmarkers,allowunstable
12 12 > [phases]
13 13 > publish=False
14 14 > [extensions]'
15 15 > rebase=
16 16 > EOF
17 17
18 18 Setup rebase canonical repo
19 19
20 20 $ hg init base
21 21 $ cd base
22 22 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
23 23 adding changesets
24 24 adding manifests
25 25 adding file changes
26 26 added 8 changesets with 7 changes to 7 files (+2 heads)
27 27 (run 'hg heads' to see heads, 'hg merge' to merge)
28 28 $ hg up tip
29 29 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30 $ hg log -G
31 31 @ 7:02de42196ebe H
32 32 |
33 33 | o 6:eea13746799a G
34 34 |/|
35 35 o | 5:24b6387c8c8c F
36 36 | |
37 37 | o 4:9520eea781bc E
38 38 |/
39 39 | o 3:32af7686d403 D
40 40 | |
41 41 | o 2:5fddd98957c8 C
42 42 | |
43 43 | o 1:42ccdea3bb16 B
44 44 |/
45 45 o 0:cd010b8cd998 A
46 46
47 47 $ cd ..
48 48
49 49 simple rebase
50 50 ---------------------------------
51 51
52 52 $ hg clone base simple
53 53 updating to branch default
54 54 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 55 $ cd simple
56 56 $ hg up 32af7686d403
57 57 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
58 58 $ hg rebase -d eea13746799a
59 59 rebasing 1:42ccdea3bb16 "B"
60 60 rebasing 2:5fddd98957c8 "C"
61 61 rebasing 3:32af7686d403 "D"
62 62 $ hg log -G
63 63 @ 10:8eeb3c33ad33 D
64 64 |
65 65 o 9:2327fea05063 C
66 66 |
67 67 o 8:e4e5be0395b2 B
68 68 |
69 69 | o 7:02de42196ebe H
70 70 | |
71 71 o | 6:eea13746799a G
72 72 |\|
73 73 | o 5:24b6387c8c8c F
74 74 | |
75 75 o | 4:9520eea781bc E
76 76 |/
77 77 o 0:cd010b8cd998 A
78 78
79 79 $ hg log --hidden -G
80 80 @ 10:8eeb3c33ad33 D
81 81 |
82 82 o 9:2327fea05063 C
83 83 |
84 84 o 8:e4e5be0395b2 B
85 85 |
86 86 | o 7:02de42196ebe H
87 87 | |
88 88 o | 6:eea13746799a G
89 89 |\|
90 90 | o 5:24b6387c8c8c F
91 91 | |
92 92 o | 4:9520eea781bc E
93 93 |/
94 94 | x 3:32af7686d403 D
95 95 | |
96 96 | x 2:5fddd98957c8 C
97 97 | |
98 98 | x 1:42ccdea3bb16 B
99 99 |/
100 100 o 0:cd010b8cd998 A
101 101
102 102 $ hg debugobsolete
103 103 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
104 104 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
105 105 32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
106 106
107 107
108 108 $ cd ..
109 109
110 110 empty changeset
111 111 ---------------------------------
112 112
113 113 $ hg clone base empty
114 114 updating to branch default
115 115 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 116 $ cd empty
117 117 $ hg up eea13746799a
118 118 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
119 119
120 120 We make a copy of both the first changeset in the rebased and some other in the
121 121 set.
122 122
123 123 $ hg graft 42ccdea3bb16 32af7686d403
124 124 grafting 1:42ccdea3bb16 "B"
125 125 grafting 3:32af7686d403 "D"
126 126 $ hg rebase -s 42ccdea3bb16 -d .
127 127 rebasing 1:42ccdea3bb16 "B"
128 128 note: rebase of 1:42ccdea3bb16 created no changes to commit
129 129 rebasing 2:5fddd98957c8 "C"
130 130 rebasing 3:32af7686d403 "D"
131 131 note: rebase of 3:32af7686d403 created no changes to commit
132 132 $ hg log -G
133 133 o 10:5ae4c968c6ac C
134 134 |
135 135 @ 9:08483444fef9 D
136 136 |
137 137 o 8:8877864f1edb B
138 138 |
139 139 | o 7:02de42196ebe H
140 140 | |
141 141 o | 6:eea13746799a G
142 142 |\|
143 143 | o 5:24b6387c8c8c F
144 144 | |
145 145 o | 4:9520eea781bc E
146 146 |/
147 147 o 0:cd010b8cd998 A
148 148
149 149 $ hg log --hidden -G
150 150 o 10:5ae4c968c6ac C
151 151 |
152 152 @ 9:08483444fef9 D
153 153 |
154 154 o 8:8877864f1edb B
155 155 |
156 156 | o 7:02de42196ebe H
157 157 | |
158 158 o | 6:eea13746799a G
159 159 |\|
160 160 | o 5:24b6387c8c8c F
161 161 | |
162 162 o | 4:9520eea781bc E
163 163 |/
164 164 | x 3:32af7686d403 D
165 165 | |
166 166 | x 2:5fddd98957c8 C
167 167 | |
168 168 | x 1:42ccdea3bb16 B
169 169 |/
170 170 o 0:cd010b8cd998 A
171 171
172 172 $ hg debugobsolete
173 173 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
174 174 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
175 175 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
176 176
177 177
178 178 More complex case were part of the rebase set were already rebased
179 179
180 180 $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
181 181 rebasing 9:08483444fef9 "D"
182 182 $ hg debugobsolete
183 183 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
184 184 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
185 185 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
186 186 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
187 187 $ hg log -G
188 188 @ 11:4596109a6a43 D
189 189 |
190 190 | o 10:5ae4c968c6ac C
191 191 | |
192 192 | x 9:08483444fef9 D
193 193 | |
194 194 | o 8:8877864f1edb B
195 195 | |
196 196 o | 7:02de42196ebe H
197 197 | |
198 198 | o 6:eea13746799a G
199 199 |/|
200 200 o | 5:24b6387c8c8c F
201 201 | |
202 202 | o 4:9520eea781bc E
203 203 |/
204 204 o 0:cd010b8cd998 A
205 205
206 206 $ hg rebase --source 'desc(B)' --dest 'tip'
207 207 rebasing 8:8877864f1edb "B"
208 208 rebasing 9:08483444fef9 "D"
209 209 note: rebase of 9:08483444fef9 created no changes to commit
210 210 rebasing 10:5ae4c968c6ac "C"
211 211 $ hg debugobsolete
212 212 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
213 213 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
214 214 32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
215 215 08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
216 216 8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
217 217 08483444fef91d6224f6655ee586a65d263ad34c 0 {8877864f1edb05d0e07dc4ba77b67a80a7b86672} (*) {'user': 'test'} (glob)
218 218 5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
219 219 $ hg log --rev 'divergent()'
220 220 $ hg log -G
221 221 o 13:98f6af4ee953 C
222 222 |
223 223 o 12:462a34d07e59 B
224 224 |
225 225 @ 11:4596109a6a43 D
226 226 |
227 227 o 7:02de42196ebe H
228 228 |
229 229 | o 6:eea13746799a G
230 230 |/|
231 231 o | 5:24b6387c8c8c F
232 232 | |
233 233 | o 4:9520eea781bc E
234 234 |/
235 235 o 0:cd010b8cd998 A
236 236
237 237 $ hg log --style default --debug -r 4596109a6a4328c398bde3a4a3b6737cfade3003
238 238 changeset: 11:4596109a6a4328c398bde3a4a3b6737cfade3003
239 239 phase: draft
240 240 parent: 7:02de42196ebee42ef284b6780a87cdc96e8eaab6
241 241 parent: -1:0000000000000000000000000000000000000000
242 242 manifest: 11:a91006e3a02f1edf631f7018e6e5684cf27dd905
243 243 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
244 244 date: Sat Apr 30 15:24:48 2011 +0200
245 245 files+: D
246 246 extra: branch=default
247 247 extra: rebase_source=08483444fef91d6224f6655ee586a65d263ad34c
248 248 extra: source=32af7686d403cf45b5d95f2d70cebea587ac806a
249 249 description:
250 250 D
251 251
252 252
253 253 $ cd ..
254 254
255 255 collapse rebase
256 256 ---------------------------------
257 257
258 258 $ hg clone base collapse
259 259 updating to branch default
260 260 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
261 261 $ cd collapse
262 262 $ hg rebase -s 42ccdea3bb16 -d eea13746799a --collapse
263 263 rebasing 1:42ccdea3bb16 "B"
264 264 note: rebase of 1:42ccdea3bb16 created no changes to commit
265 265 rebasing 2:5fddd98957c8 "C"
266 266 note: rebase of 2:5fddd98957c8 created no changes to commit
267 267 rebasing 3:32af7686d403 "D"
268 268 note: rebase of 3:32af7686d403 created no changes to commit
269 269 $ hg log -G
270 270 o 8:4dc2197e807b Collapsed revision
271 271 |
272 272 | @ 7:02de42196ebe H
273 273 | |
274 274 o | 6:eea13746799a G
275 275 |\|
276 276 | o 5:24b6387c8c8c F
277 277 | |
278 278 o | 4:9520eea781bc E
279 279 |/
280 280 o 0:cd010b8cd998 A
281 281
282 282 $ hg log --hidden -G
283 283 o 8:4dc2197e807b Collapsed revision
284 284 |
285 285 | @ 7:02de42196ebe H
286 286 | |
287 287 o | 6:eea13746799a G
288 288 |\|
289 289 | o 5:24b6387c8c8c F
290 290 | |
291 291 o | 4:9520eea781bc E
292 292 |/
293 293 | x 3:32af7686d403 D
294 294 | |
295 295 | x 2:5fddd98957c8 C
296 296 | |
297 297 | x 1:42ccdea3bb16 B
298 298 |/
299 299 o 0:cd010b8cd998 A
300 300
301 301 $ hg id --debug -r tip
302 302 4dc2197e807bae9817f09905b50ab288be2dbbcf tip
303 303 $ hg debugobsolete
304 304 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
305 305 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
306 306 32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
307 307
308 308 $ cd ..
309 309
310 310 Rebase set has hidden descendants
311 311 ---------------------------------
312 312
313 313 We rebase a changeset which has a hidden changeset. The hidden changeset must
314 314 not be rebased.
315 315
316 316 $ hg clone base hidden
317 317 updating to branch default
318 318 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
319 319 $ cd hidden
320 320 $ hg rebase -s 5fddd98957c8 -d eea13746799a
321 321 rebasing 2:5fddd98957c8 "C"
322 322 rebasing 3:32af7686d403 "D"
323 323 $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
324 324 rebasing 1:42ccdea3bb16 "B"
325 325 $ hg log -G
326 326 o 10:7c6027df6a99 B
327 327 |
328 328 | o 9:cf44d2f5a9f4 D
329 329 | |
330 330 | o 8:e273c5e7d2d2 C
331 331 | |
332 332 @ | 7:02de42196ebe H
333 333 | |
334 334 | o 6:eea13746799a G
335 335 |/|
336 336 o | 5:24b6387c8c8c F
337 337 | |
338 338 | o 4:9520eea781bc E
339 339 |/
340 340 o 0:cd010b8cd998 A
341 341
342 342 $ hg log --hidden -G
343 343 o 10:7c6027df6a99 B
344 344 |
345 345 | o 9:cf44d2f5a9f4 D
346 346 | |
347 347 | o 8:e273c5e7d2d2 C
348 348 | |
349 349 @ | 7:02de42196ebe H
350 350 | |
351 351 | o 6:eea13746799a G
352 352 |/|
353 353 o | 5:24b6387c8c8c F
354 354 | |
355 355 | o 4:9520eea781bc E
356 356 |/
357 357 | x 3:32af7686d403 D
358 358 | |
359 359 | x 2:5fddd98957c8 C
360 360 | |
361 361 | x 1:42ccdea3bb16 B
362 362 |/
363 363 o 0:cd010b8cd998 A
364 364
365 365 $ hg debugobsolete
366 366 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
367 367 32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
368 368 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
369 369
370 370 Test that rewriting leaving instability behind is allowed
371 371 ---------------------------------------------------------------------
372 372
373 373 $ hg log -r 'children(8)'
374 374 9:cf44d2f5a9f4 D (no-eol)
375 375 $ hg rebase -r 8
376 376 rebasing 8:e273c5e7d2d2 "C"
377 377 $ hg log -G
378 378 o 11:0d8f238b634c C
379 379 |
380 380 o 10:7c6027df6a99 B
381 381 |
382 382 | o 9:cf44d2f5a9f4 D
383 383 | |
384 384 | x 8:e273c5e7d2d2 C
385 385 | |
386 386 @ | 7:02de42196ebe H
387 387 | |
388 388 | o 6:eea13746799a G
389 389 |/|
390 390 o | 5:24b6387c8c8c F
391 391 | |
392 392 | o 4:9520eea781bc E
393 393 |/
394 394 o 0:cd010b8cd998 A
395 395
396 396
397 397
398 398 Test multiple root handling
399 399 ------------------------------------
400 400
401 401 $ hg rebase --dest 4 --rev '7+11+9'
402 402 rebasing 7:02de42196ebe "H"
403 403 rebasing 9:cf44d2f5a9f4 "D"
404 404 not rebasing ignored 10:7c6027df6a99 "B"
405 405 rebasing 11:0d8f238b634c "C" (tip)
406 406 $ hg log -G
407 407 o 14:1e8370e38cca C
408 408 |
409 409 | o 13:102b4c1d889b D
410 410 | |
411 411 @ | 12:bfe264faf697 H
412 412 |/
413 413 | o 10:7c6027df6a99 B
414 414 | |
415 415 | x 7:02de42196ebe H
416 416 | |
417 417 +---o 6:eea13746799a G
418 418 | |/
419 419 | o 5:24b6387c8c8c F
420 420 | |
421 421 o | 4:9520eea781bc E
422 422 |/
423 423 o 0:cd010b8cd998 A
424 424
425 425 $ cd ..
426 426
427 427 test on rebase dropping a merge
428 428
429 429 (setup)
430 430
431 431 $ hg init dropmerge
432 432 $ cd dropmerge
433 433 $ hg unbundle "$TESTDIR/bundles/rebase.hg"
434 434 adding changesets
435 435 adding manifests
436 436 adding file changes
437 437 added 8 changesets with 7 changes to 7 files (+2 heads)
438 438 (run 'hg heads' to see heads, 'hg merge' to merge)
439 439 $ hg up 3
440 440 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
441 441 $ hg merge 7
442 442 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 443 (branch merge, don't forget to commit)
444 444 $ hg ci -m 'M'
445 445 $ echo I > I
446 446 $ hg add I
447 447 $ hg ci -m I
448 448 $ hg log -G
449 449 @ 9:4bde274eefcf I
450 450 |
451 451 o 8:53a6a128b2b7 M
452 452 |\
453 453 | o 7:02de42196ebe H
454 454 | |
455 455 | | o 6:eea13746799a G
456 456 | |/|
457 457 | o | 5:24b6387c8c8c F
458 458 | | |
459 459 | | o 4:9520eea781bc E
460 460 | |/
461 461 o | 3:32af7686d403 D
462 462 | |
463 463 o | 2:5fddd98957c8 C
464 464 | |
465 465 o | 1:42ccdea3bb16 B
466 466 |/
467 467 o 0:cd010b8cd998 A
468 468
469 469 (actual test)
470 470
471 471 $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
472 472 rebasing 3:32af7686d403 "D"
473 473 rebasing 7:02de42196ebe "H"
474 474 not rebasing ignored 8:53a6a128b2b7 "M"
475 475 rebasing 9:4bde274eefcf "I" (tip)
476 476 $ hg log -G
477 477 @ 12:acd174b7ab39 I
478 478 |
479 479 o 11:6c11a6218c97 H
480 480 |
481 481 | o 10:b5313c85b22e D
482 482 |/
483 483 | o 8:53a6a128b2b7 M
484 484 | |\
485 485 | | x 7:02de42196ebe H
486 486 | | |
487 487 o---+ 6:eea13746799a G
488 488 | | |
489 489 | | o 5:24b6387c8c8c F
490 490 | | |
491 491 o---+ 4:9520eea781bc E
492 492 / /
493 493 x | 3:32af7686d403 D
494 494 | |
495 495 o | 2:5fddd98957c8 C
496 496 | |
497 497 o | 1:42ccdea3bb16 B
498 498 |/
499 499 o 0:cd010b8cd998 A
500 500
501
502 Test hidden changesets in the rebase set (issue4504)
503
504 $ hg up --hidden 9
505 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
506 $ echo J > J
507 $ hg add J
508 $ hg commit -m J
509 $ hg debugobsolete `hg log --rev . -T '{node}'`
510
511 $ hg rebase --rev .~1::. --dest 'max(desc(D))' --traceback
512 rebasing 9:4bde274eefcf "I"
513 rebasing 13:06edfc82198f "J" (tip)
514 $ hg log -G
515 @ 15:5ae8a643467b J
516 |
517 o 14:9ad579b4a5de I
518 |
519 | o 12:acd174b7ab39 I
520 | |
521 | o 11:6c11a6218c97 H
522 | |
523 o | 10:b5313c85b22e D
524 |/
525 | o 8:53a6a128b2b7 M
526 | |\
527 | | x 7:02de42196ebe H
528 | | |
529 o---+ 6:eea13746799a G
530 | | |
531 | | o 5:24b6387c8c8c F
532 | | |
533 o---+ 4:9520eea781bc E
534 / /
535 x | 3:32af7686d403 D
536 | |
537 o | 2:5fddd98957c8 C
538 | |
539 o | 1:42ccdea3bb16 B
540 |/
541 o 0:cd010b8cd998 A
542
General Comments 0
You need to be logged in to leave comments. Login now