##// END OF EJS Templates
rebase: show a note for updated mq patches...
Mads Kiilerich -
r23520:de143427 default
parent child Browse files
Show More
@@ -1,1063 +1,1064 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 19 from mercurial import copies
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 # In case of merge, we need to pick the right parent as merge base.
649 649 #
650 650 # Imagine we have:
651 651 # - M: currently rebase revision in this step
652 652 # - A: one parent of M
653 653 # - B: second parent of M
654 654 # - D: destination of this merge step (p1 var)
655 655 #
656 656 # If we are rebasing on D, D is the successors of A or B. The right
657 657 # merge base is the one D succeed to. We pretend it is B for the rest
658 658 # of this comment
659 659 #
660 660 # If we pick B as the base, the merge involves:
661 661 # - changes from B to M (actual changeset payload)
662 662 # - changes from B to D (induced by rebase) as D is a rebased
663 663 # version of B)
664 664 # Which exactly represent the rebase operation.
665 665 #
666 666 # If we pick the A as the base, the merge involves
667 667 # - changes from A to M (actual changeset payload)
668 668 # - changes from A to D (with include changes between unrelated A and B
669 669 # plus changes induced by rebase)
670 670 # Which does not represent anything sensible and creates a lot of
671 671 # conflicts.
672 672 for p in repo[rev].parents():
673 673 if state.get(p.rev()) == p1:
674 674 base = p.rev()
675 675 break
676 676 else: # fallback when base not found
677 677 base = None
678 678
679 679 # Raise because this function is called wrong (see issue 4106)
680 680 raise AssertionError('no base found to rebase on '
681 681 '(defineparents called wrong)')
682 682 return p1, p2, base
683 683
684 684 def isagitpatch(repo, patchname):
685 685 'Return true if the given patch is in git format'
686 686 mqpatch = os.path.join(repo.mq.path, patchname)
687 687 for line in patch.linereader(file(mqpatch, 'rb')):
688 688 if line.startswith('diff --git'):
689 689 return True
690 690 return False
691 691
692 692 def updatemq(repo, state, skipped, **opts):
693 693 'Update rebased mq patches - finalize and then import them'
694 694 mqrebase = {}
695 695 mq = repo.mq
696 696 original_series = mq.fullseries[:]
697 697 skippedpatches = set()
698 698
699 699 for p in mq.applied:
700 700 rev = repo[p.node].rev()
701 701 if rev in state:
702 702 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
703 703 (rev, p.name))
704 704 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
705 705 else:
706 706 # Applied but not rebased, not sure this should happen
707 707 skippedpatches.add(p.name)
708 708
709 709 if mqrebase:
710 710 mq.finish(repo, mqrebase.keys())
711 711
712 712 # We must start import from the newest revision
713 713 for rev in sorted(mqrebase, reverse=True):
714 714 if rev not in skipped:
715 715 name, isgit = mqrebase[rev]
716 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
716 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
717 (name, state[rev], repo[state[rev]]))
717 718 mq.qimport(repo, (), patchname=name, git=isgit,
718 719 rev=[str(state[rev])])
719 720 else:
720 721 # Rebased and skipped
721 722 skippedpatches.add(mqrebase[rev][0])
722 723
723 724 # Patches were either applied and rebased and imported in
724 725 # order, applied and removed or unapplied. Discard the removed
725 726 # ones while preserving the original series order and guards.
726 727 newseries = [s for s in original_series
727 728 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
728 729 mq.fullseries[:] = newseries
729 730 mq.seriesdirty = True
730 731 mq.savedirty()
731 732
732 733 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
733 734 'Move bookmarks to their correct changesets, and delete divergent ones'
734 735 marks = repo._bookmarks
735 736 for k, v in originalbookmarks.iteritems():
736 737 if v in nstate:
737 738 # update the bookmarks for revs that have moved
738 739 marks[k] = nstate[v]
739 740 bookmarks.deletedivergent(repo, [targetnode], k)
740 741
741 742 marks.write()
742 743
743 744 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
744 745 external, activebookmark):
745 746 'Store the current status to allow recovery'
746 747 f = repo.opener("rebasestate", "w")
747 748 f.write(repo[originalwd].hex() + '\n')
748 749 f.write(repo[target].hex() + '\n')
749 750 f.write(repo[external].hex() + '\n')
750 751 f.write('%d\n' % int(collapse))
751 752 f.write('%d\n' % int(keep))
752 753 f.write('%d\n' % int(keepbranches))
753 754 f.write('%s\n' % (activebookmark or ''))
754 755 for d, v in state.iteritems():
755 756 oldrev = repo[d].hex()
756 757 if v >= 0:
757 758 newrev = repo[v].hex()
758 759 elif v == revtodo:
759 760 # To maintain format compatibility, we have to use nullid.
760 761 # Please do remove this special case when upgrading the format.
761 762 newrev = hex(nullid)
762 763 else:
763 764 newrev = v
764 765 f.write("%s:%s\n" % (oldrev, newrev))
765 766 f.close()
766 767 repo.ui.debug('rebase status stored\n')
767 768
768 769 def clearstatus(repo):
769 770 'Remove the status files'
770 771 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
771 772
772 773 def restorestatus(repo):
773 774 'Restore a previously stored status'
774 775 try:
775 776 keepbranches = None
776 777 target = None
777 778 collapse = False
778 779 external = nullrev
779 780 activebookmark = None
780 781 state = {}
781 782 f = repo.opener("rebasestate")
782 783 for i, l in enumerate(f.read().splitlines()):
783 784 if i == 0:
784 785 originalwd = repo[l].rev()
785 786 elif i == 1:
786 787 target = repo[l].rev()
787 788 elif i == 2:
788 789 external = repo[l].rev()
789 790 elif i == 3:
790 791 collapse = bool(int(l))
791 792 elif i == 4:
792 793 keep = bool(int(l))
793 794 elif i == 5:
794 795 keepbranches = bool(int(l))
795 796 elif i == 6 and not (len(l) == 81 and ':' in l):
796 797 # line 6 is a recent addition, so for backwards compatibility
797 798 # check that the line doesn't look like the oldrev:newrev lines
798 799 activebookmark = l
799 800 else:
800 801 oldrev, newrev = l.split(':')
801 802 if newrev in (str(nullmerge), str(revignored)):
802 803 state[repo[oldrev].rev()] = int(newrev)
803 804 elif newrev == nullid:
804 805 state[repo[oldrev].rev()] = revtodo
805 806 # Legacy compat special case
806 807 else:
807 808 state[repo[oldrev].rev()] = repo[newrev].rev()
808 809
809 810 if keepbranches is None:
810 811 raise util.Abort(_('.hg/rebasestate is incomplete'))
811 812
812 813 skipped = set()
813 814 # recompute the set of skipped revs
814 815 if not collapse:
815 816 seen = set([target])
816 817 for old, new in sorted(state.items()):
817 818 if new != revtodo and new in seen:
818 819 skipped.add(old)
819 820 seen.add(new)
820 821 repo.ui.debug('computed skipped revs: %s\n' %
821 822 (' '.join(str(r) for r in sorted(skipped)) or None))
822 823 repo.ui.debug('rebase status resumed\n')
823 824 return (originalwd, target, state, skipped,
824 825 collapse, keep, keepbranches, external, activebookmark)
825 826 except IOError, err:
826 827 if err.errno != errno.ENOENT:
827 828 raise
828 829 raise util.Abort(_('no rebase in progress'))
829 830
830 831 def inrebase(repo, originalwd, state):
831 832 '''check whether the working dir is in an interrupted rebase'''
832 833 parents = [p.rev() for p in repo.parents()]
833 834 if originalwd in parents:
834 835 return True
835 836
836 837 for newrev in state.itervalues():
837 838 if newrev in parents:
838 839 return True
839 840
840 841 return False
841 842
842 843 def abort(repo, originalwd, target, state):
843 844 'Restore the repository to its original state'
844 845 dstates = [s for s in state.values() if s >= 0]
845 846 immutable = [d for d in dstates if not repo[d].mutable()]
846 847 cleanup = True
847 848 if immutable:
848 849 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
849 850 % ', '.join(str(repo[r]) for r in immutable),
850 851 hint=_('see hg help phases for details'))
851 852 cleanup = False
852 853
853 854 descendants = set()
854 855 if dstates:
855 856 descendants = set(repo.changelog.descendants(dstates))
856 857 if descendants - set(dstates):
857 858 repo.ui.warn(_("warning: new changesets detected on target branch, "
858 859 "can't strip\n"))
859 860 cleanup = False
860 861
861 862 if cleanup:
862 863 # Update away from the rebase if necessary
863 864 if inrebase(repo, originalwd, state):
864 865 merge.update(repo, originalwd, False, True, False)
865 866
866 867 # Strip from the first rebased revision
867 868 rebased = filter(lambda x: x >= 0 and x != target, state.values())
868 869 if rebased:
869 870 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
870 871 # no backup of rebased cset versions needed
871 872 repair.strip(repo.ui, repo, strippoints)
872 873
873 874 clearstatus(repo)
874 875 repo.ui.warn(_('rebase aborted\n'))
875 876 return 0
876 877
877 878 def buildstate(repo, dest, rebaseset, collapse):
878 879 '''Define which revisions are going to be rebased and where
879 880
880 881 repo: repo
881 882 dest: context
882 883 rebaseset: set of rev
883 884 '''
884 885
885 886 # This check isn't strictly necessary, since mq detects commits over an
886 887 # applied patch. But it prevents messing up the working directory when
887 888 # a partially completed rebase is blocked by mq.
888 889 if 'qtip' in repo.tags() and (dest.node() in
889 890 [s.node for s in repo.mq.applied]):
890 891 raise util.Abort(_('cannot rebase onto an applied mq patch'))
891 892
892 893 roots = list(repo.set('roots(%ld)', rebaseset))
893 894 if not roots:
894 895 raise util.Abort(_('no matching revisions'))
895 896 roots.sort()
896 897 state = {}
897 898 detachset = set()
898 899 for root in roots:
899 900 commonbase = root.ancestor(dest)
900 901 if commonbase == root:
901 902 raise util.Abort(_('source is ancestor of destination'))
902 903 if commonbase == dest:
903 904 samebranch = root.branch() == dest.branch()
904 905 if not collapse and samebranch and root in dest.children():
905 906 repo.ui.debug('source is a child of destination\n')
906 907 return None
907 908
908 909 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
909 910 state.update(dict.fromkeys(rebaseset, revtodo))
910 911 # Rebase tries to turn <dest> into a parent of <root> while
911 912 # preserving the number of parents of rebased changesets:
912 913 #
913 914 # - A changeset with a single parent will always be rebased as a
914 915 # changeset with a single parent.
915 916 #
916 917 # - A merge will be rebased as merge unless its parents are both
917 918 # ancestors of <dest> or are themselves in the rebased set and
918 919 # pruned while rebased.
919 920 #
920 921 # If one parent of <root> is an ancestor of <dest>, the rebased
921 922 # version of this parent will be <dest>. This is always true with
922 923 # --base option.
923 924 #
924 925 # Otherwise, we need to *replace* the original parents with
925 926 # <dest>. This "detaches" the rebased set from its former location
926 927 # and rebases it onto <dest>. Changes introduced by ancestors of
927 928 # <root> not common with <dest> (the detachset, marked as
928 929 # nullmerge) are "removed" from the rebased changesets.
929 930 #
930 931 # - If <root> has a single parent, set it to <dest>.
931 932 #
932 933 # - If <root> is a merge, we cannot decide which parent to
933 934 # replace, the rebase operation is not clearly defined.
934 935 #
935 936 # The table below sums up this behavior:
936 937 #
937 938 # +------------------+----------------------+-------------------------+
938 939 # | | one parent | merge |
939 940 # +------------------+----------------------+-------------------------+
940 941 # | parent in | new parent is <dest> | parents in ::<dest> are |
941 942 # | ::<dest> | | remapped to <dest> |
942 943 # +------------------+----------------------+-------------------------+
943 944 # | unrelated source | new parent is <dest> | ambiguous, abort |
944 945 # +------------------+----------------------+-------------------------+
945 946 #
946 947 # The actual abort is handled by `defineparents`
947 948 if len(root.parents()) <= 1:
948 949 # ancestors of <root> not ancestors of <dest>
949 950 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
950 951 [root.rev()]))
951 952 for r in detachset:
952 953 if r not in state:
953 954 state[r] = nullmerge
954 955 if len(roots) > 1:
955 956 # If we have multiple roots, we may have "hole" in the rebase set.
956 957 # Rebase roots that descend from those "hole" should not be detached as
957 958 # other root are. We use the special `revignored` to inform rebase that
958 959 # the revision should be ignored but that `defineparents` should search
959 960 # a rebase destination that make sense regarding rebased topology.
960 961 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
961 962 for ignored in set(rebasedomain) - set(rebaseset):
962 963 state[ignored] = revignored
963 964 return repo['.'].rev(), dest.rev(), state
964 965
965 966 def clearrebased(ui, repo, state, skipped, collapsedas=None):
966 967 """dispose of rebased revision at the end of the rebase
967 968
968 969 If `collapsedas` is not None, the rebase was a collapse whose result if the
969 970 `collapsedas` node."""
970 971 if obsolete.isenabled(repo, obsolete.createmarkersopt):
971 972 markers = []
972 973 for rev, newrev in sorted(state.items()):
973 974 if newrev >= 0:
974 975 if rev in skipped:
975 976 succs = ()
976 977 elif collapsedas is not None:
977 978 succs = (repo[collapsedas],)
978 979 else:
979 980 succs = (repo[newrev],)
980 981 markers.append((repo[rev], succs))
981 982 if markers:
982 983 obsolete.createmarkers(repo, markers)
983 984 else:
984 985 rebased = [rev for rev in state if state[rev] > nullmerge]
985 986 if rebased:
986 987 stripped = []
987 988 for root in repo.set('roots(%ld)', rebased):
988 989 if set(repo.changelog.descendants([root.rev()])) - set(state):
989 990 ui.warn(_("warning: new changesets detected "
990 991 "on source branch, not stripping\n"))
991 992 else:
992 993 stripped.append(root.node())
993 994 if stripped:
994 995 # backup the old csets by default
995 996 repair.strip(ui, repo, stripped, "all")
996 997
997 998
998 999 def pullrebase(orig, ui, repo, *args, **opts):
999 1000 'Call rebase after pull if the latter has been invoked with --rebase'
1000 1001 if opts.get('rebase'):
1001 1002 if opts.get('update'):
1002 1003 del opts['update']
1003 1004 ui.debug('--update and --rebase are not compatible, ignoring '
1004 1005 'the update flag\n')
1005 1006
1006 1007 movemarkfrom = repo['.'].node()
1007 1008 revsprepull = len(repo)
1008 1009 origpostincoming = commands.postincoming
1009 1010 def _dummy(*args, **kwargs):
1010 1011 pass
1011 1012 commands.postincoming = _dummy
1012 1013 try:
1013 1014 orig(ui, repo, *args, **opts)
1014 1015 finally:
1015 1016 commands.postincoming = origpostincoming
1016 1017 revspostpull = len(repo)
1017 1018 if revspostpull > revsprepull:
1018 1019 # --rev option from pull conflict with rebase own --rev
1019 1020 # dropping it
1020 1021 if 'rev' in opts:
1021 1022 del opts['rev']
1022 1023 rebase(ui, repo, **opts)
1023 1024 branch = repo[None].branch()
1024 1025 dest = repo[branch].rev()
1025 1026 if dest != repo['.'].rev():
1026 1027 # there was nothing to rebase we force an update
1027 1028 hg.update(repo, dest)
1028 1029 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1029 1030 ui.status(_("updating bookmark %s\n")
1030 1031 % repo._bookmarkcurrent)
1031 1032 else:
1032 1033 if opts.get('tool'):
1033 1034 raise util.Abort(_('--tool can only be used with --rebase'))
1034 1035 orig(ui, repo, *args, **opts)
1035 1036
1036 1037 def summaryhook(ui, repo):
1037 1038 if not os.path.exists(repo.join('rebasestate')):
1038 1039 return
1039 1040 try:
1040 1041 state = restorestatus(repo)[2]
1041 1042 except error.RepoLookupError:
1042 1043 # i18n: column positioning for "hg summary"
1043 1044 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1044 1045 ui.write(msg)
1045 1046 return
1046 1047 numrebased = len([i for i in state.itervalues() if i >= 0])
1047 1048 # i18n: column positioning for "hg summary"
1048 1049 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1049 1050 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1050 1051 ui.label(_('%d remaining'), 'rebase.remaining') %
1051 1052 (len(state) - numrebased)))
1052 1053
1053 1054 def uisetup(ui):
1054 1055 'Replace pull with a decorator to provide --rebase option'
1055 1056 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1056 1057 entry[1].append(('', 'rebase', None,
1057 1058 _("rebase working directory to branch head")))
1058 1059 entry[1].append(('t', 'tool', '',
1059 1060 _("specify merge tool for rebase")))
1060 1061 cmdutil.summaryhooks.add('rebase', summaryhook)
1061 1062 cmdutil.unfinishedstates.append(
1062 1063 ['rebasestate', False, False, _('rebase in progress'),
1063 1064 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
@@ -1,156 +1,175 b''
1 1 This emulates the effects of an hg pull --rebase in which the remote repo
2 2 already has one local mq patch
3 3
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [extensions]
6 6 > rebase=
7 7 > mq=
8 8 >
9 9 > [phases]
10 10 > publish=False
11 11 >
12 12 > [alias]
13 13 > tglog = log -G --template "{rev}: '{desc}' tags: {tags}\n"
14 14 > EOF
15 15
16 16
17 17 $ hg init a
18 18 $ cd a
19 19 $ hg qinit -c
20 20
21 21 $ echo c1 > c1
22 22 $ hg add c1
23 23 $ hg ci -m C1
24 24
25 25 $ echo r1 > r1
26 26 $ hg add r1
27 27 $ hg ci -m R1
28 28
29 29 $ hg up -q 0
30 30
31 31 $ hg qnew p0.patch -d '1 0'
32 32 $ echo p0 > p0
33 33 $ hg add p0
34 34 $ hg qref -m P0
35 35
36 36 $ hg qnew p1.patch -d '2 0'
37 37 $ echo p1 > p1
38 38 $ hg add p1
39 39 $ hg qref -m P1
40 40
41 41 $ hg export qtip > p1.patch
42 42
43 43 $ hg up -q -C 1
44 44
45 45 $ hg import p1.patch
46 46 applying p1.patch
47 47
48 48 $ rm p1.patch
49 49
50 50 $ hg up -q -C qtip
51 51
52 $ hg rebase
52 $ hg rebase -v
53 53 rebasing 2:13a46ce44f60 "P0" (p0.patch qbase)
54 resolving manifests
55 removing p0
56 getting r1
57 resolving manifests
58 getting p0
59 p0
54 60 rebasing 3:148775c71080 "P1" (p1.patch qtip)
61 resolving manifests
55 62 note: rebase of 3:148775c71080 created no changes to commit
63 rebase merging completed
64 updating mq patch p0.patch to 5:9ecc820b1737
65 $TESTTMP/a/.hg/patches/p0.patch
66 2 changesets found
56 67 saved backup bundle to $TESTTMP/a/.hg/strip-backup/13a46ce44f60-backup.hg (glob)
68 2 changesets found
69 adding branch
70 adding changesets
71 adding manifests
72 adding file changes
73 added 2 changesets with 2 changes to 2 files
74 rebase completed
75 1 revisions have been skipped
57 76
58 77 $ hg tglog
59 78 @ 3: 'P0' tags: p0.patch qbase qtip tip
60 79 |
61 80 o 2: 'P1' tags: qparent
62 81 |
63 82 o 1: 'R1' tags:
64 83 |
65 84 o 0: 'C1' tags:
66 85
67 86 $ cd ..
68 87
69 88
70 89 $ hg init b
71 90 $ cd b
72 91 $ hg qinit -c
73 92
74 93 $ for i in r0 r1 r2 r3 r4 r5 r6;
75 94 > do
76 95 > echo $i > $i
77 96 > hg ci -Am $i
78 97 > done
79 98 adding r0
80 99 adding r1
81 100 adding r2
82 101 adding r3
83 102 adding r4
84 103 adding r5
85 104 adding r6
86 105
87 106 $ hg qimport -r 1:tip
88 107
89 108 $ hg up -q 0
90 109
91 110 $ for i in r1 r3 r7 r8;
92 111 > do
93 112 > echo $i > $i
94 113 > hg ci -Am branch2-$i
95 114 > done
96 115 adding r1
97 116 created new head
98 117 adding r3
99 118 adding r7
100 119 adding r8
101 120
102 121 $ echo somethingelse > r4
103 122 $ hg ci -Am branch2-r4
104 123 adding r4
105 124
106 125 $ echo r6 > r6
107 126 $ hg ci -Am branch2-r6
108 127 adding r6
109 128
110 129 $ hg up -q qtip
111 130
112 131 $ HGMERGE=internal:fail hg rebase
113 132 rebasing 1:b4bffa6e4776 "r1" (1.diff qbase)
114 133 note: rebase of 1:b4bffa6e4776 created no changes to commit
115 134 rebasing 2:c0fd129beb01 "r2" (2.diff)
116 135 rebasing 3:6ff5b8feed8e "r3" (3.diff)
117 136 note: rebase of 3:6ff5b8feed8e created no changes to commit
118 137 rebasing 4:094320fec554 "r4" (4.diff)
119 138 unresolved conflicts (see hg resolve, then hg rebase --continue)
120 139 [1]
121 140
122 141 $ HGMERGE=internal:local hg resolve --all
123 142 (no more unresolved files)
124 143
125 144 $ hg rebase --continue
126 145 already rebased 1:b4bffa6e4776 "r1" (1.diff qbase) as 057f55ff8f44
127 146 already rebased 2:c0fd129beb01 "r2" (2.diff) as 1660ab13ce9a
128 147 already rebased 3:6ff5b8feed8e "r3" (3.diff) as 1660ab13ce9a
129 148 rebasing 4:094320fec554 "r4" (4.diff)
130 149 note: rebase of 4:094320fec554 created no changes to commit
131 150 rebasing 5:681a378595ba "r5" (5.diff)
132 151 rebasing 6:512a1f24768b "r6" (6.diff qtip)
133 152 note: rebase of 6:512a1f24768b created no changes to commit
134 153 saved backup bundle to $TESTTMP/b/.hg/strip-backup/b4bffa6e4776-backup.hg (glob)
135 154
136 155 $ hg tglog
137 156 @ 8: 'r5' tags: 5.diff qtip tip
138 157 |
139 158 o 7: 'r2' tags: 2.diff qbase
140 159 |
141 160 o 6: 'branch2-r6' tags: qparent
142 161 |
143 162 o 5: 'branch2-r4' tags:
144 163 |
145 164 o 4: 'branch2-r8' tags:
146 165 |
147 166 o 3: 'branch2-r7' tags:
148 167 |
149 168 o 2: 'branch2-r3' tags:
150 169 |
151 170 o 1: 'branch2-r1' tags:
152 171 |
153 172 o 0: 'r0' tags:
154 173
155 174
156 175 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now