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