##// END OF EJS Templates
rebase: allow destination-free continue and abort (issue5513)
Ryan McElroy -
r31731:b5afec71 default
parent child Browse files
Show More
@@ -1,1519 +1,1517
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 = cmdutil.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.target = None
143 143 self.skipped = set()
144 144 self.targetancestors = 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.target].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 target = 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 target = 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 = set([target])
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()) | set([originalwd]))
254 254
255 255 self.originalwd = originalwd
256 256 self.target = target
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, target):
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 target: 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, target)
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.target,
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.target)
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.target, self.state) = result
336 336 if self.collapsef:
337 337 self.targetancestors = self.repo.changelog.ancestors(
338 338 [self.target],
339 339 inclusive=True)
340 340 self.external = externalparent(self.repo, self.state,
341 341 self.targetancestors)
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.targetancestors:
363 363 self.targetancestors = repo.changelog.ancestors([self.target],
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] == revtodo:
388 388 pos += 1
389 389 ui.status(_('rebasing %s\n') % desc)
390 390 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
391 391 _('changesets'), total)
392 392 p1, p2, base = defineparents(repo, rev, self.target,
393 393 self.state,
394 394 self.targetancestors,
395 395 self.obsoletenotrebased)
396 396 self.storestatus(tr=tr)
397 397 storecollapsemsg(repo, self.collapsemsg)
398 398 if len(repo[None].parents()) == 2:
399 399 repo.ui.debug('resuming interrupted rebase\n')
400 400 else:
401 401 try:
402 402 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
403 403 'rebase')
404 404 stats = rebasenode(repo, rev, p1, base, self.state,
405 405 self.collapsef, self.target)
406 406 if stats and stats[3] > 0:
407 407 raise error.InterventionRequired(
408 408 _('unresolved conflicts (see hg '
409 409 'resolve, then hg rebase --continue)'))
410 410 finally:
411 411 ui.setconfig('ui', 'forcemerge', '', 'rebase')
412 412 if not self.collapsef:
413 413 merging = p2 != nullrev
414 414 editform = cmdutil.mergeeditform(merging, 'rebase')
415 415 editor = cmdutil.getcommiteditor(editform=editform, **opts)
416 416 newnode = concludenode(repo, rev, p1, p2,
417 417 extrafn=_makeextrafn(self.extrafns),
418 418 editor=editor,
419 419 keepbranches=self.keepbranchesf,
420 420 date=self.date)
421 421 else:
422 422 # Skip commit if we are collapsing
423 423 repo.dirstate.beginparentchange()
424 424 repo.setparents(repo[p1].node())
425 425 repo.dirstate.endparentchange()
426 426 newnode = None
427 427 # Update the state
428 428 if newnode is not None:
429 429 self.state[rev] = repo[newnode].rev()
430 430 ui.debug('rebased as %s\n' % short(newnode))
431 431 else:
432 432 if not self.collapsef:
433 433 ui.warn(_('note: rebase of %d:%s created no changes '
434 434 'to commit\n') % (rev, ctx))
435 435 self.skipped.add(rev)
436 436 self.state[rev] = p1
437 437 ui.debug('next revision set to %s\n' % p1)
438 438 elif self.state[rev] == nullmerge:
439 439 ui.debug('ignoring null merge rebase of %s\n' % rev)
440 440 elif self.state[rev] == revignored:
441 441 ui.status(_('not rebasing ignored %s\n') % desc)
442 442 elif self.state[rev] == revprecursor:
443 443 targetctx = repo[self.obsoletenotrebased[rev]]
444 444 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
445 445 targetctx.description().split('\n', 1)[0])
446 446 msg = _('note: not rebasing %s, already in destination as %s\n')
447 447 ui.status(msg % (desc, desctarget))
448 448 elif self.state[rev] == revpruned:
449 449 msg = _('note: not rebasing %s, it has no successor\n')
450 450 ui.status(msg % desc)
451 451 else:
452 452 ui.status(_('already rebased %s as %s\n') %
453 453 (desc, repo[self.state[rev]]))
454 454
455 455 ui.progress(_('rebasing'), None)
456 456 ui.note(_('rebase merging completed\n'))
457 457
458 458 def _finishrebase(self):
459 459 repo, ui, opts = self.repo, self.ui, self.opts
460 460 if self.collapsef and not self.keepopen:
461 461 p1, p2, _base = defineparents(repo, min(self.state),
462 462 self.target, self.state,
463 463 self.targetancestors,
464 464 self.obsoletenotrebased)
465 465 editopt = opts.get('edit')
466 466 editform = 'rebase.collapse'
467 467 if self.collapsemsg:
468 468 commitmsg = self.collapsemsg
469 469 else:
470 470 commitmsg = 'Collapsed revision'
471 471 for rebased in self.state:
472 472 if rebased not in self.skipped and\
473 473 self.state[rebased] > nullmerge:
474 474 commitmsg += '\n* %s' % repo[rebased].description()
475 475 editopt = True
476 476 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
477 477 revtoreuse = max(self.state)
478 478 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
479 479 try:
480 480 newnode = concludenode(repo, revtoreuse, p1, self.external,
481 481 commitmsg=commitmsg,
482 482 extrafn=_makeextrafn(self.extrafns),
483 483 editor=editor,
484 484 keepbranches=self.keepbranchesf,
485 485 date=self.date)
486 486 dsguard.close()
487 487 release(dsguard)
488 488 except error.InterventionRequired:
489 489 dsguard.close()
490 490 release(dsguard)
491 491 raise
492 492 except Exception:
493 493 release(dsguard)
494 494 raise
495 495
496 496 if newnode is None:
497 497 newrev = self.target
498 498 else:
499 499 newrev = repo[newnode].rev()
500 500 for oldrev in self.state.iterkeys():
501 501 if self.state[oldrev] > nullmerge:
502 502 self.state[oldrev] = newrev
503 503
504 504 if 'qtip' in repo.tags():
505 505 updatemq(repo, self.state, self.skipped, **opts)
506 506
507 507 if self.currentbookmarks:
508 508 # Nodeids are needed to reset bookmarks
509 509 nstate = {}
510 510 for k, v in self.state.iteritems():
511 511 if v > nullmerge:
512 512 nstate[repo[k].node()] = repo[v].node()
513 513 elif v == revprecursor:
514 514 succ = self.obsoletenotrebased[k]
515 515 nstate[repo[k].node()] = repo[succ].node()
516 516 # XXX this is the same as dest.node() for the non-continue path --
517 517 # this should probably be cleaned up
518 518 targetnode = repo[self.target].node()
519 519
520 520 # restore original working directory
521 521 # (we do this before stripping)
522 522 newwd = self.state.get(self.originalwd, self.originalwd)
523 523 if newwd == revprecursor:
524 524 newwd = self.obsoletenotrebased[self.originalwd]
525 525 elif newwd < 0:
526 526 # original directory is a parent of rebase set root or ignored
527 527 newwd = self.originalwd
528 528 if newwd not in [c.rev() for c in repo[None].parents()]:
529 529 ui.note(_("update back to initial working directory parent\n"))
530 530 hg.updaterepo(repo, newwd, False)
531 531
532 532 if self.currentbookmarks:
533 533 with repo.transaction('bookmark') as tr:
534 534 updatebookmarks(repo, targetnode, nstate,
535 535 self.currentbookmarks, tr)
536 536 if self.activebookmark not in repo._bookmarks:
537 537 # active bookmark was divergent one and has been deleted
538 538 self.activebookmark = None
539 539
540 540 if not self.keepf:
541 541 collapsedas = None
542 542 if self.collapsef:
543 543 collapsedas = newnode
544 544 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
545 545
546 546 clearstatus(repo)
547 547 clearcollapsemsg(repo)
548 548
549 549 ui.note(_("rebase completed\n"))
550 550 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
551 551 if self.skipped:
552 552 skippedlen = len(self.skipped)
553 553 ui.note(_("%d revisions have been skipped\n") % skippedlen)
554 554
555 555 if (self.activebookmark and
556 556 repo['.'].node() == repo._bookmarks[self.activebookmark]):
557 557 bookmarks.activate(repo, self.activebookmark)
558 558
559 559 @command('rebase',
560 560 [('s', 'source', '',
561 561 _('rebase the specified changeset and descendants'), _('REV')),
562 562 ('b', 'base', '',
563 563 _('rebase everything from branching point of specified changeset'),
564 564 _('REV')),
565 565 ('r', 'rev', [],
566 566 _('rebase these revisions'),
567 567 _('REV')),
568 568 ('d', 'dest', '',
569 569 _('rebase onto the specified changeset'), _('REV')),
570 570 ('', 'collapse', False, _('collapse the rebased changesets')),
571 571 ('m', 'message', '',
572 572 _('use text as collapse commit message'), _('TEXT')),
573 573 ('e', 'edit', False, _('invoke editor on commit messages')),
574 574 ('l', 'logfile', '',
575 575 _('read collapse commit message from file'), _('FILE')),
576 576 ('k', 'keep', False, _('keep original changesets')),
577 577 ('', 'keepbranches', False, _('keep original branch names')),
578 578 ('D', 'detach', False, _('(DEPRECATED)')),
579 579 ('i', 'interactive', False, _('(DEPRECATED)')),
580 580 ('t', 'tool', '', _('specify merge tool')),
581 581 ('c', 'continue', False, _('continue an interrupted rebase')),
582 582 ('a', 'abort', False, _('abort an interrupted rebase'))] +
583 583 templateopts,
584 584 _('[-s REV | -b REV] [-d REV] [OPTION]'))
585 585 def rebase(ui, repo, **opts):
586 586 """move changeset (and descendants) to a different branch
587 587
588 588 Rebase uses repeated merging to graft changesets from one part of
589 589 history (the source) onto another (the destination). This can be
590 590 useful for linearizing *local* changes relative to a master
591 591 development tree.
592 592
593 593 Published commits cannot be rebased (see :hg:`help phases`).
594 594 To copy commits, see :hg:`help graft`.
595 595
596 596 If you don't specify a destination changeset (``-d/--dest``), rebase
597 597 will use the same logic as :hg:`merge` to pick a destination. if
598 598 the current branch contains exactly one other head, the other head
599 599 is merged with by default. Otherwise, an explicit revision with
600 600 which to merge with must be provided. (destination changeset is not
601 601 modified by rebasing, but new changesets are added as its
602 602 descendants.)
603 603
604 604 Here are the ways to select changesets:
605 605
606 606 1. Explicitly select them using ``--rev``.
607 607
608 608 2. Use ``--source`` to select a root changeset and include all of its
609 609 descendants.
610 610
611 611 3. Use ``--base`` to select a changeset; rebase will find ancestors
612 612 and their descendants which are not also ancestors of the destination.
613 613
614 614 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
615 615 rebase will use ``--base .`` as above.
616 616
617 617 Rebase will destroy original changesets unless you use ``--keep``.
618 618 It will also move your bookmarks (even if you do).
619 619
620 620 Some changesets may be dropped if they do not contribute changes
621 621 (e.g. merges from the destination branch).
622 622
623 623 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
624 624 a named branch with two heads. You will need to explicitly specify source
625 625 and/or destination.
626 626
627 627 If you need to use a tool to automate merge/conflict decisions, you
628 628 can specify one with ``--tool``, see :hg:`help merge-tools`.
629 629 As a caveat: the tool will not be used to mediate when a file was
630 630 deleted, there is no hook presently available for this.
631 631
632 632 If a rebase is interrupted to manually resolve a conflict, it can be
633 633 continued with --continue/-c or aborted with --abort/-a.
634 634
635 635 .. container:: verbose
636 636
637 637 Examples:
638 638
639 639 - move "local changes" (current commit back to branching point)
640 640 to the current branch tip after a pull::
641 641
642 642 hg rebase
643 643
644 644 - move a single changeset to the stable branch::
645 645
646 646 hg rebase -r 5f493448 -d stable
647 647
648 648 - splice a commit and all its descendants onto another part of history::
649 649
650 650 hg rebase --source c0c3 --dest 4cf9
651 651
652 652 - rebase everything on a branch marked by a bookmark onto the
653 653 default branch::
654 654
655 655 hg rebase --base myfeature --dest default
656 656
657 657 - collapse a sequence of changes into a single commit::
658 658
659 659 hg rebase --collapse -r 1520:1525 -d .
660 660
661 661 - move a named branch while preserving its name::
662 662
663 663 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
664 664
665 665 Configuration Options:
666 666
667 667 You can make rebase require a destination if you set the following config
668 668 option:
669 669
670 670 [commands]
671 671 rebase.requiredest = False
672 672
673 673 Return Values:
674 674
675 675 Returns 0 on success, 1 if nothing to rebase or there are
676 676 unresolved conflicts.
677 677
678 678 """
679 679 rbsrt = rebaseruntime(repo, ui, opts)
680 680
681 681 lock = wlock = None
682 682 try:
683 683 wlock = repo.wlock()
684 684 lock = repo.lock()
685 685
686 686 # Validate input and define rebasing points
687 687 destf = opts.get('dest', None)
688
689 if ui.configbool('commands', 'rebase.requiredest'):
690 if not destf:
691 raise error.Abort(_('you must specify a destination'),
692 hint=_('use: hg rebase -d REV'))
693
694 688 srcf = opts.get('source', None)
695 689 basef = opts.get('base', None)
696 690 revf = opts.get('rev', [])
697 691 # search default destination in this space
698 692 # used in the 'hg pull --rebase' case, see issue 5214.
699 693 destspace = opts.get('_destspace')
700 694 contf = opts.get('continue')
701 695 abortf = opts.get('abort')
702 696 if opts.get('interactive'):
703 697 try:
704 698 if extensions.find('histedit'):
705 699 enablehistedit = ''
706 700 except KeyError:
707 701 enablehistedit = " --config extensions.histedit="
708 702 help = "hg%s help -e histedit" % enablehistedit
709 703 msg = _("interactive history editing is supported by the "
710 704 "'histedit' extension (see \"%s\")") % help
711 705 raise error.Abort(msg)
712 706
713 707 if rbsrt.collapsemsg and not rbsrt.collapsef:
714 708 raise error.Abort(
715 709 _('message can only be specified with collapse'))
716 710
717 711 if contf or abortf:
718 712 if contf and abortf:
719 713 raise error.Abort(_('cannot use both abort and continue'))
720 714 if rbsrt.collapsef:
721 715 raise error.Abort(
722 716 _('cannot use collapse with continue or abort'))
723 717 if srcf or basef or destf:
724 718 raise error.Abort(
725 719 _('abort and continue do not allow specifying revisions'))
726 720 if abortf and opts.get('tool', False):
727 721 ui.warn(_('tool option will be ignored\n'))
728 722 if contf:
729 723 ms = mergemod.mergestate.read(repo)
730 724 mergeutil.checkunresolved(ms)
731 725
732 726 retcode = rbsrt._prepareabortorcontinue(abortf)
733 727 if retcode is not None:
734 728 return retcode
735 729 else:
736 730 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
737 731 destspace=destspace)
738 732 retcode = rbsrt._preparenewrebase(dest, rebaseset)
739 733 if retcode is not None:
740 734 return retcode
741 735
742 736 with repo.transaction('rebase') as tr:
743 737 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
744 738 try:
745 739 rbsrt._performrebase(tr)
746 740 dsguard.close()
747 741 release(dsguard)
748 742 except error.InterventionRequired:
749 743 dsguard.close()
750 744 release(dsguard)
751 745 tr.close()
752 746 raise
753 747 except Exception:
754 748 release(dsguard)
755 749 raise
756 750 rbsrt._finishrebase()
757 751 finally:
758 752 release(lock, wlock)
759 753
760 754 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
761 755 destspace=None):
762 756 """use revisions argument to define destination and rebase set
763 757 """
764 758 if revf is None:
765 759 revf = []
766 760
767 761 # destspace is here to work around issues with `hg pull --rebase` see
768 762 # issue5214 for details
769 763 if srcf and basef:
770 764 raise error.Abort(_('cannot specify both a source and a base'))
771 765 if revf and basef:
772 766 raise error.Abort(_('cannot specify both a revision and a base'))
773 767 if revf and srcf:
774 768 raise error.Abort(_('cannot specify both a revision and a source'))
775 769
776 770 cmdutil.checkunfinished(repo)
777 771 cmdutil.bailifchanged(repo)
778 772
773 if ui.configbool('commands', 'rebase.requiredest') and not destf:
774 raise error.Abort(_('you must specify a destination'),
775 hint=_('use: hg rebase -d REV'))
776
779 777 if destf:
780 778 dest = scmutil.revsingle(repo, destf)
781 779
782 780 if revf:
783 781 rebaseset = scmutil.revrange(repo, revf)
784 782 if not rebaseset:
785 783 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
786 784 return None, None
787 785 elif srcf:
788 786 src = scmutil.revrange(repo, [srcf])
789 787 if not src:
790 788 ui.status(_('empty "source" revision set - nothing to rebase\n'))
791 789 return None, None
792 790 rebaseset = repo.revs('(%ld)::', src)
793 791 assert rebaseset
794 792 else:
795 793 base = scmutil.revrange(repo, [basef or '.'])
796 794 if not base:
797 795 ui.status(_('empty "base" revision set - '
798 796 "can't compute rebase set\n"))
799 797 return None, None
800 798 if not destf:
801 799 dest = repo[_destrebase(repo, base, destspace=destspace)]
802 800 destf = str(dest)
803 801
804 802 roots = [] # selected children of branching points
805 803 bpbase = {} # {branchingpoint: [origbase]}
806 804 for b in base: # group bases by branching points
807 805 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
808 806 bpbase[bp] = bpbase.get(bp, []) + [b]
809 807 if None in bpbase:
810 808 # emulate the old behavior, showing "nothing to rebase" (a better
811 809 # behavior may be abort with "cannot find branching point" error)
812 810 bpbase.clear()
813 811 for bp, bs in bpbase.iteritems(): # calculate roots
814 812 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
815 813
816 814 rebaseset = repo.revs('%ld::', roots)
817 815
818 816 if not rebaseset:
819 817 # transform to list because smartsets are not comparable to
820 818 # lists. This should be improved to honor laziness of
821 819 # smartset.
822 820 if list(base) == [dest.rev()]:
823 821 if basef:
824 822 ui.status(_('nothing to rebase - %s is both "base"'
825 823 ' and destination\n') % dest)
826 824 else:
827 825 ui.status(_('nothing to rebase - working directory '
828 826 'parent is also destination\n'))
829 827 elif not repo.revs('%ld - ::%d', base, dest):
830 828 if basef:
831 829 ui.status(_('nothing to rebase - "base" %s is '
832 830 'already an ancestor of destination '
833 831 '%s\n') %
834 832 ('+'.join(str(repo[r]) for r in base),
835 833 dest))
836 834 else:
837 835 ui.status(_('nothing to rebase - working '
838 836 'directory parent is already an '
839 837 'ancestor of destination %s\n') % dest)
840 838 else: # can it happen?
841 839 ui.status(_('nothing to rebase from %s to %s\n') %
842 840 ('+'.join(str(repo[r]) for r in base), dest))
843 841 return None, None
844 842
845 843 if not destf:
846 844 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
847 845 destf = str(dest)
848 846
849 847 return dest, rebaseset
850 848
851 849 def externalparent(repo, state, targetancestors):
852 850 """Return the revision that should be used as the second parent
853 851 when the revisions in state is collapsed on top of targetancestors.
854 852 Abort if there is more than one parent.
855 853 """
856 854 parents = set()
857 855 source = min(state)
858 856 for rev in state:
859 857 if rev == source:
860 858 continue
861 859 for p in repo[rev].parents():
862 860 if (p.rev() not in state
863 861 and p.rev() not in targetancestors):
864 862 parents.add(p.rev())
865 863 if not parents:
866 864 return nullrev
867 865 if len(parents) == 1:
868 866 return parents.pop()
869 867 raise error.Abort(_('unable to collapse on top of %s, there is more '
870 868 'than one external parent: %s') %
871 869 (max(targetancestors),
872 870 ', '.join(str(p) for p in sorted(parents))))
873 871
874 872 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
875 873 keepbranches=False, date=None):
876 874 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
877 875 but also store useful information in extra.
878 876 Return node of committed revision.'''
879 877 repo.setparents(repo[p1].node(), repo[p2].node())
880 878 ctx = repo[rev]
881 879 if commitmsg is None:
882 880 commitmsg = ctx.description()
883 881 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
884 882 extra = {'rebase_source': ctx.hex()}
885 883 if extrafn:
886 884 extrafn(ctx, extra)
887 885
888 886 targetphase = max(ctx.phase(), phases.draft)
889 887 overrides = {('phases', 'new-commit'): targetphase}
890 888 with repo.ui.configoverride(overrides, 'rebase'):
891 889 if keepbranch:
892 890 repo.ui.setconfig('ui', 'allowemptycommit', True)
893 891 # Commit might fail if unresolved files exist
894 892 if date is None:
895 893 date = ctx.date()
896 894 newnode = repo.commit(text=commitmsg, user=ctx.user(),
897 895 date=date, extra=extra, editor=editor)
898 896
899 897 repo.dirstate.setbranch(repo[newnode].branch())
900 898 return newnode
901 899
902 900 def rebasenode(repo, rev, p1, base, state, collapse, target):
903 901 'Rebase a single revision rev on top of p1 using base as merge ancestor'
904 902 # Merge phase
905 903 # Update to target and merge it with local
906 904 if repo['.'].rev() != p1:
907 905 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
908 906 mergemod.update(repo, p1, False, True)
909 907 else:
910 908 repo.ui.debug(" already in target\n")
911 909 repo.dirstate.write(repo.currenttransaction())
912 910 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
913 911 if base is not None:
914 912 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
915 913 # When collapsing in-place, the parent is the common ancestor, we
916 914 # have to allow merging with it.
917 915 stats = mergemod.update(repo, rev, True, True, base, collapse,
918 916 labels=['dest', 'source'])
919 917 if collapse:
920 918 copies.duplicatecopies(repo, rev, target)
921 919 else:
922 920 # If we're not using --collapse, we need to
923 921 # duplicate copies between the revision we're
924 922 # rebasing and its first parent, but *not*
925 923 # duplicate any copies that have already been
926 924 # performed in the destination.
927 925 p1rev = repo[rev].p1().rev()
928 926 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
929 927 return stats
930 928
931 929 def nearestrebased(repo, rev, state):
932 930 """return the nearest ancestors of rev in the rebase result"""
933 931 rebased = [r for r in state if state[r] > nullmerge]
934 932 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
935 933 if candidates:
936 934 return state[candidates.first()]
937 935 else:
938 936 return None
939 937
940 938 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
941 939 """
942 940 Abort if rebase will create divergence or rebase is noop because of markers
943 941
944 942 `rebaseobsrevs`: set of obsolete revision in source
945 943 `rebasesetrevs`: set of revisions to be rebased from source
946 944 `rebaseobsskipped`: set of revisions from source skipped because they have
947 945 successors in destination
948 946 """
949 947 # Obsolete node with successors not in dest leads to divergence
950 948 divergenceok = ui.configbool('experimental',
951 949 'allowdivergence')
952 950 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
953 951
954 952 if divergencebasecandidates and not divergenceok:
955 953 divhashes = (str(repo[r])
956 954 for r in divergencebasecandidates)
957 955 msg = _("this rebase will cause "
958 956 "divergences from: %s")
959 957 h = _("to force the rebase please set "
960 958 "experimental.allowdivergence=True")
961 959 raise error.Abort(msg % (",".join(divhashes),), hint=h)
962 960
963 961 def defineparents(repo, rev, target, state, targetancestors,
964 962 obsoletenotrebased):
965 963 'Return the new parent relationship of the revision that will be rebased'
966 964 parents = repo[rev].parents()
967 965 p1 = p2 = nullrev
968 966 rp1 = None
969 967
970 968 p1n = parents[0].rev()
971 969 if p1n in targetancestors:
972 970 p1 = target
973 971 elif p1n in state:
974 972 if state[p1n] == nullmerge:
975 973 p1 = target
976 974 elif state[p1n] in revskipped:
977 975 p1 = nearestrebased(repo, p1n, state)
978 976 if p1 is None:
979 977 p1 = target
980 978 else:
981 979 p1 = state[p1n]
982 980 else: # p1n external
983 981 p1 = target
984 982 p2 = p1n
985 983
986 984 if len(parents) == 2 and parents[1].rev() not in targetancestors:
987 985 p2n = parents[1].rev()
988 986 # interesting second parent
989 987 if p2n in state:
990 988 if p1 == target: # p1n in targetancestors or external
991 989 p1 = state[p2n]
992 990 if p1 == revprecursor:
993 991 rp1 = obsoletenotrebased[p2n]
994 992 elif state[p2n] in revskipped:
995 993 p2 = nearestrebased(repo, p2n, state)
996 994 if p2 is None:
997 995 # no ancestors rebased yet, detach
998 996 p2 = target
999 997 else:
1000 998 p2 = state[p2n]
1001 999 else: # p2n external
1002 1000 if p2 != nullrev: # p1n external too => rev is a merged revision
1003 1001 raise error.Abort(_('cannot use revision %d as base, result '
1004 1002 'would have 3 parents') % rev)
1005 1003 p2 = p2n
1006 1004 repo.ui.debug(" future parents are %d and %d\n" %
1007 1005 (repo[rp1 or p1].rev(), repo[p2].rev()))
1008 1006
1009 1007 if not any(p.rev() in state for p in parents):
1010 1008 # Case (1) root changeset of a non-detaching rebase set.
1011 1009 # Let the merge mechanism find the base itself.
1012 1010 base = None
1013 1011 elif not repo[rev].p2():
1014 1012 # Case (2) detaching the node with a single parent, use this parent
1015 1013 base = repo[rev].p1().rev()
1016 1014 else:
1017 1015 # Assuming there is a p1, this is the case where there also is a p2.
1018 1016 # We are thus rebasing a merge and need to pick the right merge base.
1019 1017 #
1020 1018 # Imagine we have:
1021 1019 # - M: current rebase revision in this step
1022 1020 # - A: one parent of M
1023 1021 # - B: other parent of M
1024 1022 # - D: destination of this merge step (p1 var)
1025 1023 #
1026 1024 # Consider the case where D is a descendant of A or B and the other is
1027 1025 # 'outside'. In this case, the right merge base is the D ancestor.
1028 1026 #
1029 1027 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1030 1028 #
1031 1029 # If we pick B as the base, the merge involves:
1032 1030 # - changes from B to M (actual changeset payload)
1033 1031 # - changes from B to D (induced by rebase) as D is a rebased
1034 1032 # version of B)
1035 1033 # Which exactly represent the rebase operation.
1036 1034 #
1037 1035 # If we pick A as the base, the merge involves:
1038 1036 # - changes from A to M (actual changeset payload)
1039 1037 # - changes from A to D (with include changes between unrelated A and B
1040 1038 # plus changes induced by rebase)
1041 1039 # Which does not represent anything sensible and creates a lot of
1042 1040 # conflicts. A is thus not the right choice - B is.
1043 1041 #
1044 1042 # Note: The base found in this 'proof' is only correct in the specified
1045 1043 # case. This base does not make sense if is not D a descendant of A or B
1046 1044 # or if the other is not parent 'outside' (especially not if the other
1047 1045 # parent has been rebased). The current implementation does not
1048 1046 # make it feasible to consider different cases separately. In these
1049 1047 # other cases we currently just leave it to the user to correctly
1050 1048 # resolve an impossible merge using a wrong ancestor.
1051 1049 #
1052 1050 # xx, p1 could be -4, and both parents could probably be -4...
1053 1051 for p in repo[rev].parents():
1054 1052 if state.get(p.rev()) == p1:
1055 1053 base = p.rev()
1056 1054 break
1057 1055 else: # fallback when base not found
1058 1056 base = None
1059 1057
1060 1058 # Raise because this function is called wrong (see issue 4106)
1061 1059 raise AssertionError('no base found to rebase on '
1062 1060 '(defineparents called wrong)')
1063 1061 return rp1 or p1, p2, base
1064 1062
1065 1063 def isagitpatch(repo, patchname):
1066 1064 'Return true if the given patch is in git format'
1067 1065 mqpatch = os.path.join(repo.mq.path, patchname)
1068 1066 for line in patch.linereader(file(mqpatch, 'rb')):
1069 1067 if line.startswith('diff --git'):
1070 1068 return True
1071 1069 return False
1072 1070
1073 1071 def updatemq(repo, state, skipped, **opts):
1074 1072 'Update rebased mq patches - finalize and then import them'
1075 1073 mqrebase = {}
1076 1074 mq = repo.mq
1077 1075 original_series = mq.fullseries[:]
1078 1076 skippedpatches = set()
1079 1077
1080 1078 for p in mq.applied:
1081 1079 rev = repo[p.node].rev()
1082 1080 if rev in state:
1083 1081 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1084 1082 (rev, p.name))
1085 1083 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1086 1084 else:
1087 1085 # Applied but not rebased, not sure this should happen
1088 1086 skippedpatches.add(p.name)
1089 1087
1090 1088 if mqrebase:
1091 1089 mq.finish(repo, mqrebase.keys())
1092 1090
1093 1091 # We must start import from the newest revision
1094 1092 for rev in sorted(mqrebase, reverse=True):
1095 1093 if rev not in skipped:
1096 1094 name, isgit = mqrebase[rev]
1097 1095 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1098 1096 (name, state[rev], repo[state[rev]]))
1099 1097 mq.qimport(repo, (), patchname=name, git=isgit,
1100 1098 rev=[str(state[rev])])
1101 1099 else:
1102 1100 # Rebased and skipped
1103 1101 skippedpatches.add(mqrebase[rev][0])
1104 1102
1105 1103 # Patches were either applied and rebased and imported in
1106 1104 # order, applied and removed or unapplied. Discard the removed
1107 1105 # ones while preserving the original series order and guards.
1108 1106 newseries = [s for s in original_series
1109 1107 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1110 1108 mq.fullseries[:] = newseries
1111 1109 mq.seriesdirty = True
1112 1110 mq.savedirty()
1113 1111
1114 1112 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1115 1113 'Move bookmarks to their correct changesets, and delete divergent ones'
1116 1114 marks = repo._bookmarks
1117 1115 for k, v in originalbookmarks.iteritems():
1118 1116 if v in nstate:
1119 1117 # update the bookmarks for revs that have moved
1120 1118 marks[k] = nstate[v]
1121 1119 bookmarks.deletedivergent(repo, [targetnode], k)
1122 1120 marks.recordchange(tr)
1123 1121
1124 1122 def storecollapsemsg(repo, collapsemsg):
1125 1123 'Store the collapse message to allow recovery'
1126 1124 collapsemsg = collapsemsg or ''
1127 1125 f = repo.vfs("last-message.txt", "w")
1128 1126 f.write("%s\n" % collapsemsg)
1129 1127 f.close()
1130 1128
1131 1129 def clearcollapsemsg(repo):
1132 1130 'Remove collapse message file'
1133 1131 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1134 1132
1135 1133 def restorecollapsemsg(repo, isabort):
1136 1134 'Restore previously stored collapse message'
1137 1135 try:
1138 1136 f = repo.vfs("last-message.txt")
1139 1137 collapsemsg = f.readline().strip()
1140 1138 f.close()
1141 1139 except IOError as err:
1142 1140 if err.errno != errno.ENOENT:
1143 1141 raise
1144 1142 if isabort:
1145 1143 # Oh well, just abort like normal
1146 1144 collapsemsg = ''
1147 1145 else:
1148 1146 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1149 1147 return collapsemsg
1150 1148
1151 1149 def clearstatus(repo):
1152 1150 'Remove the status files'
1153 1151 _clearrebasesetvisibiliy(repo)
1154 1152 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1155 1153
1156 1154 def needupdate(repo, state):
1157 1155 '''check whether we should `update --clean` away from a merge, or if
1158 1156 somehow the working dir got forcibly updated, e.g. by older hg'''
1159 1157 parents = [p.rev() for p in repo[None].parents()]
1160 1158
1161 1159 # Are we in a merge state at all?
1162 1160 if len(parents) < 2:
1163 1161 return False
1164 1162
1165 1163 # We should be standing on the first as-of-yet unrebased commit.
1166 1164 firstunrebased = min([old for old, new in state.iteritems()
1167 1165 if new == nullrev])
1168 1166 if firstunrebased in parents:
1169 1167 return True
1170 1168
1171 1169 return False
1172 1170
1173 1171 def abort(repo, originalwd, target, state, activebookmark=None):
1174 1172 '''Restore the repository to its original state. Additional args:
1175 1173
1176 1174 activebookmark: the name of the bookmark that should be active after the
1177 1175 restore'''
1178 1176
1179 1177 try:
1180 1178 # If the first commits in the rebased set get skipped during the rebase,
1181 1179 # their values within the state mapping will be the target rev id. The
1182 1180 # dstates list must must not contain the target rev (issue4896)
1183 1181 dstates = [s for s in state.values() if s >= 0 and s != target]
1184 1182 immutable = [d for d in dstates if not repo[d].mutable()]
1185 1183 cleanup = True
1186 1184 if immutable:
1187 1185 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1188 1186 % ', '.join(str(repo[r]) for r in immutable),
1189 1187 hint=_("see 'hg help phases' for details"))
1190 1188 cleanup = False
1191 1189
1192 1190 descendants = set()
1193 1191 if dstates:
1194 1192 descendants = set(repo.changelog.descendants(dstates))
1195 1193 if descendants - set(dstates):
1196 1194 repo.ui.warn(_("warning: new changesets detected on target branch, "
1197 1195 "can't strip\n"))
1198 1196 cleanup = False
1199 1197
1200 1198 if cleanup:
1201 1199 shouldupdate = False
1202 1200 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1203 1201 if rebased:
1204 1202 strippoints = [
1205 1203 c.node() for c in repo.set('roots(%ld)', rebased)]
1206 1204
1207 1205 updateifonnodes = set(rebased)
1208 1206 updateifonnodes.add(target)
1209 1207 updateifonnodes.add(originalwd)
1210 1208 shouldupdate = repo['.'].rev() in updateifonnodes
1211 1209
1212 1210 # Update away from the rebase if necessary
1213 1211 if shouldupdate or needupdate(repo, state):
1214 1212 mergemod.update(repo, originalwd, False, True)
1215 1213
1216 1214 # Strip from the first rebased revision
1217 1215 if rebased:
1218 1216 # no backup of rebased cset versions needed
1219 1217 repair.strip(repo.ui, repo, strippoints)
1220 1218
1221 1219 if activebookmark and activebookmark in repo._bookmarks:
1222 1220 bookmarks.activate(repo, activebookmark)
1223 1221
1224 1222 finally:
1225 1223 clearstatus(repo)
1226 1224 clearcollapsemsg(repo)
1227 1225 repo.ui.warn(_('rebase aborted\n'))
1228 1226 return 0
1229 1227
1230 1228 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1231 1229 '''Define which revisions are going to be rebased and where
1232 1230
1233 1231 repo: repo
1234 1232 dest: context
1235 1233 rebaseset: set of rev
1236 1234 '''
1237 1235 originalwd = repo['.'].rev()
1238 1236 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1239 1237
1240 1238 # This check isn't strictly necessary, since mq detects commits over an
1241 1239 # applied patch. But it prevents messing up the working directory when
1242 1240 # a partially completed rebase is blocked by mq.
1243 1241 if 'qtip' in repo.tags() and (dest.node() in
1244 1242 [s.node for s in repo.mq.applied]):
1245 1243 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1246 1244
1247 1245 roots = list(repo.set('roots(%ld)', rebaseset))
1248 1246 if not roots:
1249 1247 raise error.Abort(_('no matching revisions'))
1250 1248 roots.sort()
1251 1249 state = {}
1252 1250 detachset = set()
1253 1251 for root in roots:
1254 1252 commonbase = root.ancestor(dest)
1255 1253 if commonbase == root:
1256 1254 raise error.Abort(_('source is ancestor of destination'))
1257 1255 if commonbase == dest:
1258 1256 wctx = repo[None]
1259 1257 if dest == wctx.p1():
1260 1258 # when rebasing to '.', it will use the current wd branch name
1261 1259 samebranch = root.branch() == wctx.branch()
1262 1260 else:
1263 1261 samebranch = root.branch() == dest.branch()
1264 1262 if not collapse and samebranch and root in dest.children():
1265 1263 repo.ui.debug('source is a child of destination\n')
1266 1264 return None
1267 1265
1268 1266 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1269 1267 state.update(dict.fromkeys(rebaseset, revtodo))
1270 1268 # Rebase tries to turn <dest> into a parent of <root> while
1271 1269 # preserving the number of parents of rebased changesets:
1272 1270 #
1273 1271 # - A changeset with a single parent will always be rebased as a
1274 1272 # changeset with a single parent.
1275 1273 #
1276 1274 # - A merge will be rebased as merge unless its parents are both
1277 1275 # ancestors of <dest> or are themselves in the rebased set and
1278 1276 # pruned while rebased.
1279 1277 #
1280 1278 # If one parent of <root> is an ancestor of <dest>, the rebased
1281 1279 # version of this parent will be <dest>. This is always true with
1282 1280 # --base option.
1283 1281 #
1284 1282 # Otherwise, we need to *replace* the original parents with
1285 1283 # <dest>. This "detaches" the rebased set from its former location
1286 1284 # and rebases it onto <dest>. Changes introduced by ancestors of
1287 1285 # <root> not common with <dest> (the detachset, marked as
1288 1286 # nullmerge) are "removed" from the rebased changesets.
1289 1287 #
1290 1288 # - If <root> has a single parent, set it to <dest>.
1291 1289 #
1292 1290 # - If <root> is a merge, we cannot decide which parent to
1293 1291 # replace, the rebase operation is not clearly defined.
1294 1292 #
1295 1293 # The table below sums up this behavior:
1296 1294 #
1297 1295 # +------------------+----------------------+-------------------------+
1298 1296 # | | one parent | merge |
1299 1297 # +------------------+----------------------+-------------------------+
1300 1298 # | parent in | new parent is <dest> | parents in ::<dest> are |
1301 1299 # | ::<dest> | | remapped to <dest> |
1302 1300 # +------------------+----------------------+-------------------------+
1303 1301 # | unrelated source | new parent is <dest> | ambiguous, abort |
1304 1302 # +------------------+----------------------+-------------------------+
1305 1303 #
1306 1304 # The actual abort is handled by `defineparents`
1307 1305 if len(root.parents()) <= 1:
1308 1306 # ancestors of <root> not ancestors of <dest>
1309 1307 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1310 1308 [root.rev()]))
1311 1309 for r in detachset:
1312 1310 if r not in state:
1313 1311 state[r] = nullmerge
1314 1312 if len(roots) > 1:
1315 1313 # If we have multiple roots, we may have "hole" in the rebase set.
1316 1314 # Rebase roots that descend from those "hole" should not be detached as
1317 1315 # other root are. We use the special `revignored` to inform rebase that
1318 1316 # the revision should be ignored but that `defineparents` should search
1319 1317 # a rebase destination that make sense regarding rebased topology.
1320 1318 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1321 1319 for ignored in set(rebasedomain) - set(rebaseset):
1322 1320 state[ignored] = revignored
1323 1321 for r in obsoletenotrebased:
1324 1322 if obsoletenotrebased[r] is None:
1325 1323 state[r] = revpruned
1326 1324 else:
1327 1325 state[r] = revprecursor
1328 1326 return originalwd, dest.rev(), state
1329 1327
1330 1328 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1331 1329 """dispose of rebased revision at the end of the rebase
1332 1330
1333 1331 If `collapsedas` is not None, the rebase was a collapse whose result if the
1334 1332 `collapsedas` node."""
1335 1333 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1336 1334 markers = []
1337 1335 for rev, newrev in sorted(state.items()):
1338 1336 if newrev >= 0:
1339 1337 if rev in skipped:
1340 1338 succs = ()
1341 1339 elif collapsedas is not None:
1342 1340 succs = (repo[collapsedas],)
1343 1341 else:
1344 1342 succs = (repo[newrev],)
1345 1343 markers.append((repo[rev], succs))
1346 1344 if markers:
1347 1345 obsolete.createmarkers(repo, markers)
1348 1346 else:
1349 1347 rebased = [rev for rev in state if state[rev] > nullmerge]
1350 1348 if rebased:
1351 1349 stripped = []
1352 1350 for root in repo.set('roots(%ld)', rebased):
1353 1351 if set(repo.changelog.descendants([root.rev()])) - set(state):
1354 1352 ui.warn(_("warning: new changesets detected "
1355 1353 "on source branch, not stripping\n"))
1356 1354 else:
1357 1355 stripped.append(root.node())
1358 1356 if stripped:
1359 1357 # backup the old csets by default
1360 1358 repair.strip(ui, repo, stripped, "all")
1361 1359
1362 1360
1363 1361 def pullrebase(orig, ui, repo, *args, **opts):
1364 1362 'Call rebase after pull if the latter has been invoked with --rebase'
1365 1363 ret = None
1366 1364 if opts.get('rebase'):
1367 1365 wlock = lock = None
1368 1366 try:
1369 1367 wlock = repo.wlock()
1370 1368 lock = repo.lock()
1371 1369 if opts.get('update'):
1372 1370 del opts['update']
1373 1371 ui.debug('--update and --rebase are not compatible, ignoring '
1374 1372 'the update flag\n')
1375 1373
1376 1374 cmdutil.checkunfinished(repo)
1377 1375 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1378 1376 'please commit or shelve your changes first'))
1379 1377
1380 1378 revsprepull = len(repo)
1381 1379 origpostincoming = commands.postincoming
1382 1380 def _dummy(*args, **kwargs):
1383 1381 pass
1384 1382 commands.postincoming = _dummy
1385 1383 try:
1386 1384 ret = orig(ui, repo, *args, **opts)
1387 1385 finally:
1388 1386 commands.postincoming = origpostincoming
1389 1387 revspostpull = len(repo)
1390 1388 if revspostpull > revsprepull:
1391 1389 # --rev option from pull conflict with rebase own --rev
1392 1390 # dropping it
1393 1391 if 'rev' in opts:
1394 1392 del opts['rev']
1395 1393 # positional argument from pull conflicts with rebase's own
1396 1394 # --source.
1397 1395 if 'source' in opts:
1398 1396 del opts['source']
1399 1397 # revsprepull is the len of the repo, not revnum of tip.
1400 1398 destspace = list(repo.changelog.revs(start=revsprepull))
1401 1399 opts['_destspace'] = destspace
1402 1400 try:
1403 1401 rebase(ui, repo, **opts)
1404 1402 except error.NoMergeDestAbort:
1405 1403 # we can maybe update instead
1406 1404 rev, _a, _b = destutil.destupdate(repo)
1407 1405 if rev == repo['.'].rev():
1408 1406 ui.status(_('nothing to rebase\n'))
1409 1407 else:
1410 1408 ui.status(_('nothing to rebase - updating instead\n'))
1411 1409 # not passing argument to get the bare update behavior
1412 1410 # with warning and trumpets
1413 1411 commands.update(ui, repo)
1414 1412 finally:
1415 1413 release(lock, wlock)
1416 1414 else:
1417 1415 if opts.get('tool'):
1418 1416 raise error.Abort(_('--tool can only be used with --rebase'))
1419 1417 ret = orig(ui, repo, *args, **opts)
1420 1418
1421 1419 return ret
1422 1420
1423 1421 def _setrebasesetvisibility(repo, revs):
1424 1422 """store the currently rebased set on the repo object
1425 1423
1426 1424 This is used by another function to prevent rebased revision to because
1427 1425 hidden (see issue4504)"""
1428 1426 repo = repo.unfiltered()
1429 1427 repo._rebaseset = revs
1430 1428 # invalidate cache if visibility changes
1431 1429 hiddens = repo.filteredrevcache.get('visible', set())
1432 1430 if revs & hiddens:
1433 1431 repo.invalidatevolatilesets()
1434 1432
1435 1433 def _clearrebasesetvisibiliy(repo):
1436 1434 """remove rebaseset data from the repo"""
1437 1435 repo = repo.unfiltered()
1438 1436 if '_rebaseset' in vars(repo):
1439 1437 del repo._rebaseset
1440 1438
1441 1439 def _rebasedvisible(orig, repo):
1442 1440 """ensure rebased revs stay visible (see issue4504)"""
1443 1441 blockers = orig(repo)
1444 1442 blockers.update(getattr(repo, '_rebaseset', ()))
1445 1443 return blockers
1446 1444
1447 1445 def _filterobsoleterevs(repo, revs):
1448 1446 """returns a set of the obsolete revisions in revs"""
1449 1447 return set(r for r in revs if repo[r].obsolete())
1450 1448
1451 1449 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1452 1450 """return a mapping obsolete => successor for all obsolete nodes to be
1453 1451 rebased that have a successors in the destination
1454 1452
1455 1453 obsolete => None entries in the mapping indicate nodes with no successor"""
1456 1454 obsoletenotrebased = {}
1457 1455
1458 1456 # Build a mapping successor => obsolete nodes for the obsolete
1459 1457 # nodes to be rebased
1460 1458 allsuccessors = {}
1461 1459 cl = repo.changelog
1462 1460 for r in rebaseobsrevs:
1463 1461 node = cl.node(r)
1464 1462 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1465 1463 try:
1466 1464 allsuccessors[cl.rev(s)] = cl.rev(node)
1467 1465 except LookupError:
1468 1466 pass
1469 1467
1470 1468 if allsuccessors:
1471 1469 # Look for successors of obsolete nodes to be rebased among
1472 1470 # the ancestors of dest
1473 1471 ancs = cl.ancestors([repo[dest].rev()],
1474 1472 stoprev=min(allsuccessors),
1475 1473 inclusive=True)
1476 1474 for s in allsuccessors:
1477 1475 if s in ancs:
1478 1476 obsoletenotrebased[allsuccessors[s]] = s
1479 1477 elif (s == allsuccessors[s] and
1480 1478 allsuccessors.values().count(s) == 1):
1481 1479 # plain prune
1482 1480 obsoletenotrebased[s] = None
1483 1481
1484 1482 return obsoletenotrebased
1485 1483
1486 1484 def summaryhook(ui, repo):
1487 1485 if not repo.vfs.exists('rebasestate'):
1488 1486 return
1489 1487 try:
1490 1488 rbsrt = rebaseruntime(repo, ui, {})
1491 1489 rbsrt.restorestatus()
1492 1490 state = rbsrt.state
1493 1491 except error.RepoLookupError:
1494 1492 # i18n: column positioning for "hg summary"
1495 1493 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1496 1494 ui.write(msg)
1497 1495 return
1498 1496 numrebased = len([i for i in state.itervalues() if i >= 0])
1499 1497 # i18n: column positioning for "hg summary"
1500 1498 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1501 1499 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1502 1500 ui.label(_('%d remaining'), 'rebase.remaining') %
1503 1501 (len(state) - numrebased)))
1504 1502
1505 1503 def uisetup(ui):
1506 1504 #Replace pull with a decorator to provide --rebase option
1507 1505 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1508 1506 entry[1].append(('', 'rebase', None,
1509 1507 _("rebase working directory to branch head")))
1510 1508 entry[1].append(('t', 'tool', '',
1511 1509 _("specify merge tool for rebase")))
1512 1510 cmdutil.summaryhooks.add('rebase', summaryhook)
1513 1511 cmdutil.unfinishedstates.append(
1514 1512 ['rebasestate', False, False, _('rebase in progress'),
1515 1513 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1516 1514 cmdutil.afterresolvedstates.append(
1517 1515 ['rebasestate', _('hg rebase --continue')])
1518 1516 # ensure rebased rev are not hidden
1519 1517 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
@@ -1,60 +1,59
1 1 Require a destination
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > rebase =
5 5 > [commands]
6 6 > rebase.requiredest = True
7 7 > EOF
8 8 $ hg init repo
9 9 $ cd repo
10 10 $ echo a >> a
11 11 $ hg commit -qAm aa
12 12 $ echo b >> b
13 13 $ hg commit -qAm bb
14 14 $ hg up ".^"
15 15 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
16 16 $ echo c >> c
17 17 $ hg commit -qAm cc
18 18 $ hg rebase
19 19 abort: you must specify a destination
20 20 (use: hg rebase -d REV)
21 21 [255]
22 22 $ hg rebase -d 1
23 23 rebasing 2:5db65b93a12b "cc" (tip)
24 24 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5db65b93a12b-4fb789ec-backup.hg (glob)
25 25 $ hg rebase -d 0 -r . -q
26 26 $ HGPLAIN=1 hg rebase
27 27 rebasing 2:889b0bc6a730 "cc" (tip)
28 28 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/889b0bc6a730-41ec4f81-backup.hg (glob)
29 29 $ hg rebase -d 0 -r . -q
30 30 $ hg --config commands.rebase.requiredest=False rebase
31 31 rebasing 2:279de9495438 "cc" (tip)
32 32 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/279de9495438-ab0a5128-backup.hg (glob)
33 33
34 34 Requiring dest should not break continue or other rebase options
35 35 $ hg up 1 -q
36 36 $ echo d >> c
37 37 $ hg commit -qAm dc
38 38 $ hg log -G -T '{rev} {desc}'
39 39 @ 3 dc
40 40 |
41 41 | o 2 cc
42 42 |/
43 43 o 1 bb
44 44 |
45 45 o 0 aa
46 46
47 47 $ hg rebase -d 2
48 48 rebasing 3:0537f6b50def "dc" (tip)
49 49 merging c
50 50 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
51 51 unresolved conflicts (see hg resolve, then hg rebase --continue)
52 52 [1]
53 53 $ echo d > c
54 54 $ hg resolve --mark --all
55 55 (no more unresolved files)
56 56 continue: hg rebase --continue
57 57 $ hg rebase --continue
58 abort: you must specify a destination
59 (use: hg rebase -d REV)
60 [255]
58 rebasing 3:0537f6b50def "dc" (tip)
59 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/0537f6b50def-be4c7386-backup.hg (glob)
General Comments 0
You need to be logged in to leave comments. Login now