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