##// END OF EJS Templates
rebase: only change self.state when collapsing in _finishrebase...
Jun Wu -
r33864:70354bd4 default
parent child Browse files
Show More
@@ -1,1550 +1,1548
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 448 if rebased not in self.skipped:
449 449 commitmsg += '\n* %s' % repo[rebased].description()
450 450 editopt = True
451 451 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
452 452 revtoreuse = max(self.state)
453 453
454 454 dsguard = None
455 455 if ui.configbool('rebase', 'singletransaction'):
456 456 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
457 457 with util.acceptintervention(dsguard):
458 458 newnode = concludenode(repo, revtoreuse, p1, self.external,
459 459 commitmsg=commitmsg,
460 460 extrafn=_makeextrafn(self.extrafns),
461 461 editor=editor,
462 462 keepbranches=self.keepbranchesf,
463 463 date=self.date)
464 if newnode is None:
465 newrev = self.dest
466 else:
464 if newnode is not None:
467 465 newrev = repo[newnode].rev()
468 for oldrev in self.state.iterkeys():
469 self.state[oldrev] = newrev
466 for oldrev in self.state.iterkeys():
467 self.state[oldrev] = newrev
470 468
471 469 if 'qtip' in repo.tags():
472 470 updatemq(repo, self.state, self.skipped, **opts)
473 471
474 472 # restore original working directory
475 473 # (we do this before stripping)
476 474 newwd = self.state.get(self.originalwd, self.originalwd)
477 475 if newwd < 0:
478 476 # original directory is a parent of rebase set root or ignored
479 477 newwd = self.originalwd
480 478 if newwd not in [c.rev() for c in repo[None].parents()]:
481 479 ui.note(_("update back to initial working directory parent\n"))
482 480 hg.updaterepo(repo, newwd, False)
483 481
484 482 if not self.keepf:
485 483 collapsedas = None
486 484 if self.collapsef:
487 485 collapsedas = newnode
488 486 clearrebased(ui, repo, self.dest, self.state, self.skipped,
489 487 collapsedas)
490 488
491 489 clearstatus(repo)
492 490 clearcollapsemsg(repo)
493 491
494 492 ui.note(_("rebase completed\n"))
495 493 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
496 494 if self.skipped:
497 495 skippedlen = len(self.skipped)
498 496 ui.note(_("%d revisions have been skipped\n") % skippedlen)
499 497
500 498 if (self.activebookmark and self.activebookmark in repo._bookmarks and
501 499 repo['.'].node() == repo._bookmarks[self.activebookmark]):
502 500 bookmarks.activate(repo, self.activebookmark)
503 501
504 502 @command('rebase',
505 503 [('s', 'source', '',
506 504 _('rebase the specified changeset and descendants'), _('REV')),
507 505 ('b', 'base', '',
508 506 _('rebase everything from branching point of specified changeset'),
509 507 _('REV')),
510 508 ('r', 'rev', [],
511 509 _('rebase these revisions'),
512 510 _('REV')),
513 511 ('d', 'dest', '',
514 512 _('rebase onto the specified changeset'), _('REV')),
515 513 ('', 'collapse', False, _('collapse the rebased changesets')),
516 514 ('m', 'message', '',
517 515 _('use text as collapse commit message'), _('TEXT')),
518 516 ('e', 'edit', False, _('invoke editor on commit messages')),
519 517 ('l', 'logfile', '',
520 518 _('read collapse commit message from file'), _('FILE')),
521 519 ('k', 'keep', False, _('keep original changesets')),
522 520 ('', 'keepbranches', False, _('keep original branch names')),
523 521 ('D', 'detach', False, _('(DEPRECATED)')),
524 522 ('i', 'interactive', False, _('(DEPRECATED)')),
525 523 ('t', 'tool', '', _('specify merge tool')),
526 524 ('c', 'continue', False, _('continue an interrupted rebase')),
527 525 ('a', 'abort', False, _('abort an interrupted rebase'))] +
528 526 templateopts,
529 527 _('[-s REV | -b REV] [-d REV] [OPTION]'))
530 528 def rebase(ui, repo, **opts):
531 529 """move changeset (and descendants) to a different branch
532 530
533 531 Rebase uses repeated merging to graft changesets from one part of
534 532 history (the source) onto another (the destination). This can be
535 533 useful for linearizing *local* changes relative to a master
536 534 development tree.
537 535
538 536 Published commits cannot be rebased (see :hg:`help phases`).
539 537 To copy commits, see :hg:`help graft`.
540 538
541 539 If you don't specify a destination changeset (``-d/--dest``), rebase
542 540 will use the same logic as :hg:`merge` to pick a destination. if
543 541 the current branch contains exactly one other head, the other head
544 542 is merged with by default. Otherwise, an explicit revision with
545 543 which to merge with must be provided. (destination changeset is not
546 544 modified by rebasing, but new changesets are added as its
547 545 descendants.)
548 546
549 547 Here are the ways to select changesets:
550 548
551 549 1. Explicitly select them using ``--rev``.
552 550
553 551 2. Use ``--source`` to select a root changeset and include all of its
554 552 descendants.
555 553
556 554 3. Use ``--base`` to select a changeset; rebase will find ancestors
557 555 and their descendants which are not also ancestors of the destination.
558 556
559 557 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
560 558 rebase will use ``--base .`` as above.
561 559
562 560 Rebase will destroy original changesets unless you use ``--keep``.
563 561 It will also move your bookmarks (even if you do).
564 562
565 563 Some changesets may be dropped if they do not contribute changes
566 564 (e.g. merges from the destination branch).
567 565
568 566 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
569 567 a named branch with two heads. You will need to explicitly specify source
570 568 and/or destination.
571 569
572 570 If you need to use a tool to automate merge/conflict decisions, you
573 571 can specify one with ``--tool``, see :hg:`help merge-tools`.
574 572 As a caveat: the tool will not be used to mediate when a file was
575 573 deleted, there is no hook presently available for this.
576 574
577 575 If a rebase is interrupted to manually resolve a conflict, it can be
578 576 continued with --continue/-c or aborted with --abort/-a.
579 577
580 578 .. container:: verbose
581 579
582 580 Examples:
583 581
584 582 - move "local changes" (current commit back to branching point)
585 583 to the current branch tip after a pull::
586 584
587 585 hg rebase
588 586
589 587 - move a single changeset to the stable branch::
590 588
591 589 hg rebase -r 5f493448 -d stable
592 590
593 591 - splice a commit and all its descendants onto another part of history::
594 592
595 593 hg rebase --source c0c3 --dest 4cf9
596 594
597 595 - rebase everything on a branch marked by a bookmark onto the
598 596 default branch::
599 597
600 598 hg rebase --base myfeature --dest default
601 599
602 600 - collapse a sequence of changes into a single commit::
603 601
604 602 hg rebase --collapse -r 1520:1525 -d .
605 603
606 604 - move a named branch while preserving its name::
607 605
608 606 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
609 607
610 608 Configuration Options:
611 609
612 610 You can make rebase require a destination if you set the following config
613 611 option::
614 612
615 613 [commands]
616 614 rebase.requiredest = True
617 615
618 616 By default, rebase will close the transaction after each commit. For
619 617 performance purposes, you can configure rebase to use a single transaction
620 618 across the entire rebase. WARNING: This setting introduces a significant
621 619 risk of losing the work you've done in a rebase if the rebase aborts
622 620 unexpectedly::
623 621
624 622 [rebase]
625 623 singletransaction = True
626 624
627 625 Return Values:
628 626
629 627 Returns 0 on success, 1 if nothing to rebase or there are
630 628 unresolved conflicts.
631 629
632 630 """
633 631 rbsrt = rebaseruntime(repo, ui, opts)
634 632
635 633 with repo.wlock(), repo.lock():
636 634 # Validate input and define rebasing points
637 635 destf = opts.get('dest', None)
638 636 srcf = opts.get('source', None)
639 637 basef = opts.get('base', None)
640 638 revf = opts.get('rev', [])
641 639 # search default destination in this space
642 640 # used in the 'hg pull --rebase' case, see issue 5214.
643 641 destspace = opts.get('_destspace')
644 642 contf = opts.get('continue')
645 643 abortf = opts.get('abort')
646 644 if opts.get('interactive'):
647 645 try:
648 646 if extensions.find('histedit'):
649 647 enablehistedit = ''
650 648 except KeyError:
651 649 enablehistedit = " --config extensions.histedit="
652 650 help = "hg%s help -e histedit" % enablehistedit
653 651 msg = _("interactive history editing is supported by the "
654 652 "'histedit' extension (see \"%s\")") % help
655 653 raise error.Abort(msg)
656 654
657 655 if rbsrt.collapsemsg and not rbsrt.collapsef:
658 656 raise error.Abort(
659 657 _('message can only be specified with collapse'))
660 658
661 659 if contf or abortf:
662 660 if contf and abortf:
663 661 raise error.Abort(_('cannot use both abort and continue'))
664 662 if rbsrt.collapsef:
665 663 raise error.Abort(
666 664 _('cannot use collapse with continue or abort'))
667 665 if srcf or basef or destf:
668 666 raise error.Abort(
669 667 _('abort and continue do not allow specifying revisions'))
670 668 if abortf and opts.get('tool', False):
671 669 ui.warn(_('tool option will be ignored\n'))
672 670 if contf:
673 671 ms = mergemod.mergestate.read(repo)
674 672 mergeutil.checkunresolved(ms)
675 673
676 674 retcode = rbsrt._prepareabortorcontinue(abortf)
677 675 if retcode is not None:
678 676 return retcode
679 677 else:
680 678 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
681 679 destspace=destspace)
682 680 retcode = rbsrt._preparenewrebase(dest, rebaseset)
683 681 if retcode is not None:
684 682 return retcode
685 683
686 684 tr = None
687 685 dsguard = None
688 686
689 687 singletr = ui.configbool('rebase', 'singletransaction')
690 688 if singletr:
691 689 tr = repo.transaction('rebase')
692 690 with util.acceptintervention(tr):
693 691 if singletr:
694 692 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
695 693 with util.acceptintervention(dsguard):
696 694 rbsrt._performrebase(tr)
697 695
698 696 rbsrt._finishrebase()
699 697
700 698 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
701 699 destspace=None):
702 700 """use revisions argument to define destination and rebase set
703 701 """
704 702 if revf is None:
705 703 revf = []
706 704
707 705 # destspace is here to work around issues with `hg pull --rebase` see
708 706 # issue5214 for details
709 707 if srcf and basef:
710 708 raise error.Abort(_('cannot specify both a source and a base'))
711 709 if revf and basef:
712 710 raise error.Abort(_('cannot specify both a revision and a base'))
713 711 if revf and srcf:
714 712 raise error.Abort(_('cannot specify both a revision and a source'))
715 713
716 714 cmdutil.checkunfinished(repo)
717 715 cmdutil.bailifchanged(repo)
718 716
719 717 if ui.configbool('commands', 'rebase.requiredest') and not destf:
720 718 raise error.Abort(_('you must specify a destination'),
721 719 hint=_('use: hg rebase -d REV'))
722 720
723 721 if destf:
724 722 dest = scmutil.revsingle(repo, destf)
725 723
726 724 if revf:
727 725 rebaseset = scmutil.revrange(repo, revf)
728 726 if not rebaseset:
729 727 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
730 728 return None, None
731 729 elif srcf:
732 730 src = scmutil.revrange(repo, [srcf])
733 731 if not src:
734 732 ui.status(_('empty "source" revision set - nothing to rebase\n'))
735 733 return None, None
736 734 rebaseset = repo.revs('(%ld)::', src)
737 735 assert rebaseset
738 736 else:
739 737 base = scmutil.revrange(repo, [basef or '.'])
740 738 if not base:
741 739 ui.status(_('empty "base" revision set - '
742 740 "can't compute rebase set\n"))
743 741 return None, None
744 742 if not destf:
745 743 dest = repo[_destrebase(repo, base, destspace=destspace)]
746 744 destf = str(dest)
747 745
748 746 roots = [] # selected children of branching points
749 747 bpbase = {} # {branchingpoint: [origbase]}
750 748 for b in base: # group bases by branching points
751 749 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
752 750 bpbase[bp] = bpbase.get(bp, []) + [b]
753 751 if None in bpbase:
754 752 # emulate the old behavior, showing "nothing to rebase" (a better
755 753 # behavior may be abort with "cannot find branching point" error)
756 754 bpbase.clear()
757 755 for bp, bs in bpbase.iteritems(): # calculate roots
758 756 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
759 757
760 758 rebaseset = repo.revs('%ld::', roots)
761 759
762 760 if not rebaseset:
763 761 # transform to list because smartsets are not comparable to
764 762 # lists. This should be improved to honor laziness of
765 763 # smartset.
766 764 if list(base) == [dest.rev()]:
767 765 if basef:
768 766 ui.status(_('nothing to rebase - %s is both "base"'
769 767 ' and destination\n') % dest)
770 768 else:
771 769 ui.status(_('nothing to rebase - working directory '
772 770 'parent is also destination\n'))
773 771 elif not repo.revs('%ld - ::%d', base, dest):
774 772 if basef:
775 773 ui.status(_('nothing to rebase - "base" %s is '
776 774 'already an ancestor of destination '
777 775 '%s\n') %
778 776 ('+'.join(str(repo[r]) for r in base),
779 777 dest))
780 778 else:
781 779 ui.status(_('nothing to rebase - working '
782 780 'directory parent is already an '
783 781 'ancestor of destination %s\n') % dest)
784 782 else: # can it happen?
785 783 ui.status(_('nothing to rebase from %s to %s\n') %
786 784 ('+'.join(str(repo[r]) for r in base), dest))
787 785 return None, None
788 786
789 787 if not destf:
790 788 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
791 789 destf = str(dest)
792 790
793 791 return dest, rebaseset
794 792
795 793 def externalparent(repo, state, destancestors):
796 794 """Return the revision that should be used as the second parent
797 795 when the revisions in state is collapsed on top of destancestors.
798 796 Abort if there is more than one parent.
799 797 """
800 798 parents = set()
801 799 source = min(state)
802 800 for rev in state:
803 801 if rev == source:
804 802 continue
805 803 for p in repo[rev].parents():
806 804 if (p.rev() not in state
807 805 and p.rev() not in destancestors):
808 806 parents.add(p.rev())
809 807 if not parents:
810 808 return nullrev
811 809 if len(parents) == 1:
812 810 return parents.pop()
813 811 raise error.Abort(_('unable to collapse on top of %s, there is more '
814 812 'than one external parent: %s') %
815 813 (max(destancestors),
816 814 ', '.join(str(p) for p in sorted(parents))))
817 815
818 816 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
819 817 keepbranches=False, date=None):
820 818 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
821 819 but also store useful information in extra.
822 820 Return node of committed revision.'''
823 821 dsguard = util.nullcontextmanager()
824 822 if not repo.ui.configbool('rebase', 'singletransaction'):
825 823 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
826 824 with dsguard:
827 825 repo.setparents(repo[p1].node(), repo[p2].node())
828 826 ctx = repo[rev]
829 827 if commitmsg is None:
830 828 commitmsg = ctx.description()
831 829 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
832 830 extra = {'rebase_source': ctx.hex()}
833 831 if extrafn:
834 832 extrafn(ctx, extra)
835 833
836 834 destphase = max(ctx.phase(), phases.draft)
837 835 overrides = {('phases', 'new-commit'): destphase}
838 836 with repo.ui.configoverride(overrides, 'rebase'):
839 837 if keepbranch:
840 838 repo.ui.setconfig('ui', 'allowemptycommit', True)
841 839 # Commit might fail if unresolved files exist
842 840 if date is None:
843 841 date = ctx.date()
844 842 newnode = repo.commit(text=commitmsg, user=ctx.user(),
845 843 date=date, extra=extra, editor=editor)
846 844
847 845 repo.dirstate.setbranch(repo[newnode].branch())
848 846 return newnode
849 847
850 848 def rebasenode(repo, rev, p1, base, state, collapse, dest):
851 849 'Rebase a single revision rev on top of p1 using base as merge ancestor'
852 850 # Merge phase
853 851 # Update to destination and merge it with local
854 852 if repo['.'].rev() != p1:
855 853 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
856 854 mergemod.update(repo, p1, False, True)
857 855 else:
858 856 repo.ui.debug(" already in destination\n")
859 857 repo.dirstate.write(repo.currenttransaction())
860 858 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
861 859 if base is not None:
862 860 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
863 861 # When collapsing in-place, the parent is the common ancestor, we
864 862 # have to allow merging with it.
865 863 stats = mergemod.update(repo, rev, True, True, base, collapse,
866 864 labels=['dest', 'source'])
867 865 if collapse:
868 866 copies.duplicatecopies(repo, rev, dest)
869 867 else:
870 868 # If we're not using --collapse, we need to
871 869 # duplicate copies between the revision we're
872 870 # rebasing and its first parent, but *not*
873 871 # duplicate any copies that have already been
874 872 # performed in the destination.
875 873 p1rev = repo[rev].p1().rev()
876 874 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
877 875 return stats
878 876
879 877 def adjustdest(repo, rev, dest, state):
880 878 """adjust rebase destination given the current rebase state
881 879
882 880 rev is what is being rebased. Return a list of two revs, which are the
883 881 adjusted destinations for rev's p1 and p2, respectively. If a parent is
884 882 nullrev, return dest without adjustment for it.
885 883
886 884 For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
887 885 and E's destination will be adjusted from F to B1.
888 886
889 887 B1 <- written during rebasing B
890 888 |
891 889 F <- original destination of B, E
892 890 |
893 891 | E <- rev, which is being rebased
894 892 | |
895 893 | D <- prev, one parent of rev being checked
896 894 | |
897 895 | x <- skipped, ex. no successor or successor in (::dest)
898 896 | |
899 897 | C
900 898 | |
901 899 | B <- rebased as B1
902 900 |/
903 901 A
904 902
905 903 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
906 904 first move C to C1, G to G1, and when it's checking H, the adjusted
907 905 destinations will be [C1, G1].
908 906
909 907 H C1 G1
910 908 /| | /
911 909 F G |/
912 910 K | | -> K
913 911 | C D |
914 912 | |/ |
915 913 | B | ...
916 914 |/ |/
917 915 A A
918 916 """
919 917 # pick already rebased revs from state
920 918 source = [s for s, d in state.items() if d > 0]
921 919
922 920 result = []
923 921 for prev in repo.changelog.parentrevs(rev):
924 922 adjusted = dest
925 923 if prev != nullrev:
926 924 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
927 925 if candidate is not None:
928 926 adjusted = state[candidate]
929 927 result.append(adjusted)
930 928 return result
931 929
932 930 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
933 931 """
934 932 Abort if rebase will create divergence or rebase is noop because of markers
935 933
936 934 `rebaseobsrevs`: set of obsolete revision in source
937 935 `rebaseobsskipped`: set of revisions from source skipped because they have
938 936 successors in destination
939 937 """
940 938 # Obsolete node with successors not in dest leads to divergence
941 939 divergenceok = ui.configbool('experimental',
942 940 'allowdivergence')
943 941 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
944 942
945 943 if divergencebasecandidates and not divergenceok:
946 944 divhashes = (str(repo[r])
947 945 for r in divergencebasecandidates)
948 946 msg = _("this rebase will cause "
949 947 "divergences from: %s")
950 948 h = _("to force the rebase please set "
951 949 "experimental.allowdivergence=True")
952 950 raise error.Abort(msg % (",".join(divhashes),), hint=h)
953 951
954 952 def successorrevs(repo, rev):
955 953 """yield revision numbers for successors of rev"""
956 954 unfi = repo.unfiltered()
957 955 nodemap = unfi.changelog.nodemap
958 956 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
959 957 if s in nodemap:
960 958 yield nodemap[s]
961 959
962 960 def defineparents(repo, rev, dest, state):
963 961 """Return new parents and optionally a merge base for rev being rebased
964 962
965 963 The destination specified by "dest" cannot always be used directly because
966 964 previously rebase result could affect destination. For example,
967 965
968 966 D E rebase -r C+D+E -d B
969 967 |/ C will be rebased to C'
970 968 B C D's new destination will be C' instead of B
971 969 |/ E's new destination will be C' instead of B
972 970 A
973 971
974 972 The new parents of a merge is slightly more complicated. See the comment
975 973 block below.
976 974 """
977 975 cl = repo.changelog
978 976 def isancestor(a, b):
979 977 # take revision numbers instead of nodes
980 978 if a == b:
981 979 return True
982 980 elif a > b:
983 981 return False
984 982 return cl.isancestor(cl.node(a), cl.node(b))
985 983
986 984 oldps = repo.changelog.parentrevs(rev) # old parents
987 985 newps = [nullrev, nullrev] # new parents
988 986 dests = adjustdest(repo, rev, dest, state) # adjusted destinations
989 987 bases = list(oldps) # merge base candidates, initially just old parents
990 988
991 989 if all(r == nullrev for r in oldps[1:]):
992 990 # For non-merge changeset, just move p to adjusted dest as requested.
993 991 newps[0] = dests[0]
994 992 else:
995 993 # For merge changeset, if we move p to dests[i] unconditionally, both
996 994 # parents may change and the end result looks like "the merge loses a
997 995 # parent", which is a surprise. This is a limit because "--dest" only
998 996 # accepts one dest per src.
999 997 #
1000 998 # Therefore, only move p with reasonable conditions (in this order):
1001 999 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1002 1000 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1003 1001 #
1004 1002 # Comparing with adjustdest, the logic here does some additional work:
1005 1003 # 1. decide which parents will not be moved towards dest
1006 1004 # 2. if the above decision is "no", should a parent still be moved
1007 1005 # because it was rebased?
1008 1006 #
1009 1007 # For example:
1010 1008 #
1011 1009 # C # "rebase -r C -d D" is an error since none of the parents
1012 1010 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1013 1011 # A B D # B (using rule "2."), since B will be rebased.
1014 1012 #
1015 1013 # The loop tries to be not rely on the fact that a Mercurial node has
1016 1014 # at most 2 parents.
1017 1015 for i, p in enumerate(oldps):
1018 1016 np = p # new parent
1019 1017 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1020 1018 np = dests[i]
1021 1019 elif p in state and state[p] > 0:
1022 1020 np = state[p]
1023 1021
1024 1022 # "bases" only record "special" merge bases that cannot be
1025 1023 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1026 1024 # For example:
1027 1025 #
1028 1026 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1029 1027 # | C # is B', but merge base for C is B, instead of
1030 1028 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1031 1029 # | B # "state" edges are merged (so there will be an edge from
1032 1030 # |/ # B to B'), the merge base is still ancestor(C, B') in
1033 1031 # A # the merged graph.
1034 1032 #
1035 1033 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1036 1034 # which uses "virtual null merge" to explain this situation.
1037 1035 if isancestor(p, np):
1038 1036 bases[i] = nullrev
1039 1037
1040 1038 # If one parent becomes an ancestor of the other, drop the ancestor
1041 1039 for j, x in enumerate(newps[:i]):
1042 1040 if x == nullrev:
1043 1041 continue
1044 1042 if isancestor(np, x): # CASE-1
1045 1043 np = nullrev
1046 1044 elif isancestor(x, np): # CASE-2
1047 1045 newps[j] = np
1048 1046 np = nullrev
1049 1047 # New parents forming an ancestor relationship does not
1050 1048 # mean the old parents have a similar relationship. Do not
1051 1049 # set bases[x] to nullrev.
1052 1050 bases[j], bases[i] = bases[i], bases[j]
1053 1051
1054 1052 newps[i] = np
1055 1053
1056 1054 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1057 1055 # base. If only p2 changes, merging using unchanged p1 as merge base is
1058 1056 # suboptimal. Therefore swap parents to make the merge sane.
1059 1057 if newps[1] != nullrev and oldps[0] == newps[0]:
1060 1058 assert len(newps) == 2 and len(oldps) == 2
1061 1059 newps.reverse()
1062 1060 bases.reverse()
1063 1061
1064 1062 # No parent change might be an error because we fail to make rev a
1065 1063 # descendent of requested dest. This can happen, for example:
1066 1064 #
1067 1065 # C # rebase -r C -d D
1068 1066 # /| # None of A and B will be changed to D and rebase fails.
1069 1067 # A B D
1070 1068 if set(newps) == set(oldps) and dest not in newps:
1071 1069 raise error.Abort(_('cannot rebase %d:%s without '
1072 1070 'moving at least one of its parents')
1073 1071 % (rev, repo[rev]))
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 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1098 1096 for i, base in enumerate(bases):
1099 1097 if base == nullrev:
1100 1098 continue
1101 1099 # Revisions in the side (not chosen as merge base) branch that
1102 1100 # might contain "surprising" contents
1103 1101 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1104 1102 bases, base, base, dest))
1105 1103
1106 1104 # If those revisions are covered by rebaseset, the result is good.
1107 1105 # A merge in rebaseset would be considered to cover its ancestors.
1108 1106 if siderevs:
1109 1107 rebaseset = [r for r, d in state.items() if d > 0]
1110 1108 merges = [r for r in rebaseset
1111 1109 if cl.parentrevs(r)[1] != nullrev]
1112 1110 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1113 1111 siderevs, merges, rebaseset))
1114 1112
1115 1113 # Choose a merge base that has a minimal number of unwanted revs.
1116 1114 l, i = min((len(revs), i)
1117 1115 for i, revs in enumerate(unwanted) if revs is not None)
1118 1116 base = bases[i]
1119 1117
1120 1118 # newps[0] should match merge base if possible. Currently, if newps[i]
1121 1119 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1122 1120 # the other's ancestor. In that case, it's fine to not swap newps here.
1123 1121 # (see CASE-1 and CASE-2 above)
1124 1122 if i != 0 and newps[i] != nullrev:
1125 1123 newps[0], newps[i] = newps[i], newps[0]
1126 1124
1127 1125 # The merge will include unwanted revisions. Abort now. Revisit this if
1128 1126 # we have a more advanced merge algorithm that handles multiple bases.
1129 1127 if l > 0:
1130 1128 unwanteddesc = _(' or ').join(
1131 1129 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1132 1130 for revs in unwanted if revs is not None))
1133 1131 raise error.Abort(
1134 1132 _('rebasing %d:%s will include unwanted changes from %s')
1135 1133 % (rev, repo[rev], unwanteddesc))
1136 1134
1137 1135 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1138 1136
1139 1137 return newps[0], newps[1], base
1140 1138
1141 1139 def isagitpatch(repo, patchname):
1142 1140 'Return true if the given patch is in git format'
1143 1141 mqpatch = os.path.join(repo.mq.path, patchname)
1144 1142 for line in patch.linereader(file(mqpatch, 'rb')):
1145 1143 if line.startswith('diff --git'):
1146 1144 return True
1147 1145 return False
1148 1146
1149 1147 def updatemq(repo, state, skipped, **opts):
1150 1148 'Update rebased mq patches - finalize and then import them'
1151 1149 mqrebase = {}
1152 1150 mq = repo.mq
1153 1151 original_series = mq.fullseries[:]
1154 1152 skippedpatches = set()
1155 1153
1156 1154 for p in mq.applied:
1157 1155 rev = repo[p.node].rev()
1158 1156 if rev in state:
1159 1157 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1160 1158 (rev, p.name))
1161 1159 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1162 1160 else:
1163 1161 # Applied but not rebased, not sure this should happen
1164 1162 skippedpatches.add(p.name)
1165 1163
1166 1164 if mqrebase:
1167 1165 mq.finish(repo, mqrebase.keys())
1168 1166
1169 1167 # We must start import from the newest revision
1170 1168 for rev in sorted(mqrebase, reverse=True):
1171 1169 if rev not in skipped:
1172 1170 name, isgit = mqrebase[rev]
1173 1171 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1174 1172 (name, state[rev], repo[state[rev]]))
1175 1173 mq.qimport(repo, (), patchname=name, git=isgit,
1176 1174 rev=[str(state[rev])])
1177 1175 else:
1178 1176 # Rebased and skipped
1179 1177 skippedpatches.add(mqrebase[rev][0])
1180 1178
1181 1179 # Patches were either applied and rebased and imported in
1182 1180 # order, applied and removed or unapplied. Discard the removed
1183 1181 # ones while preserving the original series order and guards.
1184 1182 newseries = [s for s in original_series
1185 1183 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1186 1184 mq.fullseries[:] = newseries
1187 1185 mq.seriesdirty = True
1188 1186 mq.savedirty()
1189 1187
1190 1188 def storecollapsemsg(repo, collapsemsg):
1191 1189 'Store the collapse message to allow recovery'
1192 1190 collapsemsg = collapsemsg or ''
1193 1191 f = repo.vfs("last-message.txt", "w")
1194 1192 f.write("%s\n" % collapsemsg)
1195 1193 f.close()
1196 1194
1197 1195 def clearcollapsemsg(repo):
1198 1196 'Remove collapse message file'
1199 1197 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1200 1198
1201 1199 def restorecollapsemsg(repo, isabort):
1202 1200 'Restore previously stored collapse message'
1203 1201 try:
1204 1202 f = repo.vfs("last-message.txt")
1205 1203 collapsemsg = f.readline().strip()
1206 1204 f.close()
1207 1205 except IOError as err:
1208 1206 if err.errno != errno.ENOENT:
1209 1207 raise
1210 1208 if isabort:
1211 1209 # Oh well, just abort like normal
1212 1210 collapsemsg = ''
1213 1211 else:
1214 1212 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1215 1213 return collapsemsg
1216 1214
1217 1215 def clearstatus(repo):
1218 1216 'Remove the status files'
1219 1217 _clearrebasesetvisibiliy(repo)
1220 1218 # Make sure the active transaction won't write the state file
1221 1219 tr = repo.currenttransaction()
1222 1220 if tr:
1223 1221 tr.removefilegenerator('rebasestate')
1224 1222 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1225 1223
1226 1224 def needupdate(repo, state):
1227 1225 '''check whether we should `update --clean` away from a merge, or if
1228 1226 somehow the working dir got forcibly updated, e.g. by older hg'''
1229 1227 parents = [p.rev() for p in repo[None].parents()]
1230 1228
1231 1229 # Are we in a merge state at all?
1232 1230 if len(parents) < 2:
1233 1231 return False
1234 1232
1235 1233 # We should be standing on the first as-of-yet unrebased commit.
1236 1234 firstunrebased = min([old for old, new in state.iteritems()
1237 1235 if new == nullrev])
1238 1236 if firstunrebased in parents:
1239 1237 return True
1240 1238
1241 1239 return False
1242 1240
1243 1241 def abort(repo, originalwd, dest, state, activebookmark=None):
1244 1242 '''Restore the repository to its original state. Additional args:
1245 1243
1246 1244 activebookmark: the name of the bookmark that should be active after the
1247 1245 restore'''
1248 1246
1249 1247 try:
1250 1248 # If the first commits in the rebased set get skipped during the rebase,
1251 1249 # their values within the state mapping will be the dest rev id. The
1252 1250 # dstates list must must not contain the dest rev (issue4896)
1253 1251 dstates = [s for s in state.values() if s >= 0 and s != dest]
1254 1252 immutable = [d for d in dstates if not repo[d].mutable()]
1255 1253 cleanup = True
1256 1254 if immutable:
1257 1255 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1258 1256 % ', '.join(str(repo[r]) for r in immutable),
1259 1257 hint=_("see 'hg help phases' for details"))
1260 1258 cleanup = False
1261 1259
1262 1260 descendants = set()
1263 1261 if dstates:
1264 1262 descendants = set(repo.changelog.descendants(dstates))
1265 1263 if descendants - set(dstates):
1266 1264 repo.ui.warn(_("warning: new changesets detected on destination "
1267 1265 "branch, can't strip\n"))
1268 1266 cleanup = False
1269 1267
1270 1268 if cleanup:
1271 1269 shouldupdate = False
1272 1270 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1273 1271 if rebased:
1274 1272 strippoints = [
1275 1273 c.node() for c in repo.set('roots(%ld)', rebased)]
1276 1274
1277 1275 updateifonnodes = set(rebased)
1278 1276 updateifonnodes.add(dest)
1279 1277 updateifonnodes.add(originalwd)
1280 1278 shouldupdate = repo['.'].rev() in updateifonnodes
1281 1279
1282 1280 # Update away from the rebase if necessary
1283 1281 if shouldupdate or needupdate(repo, state):
1284 1282 mergemod.update(repo, originalwd, False, True)
1285 1283
1286 1284 # Strip from the first rebased revision
1287 1285 if rebased:
1288 1286 # no backup of rebased cset versions needed
1289 1287 repair.strip(repo.ui, repo, strippoints)
1290 1288
1291 1289 if activebookmark and activebookmark in repo._bookmarks:
1292 1290 bookmarks.activate(repo, activebookmark)
1293 1291
1294 1292 finally:
1295 1293 clearstatus(repo)
1296 1294 clearcollapsemsg(repo)
1297 1295 repo.ui.warn(_('rebase aborted\n'))
1298 1296 return 0
1299 1297
1300 1298 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1301 1299 '''Define which revisions are going to be rebased and where
1302 1300
1303 1301 repo: repo
1304 1302 dest: context
1305 1303 rebaseset: set of rev
1306 1304 '''
1307 1305 originalwd = repo['.'].rev()
1308 1306 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
1309 1307
1310 1308 # This check isn't strictly necessary, since mq detects commits over an
1311 1309 # applied patch. But it prevents messing up the working directory when
1312 1310 # a partially completed rebase is blocked by mq.
1313 1311 if 'qtip' in repo.tags() and (dest.node() in
1314 1312 [s.node for s in repo.mq.applied]):
1315 1313 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1316 1314
1317 1315 roots = list(repo.set('roots(%ld)', rebaseset))
1318 1316 if not roots:
1319 1317 raise error.Abort(_('no matching revisions'))
1320 1318 roots.sort()
1321 1319 state = dict.fromkeys(rebaseset, revtodo)
1322 1320 emptyrebase = True
1323 1321 for root in roots:
1324 1322 commonbase = root.ancestor(dest)
1325 1323 if commonbase == root:
1326 1324 raise error.Abort(_('source is ancestor of destination'))
1327 1325 if commonbase == dest:
1328 1326 wctx = repo[None]
1329 1327 if dest == wctx.p1():
1330 1328 # when rebasing to '.', it will use the current wd branch name
1331 1329 samebranch = root.branch() == wctx.branch()
1332 1330 else:
1333 1331 samebranch = root.branch() == dest.branch()
1334 1332 if not collapse and samebranch and dest in root.parents():
1335 1333 # mark the revision as done by setting its new revision
1336 1334 # equal to its old (current) revisions
1337 1335 state[root.rev()] = root.rev()
1338 1336 repo.ui.debug('source is a child of destination\n')
1339 1337 continue
1340 1338
1341 1339 emptyrebase = False
1342 1340 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1343 1341 if emptyrebase:
1344 1342 return None
1345 1343 for rev in sorted(state):
1346 1344 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1347 1345 # if all parents of this revision are done, then so is this revision
1348 1346 if parents and all((state.get(p) == p for p in parents)):
1349 1347 state[rev] = rev
1350 1348 unfi = repo.unfiltered()
1351 1349 for r in obsoletenotrebased:
1352 1350 desc = _ctxdesc(unfi[r])
1353 1351 succ = obsoletenotrebased[r]
1354 1352 if succ is None:
1355 1353 msg = _('note: not rebasing %s, it has no successor\n') % desc
1356 1354 del state[r]
1357 1355 else:
1358 1356 destctx = unfi[succ]
1359 1357 destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
1360 1358 destctx.description().split('\n', 1)[0])
1361 1359 msg = (_('note: not rebasing %s, already in destination as %s\n')
1362 1360 % (desc, destdesc))
1363 1361 del state[r]
1364 1362 repo.ui.status(msg)
1365 1363 return originalwd, dest.rev(), state
1366 1364
1367 1365 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
1368 1366 """dispose of rebased revision at the end of the rebase
1369 1367
1370 1368 If `collapsedas` is not None, the rebase was a collapse whose result if the
1371 1369 `collapsedas` node."""
1372 1370 tonode = repo.changelog.node
1373 1371 # Move bookmark of skipped nodes to destination. This cannot be handled
1374 1372 # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
1375 1373 # and move bookmark backwards.
1376 1374 bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
1377 1375 for rev in skipped
1378 1376 for name in repo.nodebookmarks(tonode(rev))]
1379 1377 if bmchanges:
1380 1378 with repo.transaction('rebase') as tr:
1381 1379 repo._bookmarks.applychanges(repo, tr, bmchanges)
1382 1380 mapping = {}
1383 1381 for rev, newrev in sorted(state.items()):
1384 1382 if newrev >= 0 and newrev != rev:
1385 1383 if rev in skipped:
1386 1384 succs = ()
1387 1385 elif collapsedas is not None:
1388 1386 succs = (collapsedas,)
1389 1387 else:
1390 1388 succs = (tonode(newrev),)
1391 1389 mapping[tonode(rev)] = succs
1392 1390 scmutil.cleanupnodes(repo, mapping, 'rebase')
1393 1391
1394 1392 def pullrebase(orig, ui, repo, *args, **opts):
1395 1393 'Call rebase after pull if the latter has been invoked with --rebase'
1396 1394 ret = None
1397 1395 if opts.get('rebase'):
1398 1396 if ui.configbool('commands', 'rebase.requiredest'):
1399 1397 msg = _('rebase destination required by configuration')
1400 1398 hint = _('use hg pull followed by hg rebase -d DEST')
1401 1399 raise error.Abort(msg, hint=hint)
1402 1400
1403 1401 with repo.wlock(), repo.lock():
1404 1402 if opts.get('update'):
1405 1403 del opts['update']
1406 1404 ui.debug('--update and --rebase are not compatible, ignoring '
1407 1405 'the update flag\n')
1408 1406
1409 1407 cmdutil.checkunfinished(repo)
1410 1408 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1411 1409 'please commit or shelve your changes first'))
1412 1410
1413 1411 revsprepull = len(repo)
1414 1412 origpostincoming = commands.postincoming
1415 1413 def _dummy(*args, **kwargs):
1416 1414 pass
1417 1415 commands.postincoming = _dummy
1418 1416 try:
1419 1417 ret = orig(ui, repo, *args, **opts)
1420 1418 finally:
1421 1419 commands.postincoming = origpostincoming
1422 1420 revspostpull = len(repo)
1423 1421 if revspostpull > revsprepull:
1424 1422 # --rev option from pull conflict with rebase own --rev
1425 1423 # dropping it
1426 1424 if 'rev' in opts:
1427 1425 del opts['rev']
1428 1426 # positional argument from pull conflicts with rebase's own
1429 1427 # --source.
1430 1428 if 'source' in opts:
1431 1429 del opts['source']
1432 1430 # revsprepull is the len of the repo, not revnum of tip.
1433 1431 destspace = list(repo.changelog.revs(start=revsprepull))
1434 1432 opts['_destspace'] = destspace
1435 1433 try:
1436 1434 rebase(ui, repo, **opts)
1437 1435 except error.NoMergeDestAbort:
1438 1436 # we can maybe update instead
1439 1437 rev, _a, _b = destutil.destupdate(repo)
1440 1438 if rev == repo['.'].rev():
1441 1439 ui.status(_('nothing to rebase\n'))
1442 1440 else:
1443 1441 ui.status(_('nothing to rebase - updating instead\n'))
1444 1442 # not passing argument to get the bare update behavior
1445 1443 # with warning and trumpets
1446 1444 commands.update(ui, repo)
1447 1445 else:
1448 1446 if opts.get('tool'):
1449 1447 raise error.Abort(_('--tool can only be used with --rebase'))
1450 1448 ret = orig(ui, repo, *args, **opts)
1451 1449
1452 1450 return ret
1453 1451
1454 1452 def _setrebasesetvisibility(repo, revs):
1455 1453 """store the currently rebased set on the repo object
1456 1454
1457 1455 This is used by another function to prevent rebased revision to because
1458 1456 hidden (see issue4504)"""
1459 1457 repo = repo.unfiltered()
1460 1458 repo._rebaseset = revs
1461 1459 # invalidate cache if visibility changes
1462 1460 hiddens = repo.filteredrevcache.get('visible', set())
1463 1461 if revs & hiddens:
1464 1462 repo.invalidatevolatilesets()
1465 1463
1466 1464 def _clearrebasesetvisibiliy(repo):
1467 1465 """remove rebaseset data from the repo"""
1468 1466 repo = repo.unfiltered()
1469 1467 if '_rebaseset' in vars(repo):
1470 1468 del repo._rebaseset
1471 1469
1472 1470 def _rebasedvisible(orig, repo):
1473 1471 """ensure rebased revs stay visible (see issue4504)"""
1474 1472 blockers = orig(repo)
1475 1473 blockers.update(getattr(repo, '_rebaseset', ()))
1476 1474 return blockers
1477 1475
1478 1476 def _filterobsoleterevs(repo, revs):
1479 1477 """returns a set of the obsolete revisions in revs"""
1480 1478 return set(r for r in revs if repo[r].obsolete())
1481 1479
1482 1480 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1483 1481 """return a mapping obsolete => successor for all obsolete nodes to be
1484 1482 rebased that have a successors in the destination
1485 1483
1486 1484 obsolete => None entries in the mapping indicate nodes with no successor"""
1487 1485 obsoletenotrebased = {}
1488 1486
1489 1487 # Build a mapping successor => obsolete nodes for the obsolete
1490 1488 # nodes to be rebased
1491 1489 allsuccessors = {}
1492 1490 cl = repo.changelog
1493 1491 for r in rebaseobsrevs:
1494 1492 node = cl.node(r)
1495 1493 for s in obsutil.allsuccessors(repo.obsstore, [node]):
1496 1494 try:
1497 1495 allsuccessors[cl.rev(s)] = cl.rev(node)
1498 1496 except LookupError:
1499 1497 pass
1500 1498
1501 1499 if allsuccessors:
1502 1500 # Look for successors of obsolete nodes to be rebased among
1503 1501 # the ancestors of dest
1504 1502 ancs = cl.ancestors([dest],
1505 1503 stoprev=min(allsuccessors),
1506 1504 inclusive=True)
1507 1505 for s in allsuccessors:
1508 1506 if s in ancs:
1509 1507 obsoletenotrebased[allsuccessors[s]] = s
1510 1508 elif (s == allsuccessors[s] and
1511 1509 allsuccessors.values().count(s) == 1):
1512 1510 # plain prune
1513 1511 obsoletenotrebased[s] = None
1514 1512
1515 1513 return obsoletenotrebased
1516 1514
1517 1515 def summaryhook(ui, repo):
1518 1516 if not repo.vfs.exists('rebasestate'):
1519 1517 return
1520 1518 try:
1521 1519 rbsrt = rebaseruntime(repo, ui, {})
1522 1520 rbsrt.restorestatus()
1523 1521 state = rbsrt.state
1524 1522 except error.RepoLookupError:
1525 1523 # i18n: column positioning for "hg summary"
1526 1524 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1527 1525 ui.write(msg)
1528 1526 return
1529 1527 numrebased = len([i for i in state.itervalues() if i >= 0])
1530 1528 # i18n: column positioning for "hg summary"
1531 1529 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1532 1530 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1533 1531 ui.label(_('%d remaining'), 'rebase.remaining') %
1534 1532 (len(state) - numrebased)))
1535 1533
1536 1534 def uisetup(ui):
1537 1535 #Replace pull with a decorator to provide --rebase option
1538 1536 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1539 1537 entry[1].append(('', 'rebase', None,
1540 1538 _("rebase working directory to branch head")))
1541 1539 entry[1].append(('t', 'tool', '',
1542 1540 _("specify merge tool for rebase")))
1543 1541 cmdutil.summaryhooks.add('rebase', summaryhook)
1544 1542 cmdutil.unfinishedstates.append(
1545 1543 ['rebasestate', False, False, _('rebase in progress'),
1546 1544 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1547 1545 cmdutil.afterresolvedstates.append(
1548 1546 ['rebasestate', _('hg rebase --continue')])
1549 1547 # ensure rebased rev are not hidden
1550 1548 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
General Comments 0
You need to be logged in to leave comments. Login now