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