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