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