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