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