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