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