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