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