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