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