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