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