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