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