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