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