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