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