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