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