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