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