##// END OF EJS Templates
rebase: extract dryrun as a function...
Sushil khanchi -
r38515:c92fdc27 default
parent child Browse files
Show More
@@ -1,1881 +1,1884 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from __future__ import absolute_import
18 18
19 19 import errno
20 20 import os
21 21
22 22 from mercurial.i18n import _
23 23 from mercurial.node import (
24 24 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 rbsrt = rebaseruntime(repo, ui, inmemory=True,
822 opts=pycompat.byteskwargs(opts))
823 with repo.wlock(), repo.lock():
824 try:
825 overrides = {('rebase', 'singletransaction'): True}
826 with ui.configoverride(overrides, 'rebase'):
827 _origrebase(ui, repo, inmemory=True, rbsrt=rbsrt,
828 leaveunfinished=True, **opts)
829 except error.InMemoryMergeConflictsError:
830 ui.status(_('hit a merge conflict\n'))
831 return 1
832 else:
833 ui.status(_('there will be no conflict, you can rebase\n'))
834 return 0
835 finally:
836 rbsrt._prepareabortorcontinue(isabort=True)
821 return _dryrunrebase(ui, repo, **opts)
837 822 elif inmemory:
838 823 try:
839 824 # in-memory merge doesn't support conflicts, so if we hit any, abort
840 825 # and re-run as an on-disk merge.
841 826 overrides = {('rebase', 'singletransaction'): True}
842 827 with ui.configoverride(overrides, 'rebase'):
843 828 return _origrebase(ui, repo, inmemory=inmemory, **opts)
844 829 except error.InMemoryMergeConflictsError:
845 830 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
846 831 ' merge\n'))
847 832 _origrebase(ui, repo, abort=True)
848 833 return _origrebase(ui, repo, inmemory=False, **opts)
849 834 else:
850 835 return _origrebase(ui, repo, **opts)
851 836
837 def _dryrunrebase(ui, repo, **opts):
838 rbsrt = rebaseruntime(repo, ui, inmemory=True,
839 opts=pycompat.byteskwargs(opts))
840 with repo.wlock(), repo.lock():
841 try:
842 overrides = {('rebase', 'singletransaction'): True}
843 with ui.configoverride(overrides, 'rebase'):
844 _origrebase(ui, repo, inmemory=True, rbsrt=rbsrt,
845 leaveunfinished=True, **opts)
846 except error.InMemoryMergeConflictsError:
847 ui.status(_('hit a merge conflict\n'))
848 return 1
849 else:
850 ui.status(_('there will be no conflict, you can rebase\n'))
851 return 0
852 finally:
853 rbsrt._prepareabortorcontinue(isabort=True)
854
852 855 def _origrebase(ui, repo, inmemory=False, leaveunfinished=False, rbsrt=None,
853 856 **opts):
854 857 opts = pycompat.byteskwargs(opts)
855 858 if not rbsrt:
856 859 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
857 860
858 861 with repo.wlock(), repo.lock():
859 862 # Validate input and define rebasing points
860 863 destf = opts.get('dest', None)
861 864 srcf = opts.get('source', None)
862 865 basef = opts.get('base', None)
863 866 revf = opts.get('rev', [])
864 867 # search default destination in this space
865 868 # used in the 'hg pull --rebase' case, see issue 5214.
866 869 destspace = opts.get('_destspace')
867 870 contf = opts.get('continue')
868 871 abortf = opts.get('abort')
869 872 if opts.get('interactive'):
870 873 try:
871 874 if extensions.find('histedit'):
872 875 enablehistedit = ''
873 876 except KeyError:
874 877 enablehistedit = " --config extensions.histedit="
875 878 help = "hg%s help -e histedit" % enablehistedit
876 879 msg = _("interactive history editing is supported by the "
877 880 "'histedit' extension (see \"%s\")") % help
878 881 raise error.Abort(msg)
879 882
880 883 if rbsrt.collapsemsg and not rbsrt.collapsef:
881 884 raise error.Abort(
882 885 _('message can only be specified with collapse'))
883 886
884 887 if contf or abortf:
885 888 if contf and abortf:
886 889 raise error.Abort(_('cannot use both abort and continue'))
887 890 if rbsrt.collapsef:
888 891 raise error.Abort(
889 892 _('cannot use collapse with continue or abort'))
890 893 if srcf or basef or destf:
891 894 raise error.Abort(
892 895 _('abort and continue do not allow specifying revisions'))
893 896 if abortf and opts.get('tool', False):
894 897 ui.warn(_('tool option will be ignored\n'))
895 898 if contf:
896 899 ms = mergemod.mergestate.read(repo)
897 900 mergeutil.checkunresolved(ms)
898 901
899 902 retcode = rbsrt._prepareabortorcontinue(abortf)
900 903 if retcode is not None:
901 904 return retcode
902 905 else:
903 906 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
904 907 revf, destspace=destspace)
905 908 retcode = rbsrt._preparenewrebase(destmap)
906 909 if retcode is not None:
907 910 return retcode
908 911 storecollapsemsg(repo, rbsrt.collapsemsg)
909 912
910 913 tr = None
911 914
912 915 singletr = ui.configbool('rebase', 'singletransaction')
913 916 if singletr:
914 917 tr = repo.transaction('rebase')
915 918
916 919 # If `rebase.singletransaction` is enabled, wrap the entire operation in
917 920 # one transaction here. Otherwise, transactions are obtained when
918 921 # committing each node, which is slower but allows partial success.
919 922 with util.acceptintervention(tr):
920 923 # Same logic for the dirstate guard, except we don't create one when
921 924 # rebasing in-memory (it's not needed).
922 925 dsguard = None
923 926 if singletr and not inmemory:
924 927 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
925 928 with util.acceptintervention(dsguard):
926 929 rbsrt._performrebase(tr)
927 930 if not leaveunfinished:
928 931 rbsrt._finishrebase()
929 932
930 933 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
931 934 revf=None, destspace=None):
932 935 """use revisions argument to define destmap {srcrev: destrev}"""
933 936 if revf is None:
934 937 revf = []
935 938
936 939 # destspace is here to work around issues with `hg pull --rebase` see
937 940 # issue5214 for details
938 941 if srcf and basef:
939 942 raise error.Abort(_('cannot specify both a source and a base'))
940 943 if revf and basef:
941 944 raise error.Abort(_('cannot specify both a revision and a base'))
942 945 if revf and srcf:
943 946 raise error.Abort(_('cannot specify both a revision and a source'))
944 947
945 948 if not inmemory:
946 949 cmdutil.checkunfinished(repo)
947 950 cmdutil.bailifchanged(repo)
948 951
949 952 if ui.configbool('commands', 'rebase.requiredest') and not destf:
950 953 raise error.Abort(_('you must specify a destination'),
951 954 hint=_('use: hg rebase -d REV'))
952 955
953 956 dest = None
954 957
955 958 if revf:
956 959 rebaseset = scmutil.revrange(repo, revf)
957 960 if not rebaseset:
958 961 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
959 962 return None
960 963 elif srcf:
961 964 src = scmutil.revrange(repo, [srcf])
962 965 if not src:
963 966 ui.status(_('empty "source" revision set - nothing to rebase\n'))
964 967 return None
965 968 rebaseset = repo.revs('(%ld)::', src)
966 969 assert rebaseset
967 970 else:
968 971 base = scmutil.revrange(repo, [basef or '.'])
969 972 if not base:
970 973 ui.status(_('empty "base" revision set - '
971 974 "can't compute rebase set\n"))
972 975 return None
973 976 if destf:
974 977 # --base does not support multiple destinations
975 978 dest = scmutil.revsingle(repo, destf)
976 979 else:
977 980 dest = repo[_destrebase(repo, base, destspace=destspace)]
978 981 destf = bytes(dest)
979 982
980 983 roots = [] # selected children of branching points
981 984 bpbase = {} # {branchingpoint: [origbase]}
982 985 for b in base: # group bases by branching points
983 986 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
984 987 bpbase[bp] = bpbase.get(bp, []) + [b]
985 988 if None in bpbase:
986 989 # emulate the old behavior, showing "nothing to rebase" (a better
987 990 # behavior may be abort with "cannot find branching point" error)
988 991 bpbase.clear()
989 992 for bp, bs in bpbase.iteritems(): # calculate roots
990 993 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
991 994
992 995 rebaseset = repo.revs('%ld::', roots)
993 996
994 997 if not rebaseset:
995 998 # transform to list because smartsets are not comparable to
996 999 # lists. This should be improved to honor laziness of
997 1000 # smartset.
998 1001 if list(base) == [dest.rev()]:
999 1002 if basef:
1000 1003 ui.status(_('nothing to rebase - %s is both "base"'
1001 1004 ' and destination\n') % dest)
1002 1005 else:
1003 1006 ui.status(_('nothing to rebase - working directory '
1004 1007 'parent is also destination\n'))
1005 1008 elif not repo.revs('%ld - ::%d', base, dest.rev()):
1006 1009 if basef:
1007 1010 ui.status(_('nothing to rebase - "base" %s is '
1008 1011 'already an ancestor of destination '
1009 1012 '%s\n') %
1010 1013 ('+'.join(bytes(repo[r]) for r in base),
1011 1014 dest))
1012 1015 else:
1013 1016 ui.status(_('nothing to rebase - working '
1014 1017 'directory parent is already an '
1015 1018 'ancestor of destination %s\n') % dest)
1016 1019 else: # can it happen?
1017 1020 ui.status(_('nothing to rebase from %s to %s\n') %
1018 1021 ('+'.join(bytes(repo[r]) for r in base), dest))
1019 1022 return None
1020 1023
1021 1024 rebasingwcp = repo['.'].rev() in rebaseset
1022 1025 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1023 1026 if inmemory and rebasingwcp:
1024 1027 # Check these since we did not before.
1025 1028 cmdutil.checkunfinished(repo)
1026 1029 cmdutil.bailifchanged(repo)
1027 1030
1028 1031 if not destf:
1029 1032 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1030 1033 destf = bytes(dest)
1031 1034
1032 1035 allsrc = revsetlang.formatspec('%ld', rebaseset)
1033 1036 alias = {'ALLSRC': allsrc}
1034 1037
1035 1038 if dest is None:
1036 1039 try:
1037 1040 # fast path: try to resolve dest without SRC alias
1038 1041 dest = scmutil.revsingle(repo, destf, localalias=alias)
1039 1042 except error.RepoLookupError:
1040 1043 # multi-dest path: resolve dest for each SRC separately
1041 1044 destmap = {}
1042 1045 for r in rebaseset:
1043 1046 alias['SRC'] = revsetlang.formatspec('%d', r)
1044 1047 # use repo.anyrevs instead of scmutil.revsingle because we
1045 1048 # don't want to abort if destset is empty.
1046 1049 destset = repo.anyrevs([destf], user=True, localalias=alias)
1047 1050 size = len(destset)
1048 1051 if size == 1:
1049 1052 destmap[r] = destset.first()
1050 1053 elif size == 0:
1051 1054 ui.note(_('skipping %s - empty destination\n') % repo[r])
1052 1055 else:
1053 1056 raise error.Abort(_('rebase destination for %s is not '
1054 1057 'unique') % repo[r])
1055 1058
1056 1059 if dest is not None:
1057 1060 # single-dest case: assign dest to each rev in rebaseset
1058 1061 destrev = dest.rev()
1059 1062 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1060 1063
1061 1064 if not destmap:
1062 1065 ui.status(_('nothing to rebase - empty destination\n'))
1063 1066 return None
1064 1067
1065 1068 return destmap
1066 1069
1067 1070 def externalparent(repo, state, destancestors):
1068 1071 """Return the revision that should be used as the second parent
1069 1072 when the revisions in state is collapsed on top of destancestors.
1070 1073 Abort if there is more than one parent.
1071 1074 """
1072 1075 parents = set()
1073 1076 source = min(state)
1074 1077 for rev in state:
1075 1078 if rev == source:
1076 1079 continue
1077 1080 for p in repo[rev].parents():
1078 1081 if (p.rev() not in state
1079 1082 and p.rev() not in destancestors):
1080 1083 parents.add(p.rev())
1081 1084 if not parents:
1082 1085 return nullrev
1083 1086 if len(parents) == 1:
1084 1087 return parents.pop()
1085 1088 raise error.Abort(_('unable to collapse on top of %d, there is more '
1086 1089 'than one external parent: %s') %
1087 1090 (max(destancestors),
1088 1091 ', '.join("%d" % p for p in sorted(parents))))
1089 1092
1090 1093 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1091 1094 '''Commit the memory changes with parents p1 and p2.
1092 1095 Return node of committed revision.'''
1093 1096 # Replicates the empty check in ``repo.commit``.
1094 1097 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1095 1098 return None
1096 1099
1097 1100 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1098 1101 # ``branch`` (used when passing ``--keepbranches``).
1099 1102 branch = repo[p1].branch()
1100 1103 if 'branch' in extra:
1101 1104 branch = extra['branch']
1102 1105
1103 1106 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1104 1107 extra=extra, user=user, branch=branch, editor=editor)
1105 1108 commitres = repo.commitctx(memctx)
1106 1109 wctx.clean() # Might be reused
1107 1110 return commitres
1108 1111
1109 1112 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1110 1113 '''Commit the wd changes with parents p1 and p2.
1111 1114 Return node of committed revision.'''
1112 1115 dsguard = util.nullcontextmanager()
1113 1116 if not repo.ui.configbool('rebase', 'singletransaction'):
1114 1117 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1115 1118 with dsguard:
1116 1119 repo.setparents(repo[p1].node(), repo[p2].node())
1117 1120
1118 1121 # Commit might fail if unresolved files exist
1119 1122 newnode = repo.commit(text=commitmsg, user=user, date=date,
1120 1123 extra=extra, editor=editor)
1121 1124
1122 1125 repo.dirstate.setbranch(repo[newnode].branch())
1123 1126 return newnode
1124 1127
1125 1128 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1126 1129 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1127 1130 # Merge phase
1128 1131 # Update to destination and merge it with local
1129 1132 if wctx.isinmemory():
1130 1133 wctx.setbase(repo[p1])
1131 1134 else:
1132 1135 if repo['.'].rev() != p1:
1133 1136 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1134 1137 mergemod.update(repo, p1, False, True)
1135 1138 else:
1136 1139 repo.ui.debug(" already in destination\n")
1137 1140 # This is, alas, necessary to invalidate workingctx's manifest cache,
1138 1141 # as well as other data we litter on it in other places.
1139 1142 wctx = repo[None]
1140 1143 repo.dirstate.write(repo.currenttransaction())
1141 1144 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1142 1145 if base is not None:
1143 1146 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1144 1147 # When collapsing in-place, the parent is the common ancestor, we
1145 1148 # have to allow merging with it.
1146 1149 stats = mergemod.update(repo, rev, True, True, base, collapse,
1147 1150 labels=['dest', 'source'], wc=wctx)
1148 1151 if collapse:
1149 1152 copies.duplicatecopies(repo, wctx, rev, dest)
1150 1153 else:
1151 1154 # If we're not using --collapse, we need to
1152 1155 # duplicate copies between the revision we're
1153 1156 # rebasing and its first parent, but *not*
1154 1157 # duplicate any copies that have already been
1155 1158 # performed in the destination.
1156 1159 p1rev = repo[rev].p1().rev()
1157 1160 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1158 1161 return stats
1159 1162
1160 1163 def adjustdest(repo, rev, destmap, state, skipped):
1161 1164 """adjust rebase destination given the current rebase state
1162 1165
1163 1166 rev is what is being rebased. Return a list of two revs, which are the
1164 1167 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1165 1168 nullrev, return dest without adjustment for it.
1166 1169
1167 1170 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1168 1171 to B1, and E's destination will be adjusted from F to B1.
1169 1172
1170 1173 B1 <- written during rebasing B
1171 1174 |
1172 1175 F <- original destination of B, E
1173 1176 |
1174 1177 | E <- rev, which is being rebased
1175 1178 | |
1176 1179 | D <- prev, one parent of rev being checked
1177 1180 | |
1178 1181 | x <- skipped, ex. no successor or successor in (::dest)
1179 1182 | |
1180 1183 | C <- rebased as C', different destination
1181 1184 | |
1182 1185 | B <- rebased as B1 C'
1183 1186 |/ |
1184 1187 A G <- destination of C, different
1185 1188
1186 1189 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1187 1190 first move C to C1, G to G1, and when it's checking H, the adjusted
1188 1191 destinations will be [C1, G1].
1189 1192
1190 1193 H C1 G1
1191 1194 /| | /
1192 1195 F G |/
1193 1196 K | | -> K
1194 1197 | C D |
1195 1198 | |/ |
1196 1199 | B | ...
1197 1200 |/ |/
1198 1201 A A
1199 1202
1200 1203 Besides, adjust dest according to existing rebase information. For example,
1201 1204
1202 1205 B C D B needs to be rebased on top of C, C needs to be rebased on top
1203 1206 \|/ of D. We will rebase C first.
1204 1207 A
1205 1208
1206 1209 C' After rebasing C, when considering B's destination, use C'
1207 1210 | instead of the original C.
1208 1211 B D
1209 1212 \ /
1210 1213 A
1211 1214 """
1212 1215 # pick already rebased revs with same dest from state as interesting source
1213 1216 dest = destmap[rev]
1214 1217 source = [s for s, d in state.items()
1215 1218 if d > 0 and destmap[s] == dest and s not in skipped]
1216 1219
1217 1220 result = []
1218 1221 for prev in repo.changelog.parentrevs(rev):
1219 1222 adjusted = dest
1220 1223 if prev != nullrev:
1221 1224 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1222 1225 if candidate is not None:
1223 1226 adjusted = state[candidate]
1224 1227 if adjusted == dest and dest in state:
1225 1228 adjusted = state[dest]
1226 1229 if adjusted == revtodo:
1227 1230 # sortsource should produce an order that makes this impossible
1228 1231 raise error.ProgrammingError(
1229 1232 'rev %d should be rebased already at this time' % dest)
1230 1233 result.append(adjusted)
1231 1234 return result
1232 1235
1233 1236 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1234 1237 """
1235 1238 Abort if rebase will create divergence or rebase is noop because of markers
1236 1239
1237 1240 `rebaseobsrevs`: set of obsolete revision in source
1238 1241 `rebaseobsskipped`: set of revisions from source skipped because they have
1239 1242 successors in destination or no non-obsolete successor.
1240 1243 """
1241 1244 # Obsolete node with successors not in dest leads to divergence
1242 1245 divergenceok = ui.configbool('experimental',
1243 1246 'evolution.allowdivergence')
1244 1247 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1245 1248
1246 1249 if divergencebasecandidates and not divergenceok:
1247 1250 divhashes = (bytes(repo[r])
1248 1251 for r in divergencebasecandidates)
1249 1252 msg = _("this rebase will cause "
1250 1253 "divergences from: %s")
1251 1254 h = _("to force the rebase please set "
1252 1255 "experimental.evolution.allowdivergence=True")
1253 1256 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1254 1257
1255 1258 def successorrevs(unfi, rev):
1256 1259 """yield revision numbers for successors of rev"""
1257 1260 assert unfi.filtername is None
1258 1261 nodemap = unfi.changelog.nodemap
1259 1262 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1260 1263 if s in nodemap:
1261 1264 yield nodemap[s]
1262 1265
1263 1266 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1264 1267 """Return new parents and optionally a merge base for rev being rebased
1265 1268
1266 1269 The destination specified by "dest" cannot always be used directly because
1267 1270 previously rebase result could affect destination. For example,
1268 1271
1269 1272 D E rebase -r C+D+E -d B
1270 1273 |/ C will be rebased to C'
1271 1274 B C D's new destination will be C' instead of B
1272 1275 |/ E's new destination will be C' instead of B
1273 1276 A
1274 1277
1275 1278 The new parents of a merge is slightly more complicated. See the comment
1276 1279 block below.
1277 1280 """
1278 1281 # use unfiltered changelog since successorrevs may return filtered nodes
1279 1282 assert repo.filtername is None
1280 1283 cl = repo.changelog
1281 1284 def isancestor(a, b):
1282 1285 # take revision numbers instead of nodes
1283 1286 if a == b:
1284 1287 return True
1285 1288 elif a > b:
1286 1289 return False
1287 1290 return cl.isancestor(cl.node(a), cl.node(b))
1288 1291
1289 1292 dest = destmap[rev]
1290 1293 oldps = repo.changelog.parentrevs(rev) # old parents
1291 1294 newps = [nullrev, nullrev] # new parents
1292 1295 dests = adjustdest(repo, rev, destmap, state, skipped)
1293 1296 bases = list(oldps) # merge base candidates, initially just old parents
1294 1297
1295 1298 if all(r == nullrev for r in oldps[1:]):
1296 1299 # For non-merge changeset, just move p to adjusted dest as requested.
1297 1300 newps[0] = dests[0]
1298 1301 else:
1299 1302 # For merge changeset, if we move p to dests[i] unconditionally, both
1300 1303 # parents may change and the end result looks like "the merge loses a
1301 1304 # parent", which is a surprise. This is a limit because "--dest" only
1302 1305 # accepts one dest per src.
1303 1306 #
1304 1307 # Therefore, only move p with reasonable conditions (in this order):
1305 1308 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1306 1309 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1307 1310 #
1308 1311 # Comparing with adjustdest, the logic here does some additional work:
1309 1312 # 1. decide which parents will not be moved towards dest
1310 1313 # 2. if the above decision is "no", should a parent still be moved
1311 1314 # because it was rebased?
1312 1315 #
1313 1316 # For example:
1314 1317 #
1315 1318 # C # "rebase -r C -d D" is an error since none of the parents
1316 1319 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1317 1320 # A B D # B (using rule "2."), since B will be rebased.
1318 1321 #
1319 1322 # The loop tries to be not rely on the fact that a Mercurial node has
1320 1323 # at most 2 parents.
1321 1324 for i, p in enumerate(oldps):
1322 1325 np = p # new parent
1323 1326 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1324 1327 np = dests[i]
1325 1328 elif p in state and state[p] > 0:
1326 1329 np = state[p]
1327 1330
1328 1331 # "bases" only record "special" merge bases that cannot be
1329 1332 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1330 1333 # For example:
1331 1334 #
1332 1335 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1333 1336 # | C # is B', but merge base for C is B, instead of
1334 1337 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1335 1338 # | B # "state" edges are merged (so there will be an edge from
1336 1339 # |/ # B to B'), the merge base is still ancestor(C, B') in
1337 1340 # A # the merged graph.
1338 1341 #
1339 1342 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1340 1343 # which uses "virtual null merge" to explain this situation.
1341 1344 if isancestor(p, np):
1342 1345 bases[i] = nullrev
1343 1346
1344 1347 # If one parent becomes an ancestor of the other, drop the ancestor
1345 1348 for j, x in enumerate(newps[:i]):
1346 1349 if x == nullrev:
1347 1350 continue
1348 1351 if isancestor(np, x): # CASE-1
1349 1352 np = nullrev
1350 1353 elif isancestor(x, np): # CASE-2
1351 1354 newps[j] = np
1352 1355 np = nullrev
1353 1356 # New parents forming an ancestor relationship does not
1354 1357 # mean the old parents have a similar relationship. Do not
1355 1358 # set bases[x] to nullrev.
1356 1359 bases[j], bases[i] = bases[i], bases[j]
1357 1360
1358 1361 newps[i] = np
1359 1362
1360 1363 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1361 1364 # base. If only p2 changes, merging using unchanged p1 as merge base is
1362 1365 # suboptimal. Therefore swap parents to make the merge sane.
1363 1366 if newps[1] != nullrev and oldps[0] == newps[0]:
1364 1367 assert len(newps) == 2 and len(oldps) == 2
1365 1368 newps.reverse()
1366 1369 bases.reverse()
1367 1370
1368 1371 # No parent change might be an error because we fail to make rev a
1369 1372 # descendent of requested dest. This can happen, for example:
1370 1373 #
1371 1374 # C # rebase -r C -d D
1372 1375 # /| # None of A and B will be changed to D and rebase fails.
1373 1376 # A B D
1374 1377 if set(newps) == set(oldps) and dest not in newps:
1375 1378 raise error.Abort(_('cannot rebase %d:%s without '
1376 1379 'moving at least one of its parents')
1377 1380 % (rev, repo[rev]))
1378 1381
1379 1382 # Source should not be ancestor of dest. The check here guarantees it's
1380 1383 # impossible. With multi-dest, the initial check does not cover complex
1381 1384 # cases since we don't have abstractions to dry-run rebase cheaply.
1382 1385 if any(p != nullrev and isancestor(rev, p) for p in newps):
1383 1386 raise error.Abort(_('source is ancestor of destination'))
1384 1387
1385 1388 # "rebasenode" updates to new p1, use the corresponding merge base.
1386 1389 if bases[0] != nullrev:
1387 1390 base = bases[0]
1388 1391 else:
1389 1392 base = None
1390 1393
1391 1394 # Check if the merge will contain unwanted changes. That may happen if
1392 1395 # there are multiple special (non-changelog ancestor) merge bases, which
1393 1396 # cannot be handled well by the 3-way merge algorithm. For example:
1394 1397 #
1395 1398 # F
1396 1399 # /|
1397 1400 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1398 1401 # | | # as merge base, the difference between D and F will include
1399 1402 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1400 1403 # |/ # chosen, the rebased F will contain B.
1401 1404 # A Z
1402 1405 #
1403 1406 # But our merge base candidates (D and E in above case) could still be
1404 1407 # better than the default (ancestor(F, Z) == null). Therefore still
1405 1408 # pick one (so choose p1 above).
1406 1409 if sum(1 for b in bases if b != nullrev) > 1:
1407 1410 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1408 1411 for i, base in enumerate(bases):
1409 1412 if base == nullrev:
1410 1413 continue
1411 1414 # Revisions in the side (not chosen as merge base) branch that
1412 1415 # might contain "surprising" contents
1413 1416 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1414 1417 bases, base, base, dest))
1415 1418
1416 1419 # If those revisions are covered by rebaseset, the result is good.
1417 1420 # A merge in rebaseset would be considered to cover its ancestors.
1418 1421 if siderevs:
1419 1422 rebaseset = [r for r, d in state.items()
1420 1423 if d > 0 and r not in obsskipped]
1421 1424 merges = [r for r in rebaseset
1422 1425 if cl.parentrevs(r)[1] != nullrev]
1423 1426 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1424 1427 siderevs, merges, rebaseset))
1425 1428
1426 1429 # Choose a merge base that has a minimal number of unwanted revs.
1427 1430 l, i = min((len(revs), i)
1428 1431 for i, revs in enumerate(unwanted) if revs is not None)
1429 1432 base = bases[i]
1430 1433
1431 1434 # newps[0] should match merge base if possible. Currently, if newps[i]
1432 1435 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1433 1436 # the other's ancestor. In that case, it's fine to not swap newps here.
1434 1437 # (see CASE-1 and CASE-2 above)
1435 1438 if i != 0 and newps[i] != nullrev:
1436 1439 newps[0], newps[i] = newps[i], newps[0]
1437 1440
1438 1441 # The merge will include unwanted revisions. Abort now. Revisit this if
1439 1442 # we have a more advanced merge algorithm that handles multiple bases.
1440 1443 if l > 0:
1441 1444 unwanteddesc = _(' or ').join(
1442 1445 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1443 1446 for revs in unwanted if revs is not None))
1444 1447 raise error.Abort(
1445 1448 _('rebasing %d:%s will include unwanted changes from %s')
1446 1449 % (rev, repo[rev], unwanteddesc))
1447 1450
1448 1451 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1449 1452
1450 1453 return newps[0], newps[1], base
1451 1454
1452 1455 def isagitpatch(repo, patchname):
1453 1456 'Return true if the given patch is in git format'
1454 1457 mqpatch = os.path.join(repo.mq.path, patchname)
1455 1458 for line in patch.linereader(open(mqpatch, 'rb')):
1456 1459 if line.startswith('diff --git'):
1457 1460 return True
1458 1461 return False
1459 1462
1460 1463 def updatemq(repo, state, skipped, **opts):
1461 1464 'Update rebased mq patches - finalize and then import them'
1462 1465 mqrebase = {}
1463 1466 mq = repo.mq
1464 1467 original_series = mq.fullseries[:]
1465 1468 skippedpatches = set()
1466 1469
1467 1470 for p in mq.applied:
1468 1471 rev = repo[p.node].rev()
1469 1472 if rev in state:
1470 1473 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1471 1474 (rev, p.name))
1472 1475 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1473 1476 else:
1474 1477 # Applied but not rebased, not sure this should happen
1475 1478 skippedpatches.add(p.name)
1476 1479
1477 1480 if mqrebase:
1478 1481 mq.finish(repo, mqrebase.keys())
1479 1482
1480 1483 # We must start import from the newest revision
1481 1484 for rev in sorted(mqrebase, reverse=True):
1482 1485 if rev not in skipped:
1483 1486 name, isgit = mqrebase[rev]
1484 1487 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1485 1488 (name, state[rev], repo[state[rev]]))
1486 1489 mq.qimport(repo, (), patchname=name, git=isgit,
1487 1490 rev=["%d" % state[rev]])
1488 1491 else:
1489 1492 # Rebased and skipped
1490 1493 skippedpatches.add(mqrebase[rev][0])
1491 1494
1492 1495 # Patches were either applied and rebased and imported in
1493 1496 # order, applied and removed or unapplied. Discard the removed
1494 1497 # ones while preserving the original series order and guards.
1495 1498 newseries = [s for s in original_series
1496 1499 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1497 1500 mq.fullseries[:] = newseries
1498 1501 mq.seriesdirty = True
1499 1502 mq.savedirty()
1500 1503
1501 1504 def storecollapsemsg(repo, collapsemsg):
1502 1505 'Store the collapse message to allow recovery'
1503 1506 collapsemsg = collapsemsg or ''
1504 1507 f = repo.vfs("last-message.txt", "w")
1505 1508 f.write("%s\n" % collapsemsg)
1506 1509 f.close()
1507 1510
1508 1511 def clearcollapsemsg(repo):
1509 1512 'Remove collapse message file'
1510 1513 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1511 1514
1512 1515 def restorecollapsemsg(repo, isabort):
1513 1516 'Restore previously stored collapse message'
1514 1517 try:
1515 1518 f = repo.vfs("last-message.txt")
1516 1519 collapsemsg = f.readline().strip()
1517 1520 f.close()
1518 1521 except IOError as err:
1519 1522 if err.errno != errno.ENOENT:
1520 1523 raise
1521 1524 if isabort:
1522 1525 # Oh well, just abort like normal
1523 1526 collapsemsg = ''
1524 1527 else:
1525 1528 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1526 1529 return collapsemsg
1527 1530
1528 1531 def clearstatus(repo):
1529 1532 'Remove the status files'
1530 1533 # Make sure the active transaction won't write the state file
1531 1534 tr = repo.currenttransaction()
1532 1535 if tr:
1533 1536 tr.removefilegenerator('rebasestate')
1534 1537 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1535 1538
1536 1539 def needupdate(repo, state):
1537 1540 '''check whether we should `update --clean` away from a merge, or if
1538 1541 somehow the working dir got forcibly updated, e.g. by older hg'''
1539 1542 parents = [p.rev() for p in repo[None].parents()]
1540 1543
1541 1544 # Are we in a merge state at all?
1542 1545 if len(parents) < 2:
1543 1546 return False
1544 1547
1545 1548 # We should be standing on the first as-of-yet unrebased commit.
1546 1549 firstunrebased = min([old for old, new in state.iteritems()
1547 1550 if new == nullrev])
1548 1551 if firstunrebased in parents:
1549 1552 return True
1550 1553
1551 1554 return False
1552 1555
1553 1556 def abort(repo, originalwd, destmap, state, activebookmark=None):
1554 1557 '''Restore the repository to its original state. Additional args:
1555 1558
1556 1559 activebookmark: the name of the bookmark that should be active after the
1557 1560 restore'''
1558 1561
1559 1562 try:
1560 1563 # If the first commits in the rebased set get skipped during the rebase,
1561 1564 # their values within the state mapping will be the dest rev id. The
1562 1565 # rebased list must must not contain the dest rev (issue4896)
1563 1566 rebased = [s for r, s in state.items()
1564 1567 if s >= 0 and s != r and s != destmap[r]]
1565 1568 immutable = [d for d in rebased if not repo[d].mutable()]
1566 1569 cleanup = True
1567 1570 if immutable:
1568 1571 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1569 1572 % ', '.join(bytes(repo[r]) for r in immutable),
1570 1573 hint=_("see 'hg help phases' for details"))
1571 1574 cleanup = False
1572 1575
1573 1576 descendants = set()
1574 1577 if rebased:
1575 1578 descendants = set(repo.changelog.descendants(rebased))
1576 1579 if descendants - set(rebased):
1577 1580 repo.ui.warn(_("warning: new changesets detected on destination "
1578 1581 "branch, can't strip\n"))
1579 1582 cleanup = False
1580 1583
1581 1584 if cleanup:
1582 1585 shouldupdate = False
1583 1586 if rebased:
1584 1587 strippoints = [
1585 1588 c.node() for c in repo.set('roots(%ld)', rebased)]
1586 1589
1587 1590 updateifonnodes = set(rebased)
1588 1591 updateifonnodes.update(destmap.values())
1589 1592 updateifonnodes.add(originalwd)
1590 1593 shouldupdate = repo['.'].rev() in updateifonnodes
1591 1594
1592 1595 # Update away from the rebase if necessary
1593 1596 if shouldupdate or needupdate(repo, state):
1594 1597 mergemod.update(repo, originalwd, False, True)
1595 1598
1596 1599 # Strip from the first rebased revision
1597 1600 if rebased:
1598 1601 repair.strip(repo.ui, repo, strippoints)
1599 1602
1600 1603 if activebookmark and activebookmark in repo._bookmarks:
1601 1604 bookmarks.activate(repo, activebookmark)
1602 1605
1603 1606 finally:
1604 1607 clearstatus(repo)
1605 1608 clearcollapsemsg(repo)
1606 1609 repo.ui.warn(_('rebase aborted\n'))
1607 1610 return 0
1608 1611
1609 1612 def sortsource(destmap):
1610 1613 """yield source revisions in an order that we only rebase things once
1611 1614
1612 1615 If source and destination overlaps, we should filter out revisions
1613 1616 depending on other revisions which hasn't been rebased yet.
1614 1617
1615 1618 Yield a sorted list of revisions each time.
1616 1619
1617 1620 For example, when rebasing A to B, B to C. This function yields [B], then
1618 1621 [A], indicating B needs to be rebased first.
1619 1622
1620 1623 Raise if there is a cycle so the rebase is impossible.
1621 1624 """
1622 1625 srcset = set(destmap)
1623 1626 while srcset:
1624 1627 srclist = sorted(srcset)
1625 1628 result = []
1626 1629 for r in srclist:
1627 1630 if destmap[r] not in srcset:
1628 1631 result.append(r)
1629 1632 if not result:
1630 1633 raise error.Abort(_('source and destination form a cycle'))
1631 1634 srcset -= set(result)
1632 1635 yield result
1633 1636
1634 1637 def buildstate(repo, destmap, collapse):
1635 1638 '''Define which revisions are going to be rebased and where
1636 1639
1637 1640 repo: repo
1638 1641 destmap: {srcrev: destrev}
1639 1642 '''
1640 1643 rebaseset = destmap.keys()
1641 1644 originalwd = repo['.'].rev()
1642 1645
1643 1646 # This check isn't strictly necessary, since mq detects commits over an
1644 1647 # applied patch. But it prevents messing up the working directory when
1645 1648 # a partially completed rebase is blocked by mq.
1646 1649 if 'qtip' in repo.tags():
1647 1650 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1648 1651 if set(destmap.values()) & mqapplied:
1649 1652 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1650 1653
1651 1654 # Get "cycle" error early by exhausting the generator.
1652 1655 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1653 1656 if not sortedsrc:
1654 1657 raise error.Abort(_('no matching revisions'))
1655 1658
1656 1659 # Only check the first batch of revisions to rebase not depending on other
1657 1660 # rebaseset. This means "source is ancestor of destination" for the second
1658 1661 # (and following) batches of revisions are not checked here. We rely on
1659 1662 # "defineparents" to do that check.
1660 1663 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1661 1664 if not roots:
1662 1665 raise error.Abort(_('no matching revisions'))
1663 1666 def revof(r):
1664 1667 return r.rev()
1665 1668 roots = sorted(roots, key=revof)
1666 1669 state = dict.fromkeys(rebaseset, revtodo)
1667 1670 emptyrebase = (len(sortedsrc) == 1)
1668 1671 for root in roots:
1669 1672 dest = repo[destmap[root.rev()]]
1670 1673 commonbase = root.ancestor(dest)
1671 1674 if commonbase == root:
1672 1675 raise error.Abort(_('source is ancestor of destination'))
1673 1676 if commonbase == dest:
1674 1677 wctx = repo[None]
1675 1678 if dest == wctx.p1():
1676 1679 # when rebasing to '.', it will use the current wd branch name
1677 1680 samebranch = root.branch() == wctx.branch()
1678 1681 else:
1679 1682 samebranch = root.branch() == dest.branch()
1680 1683 if not collapse and samebranch and dest in root.parents():
1681 1684 # mark the revision as done by setting its new revision
1682 1685 # equal to its old (current) revisions
1683 1686 state[root.rev()] = root.rev()
1684 1687 repo.ui.debug('source is a child of destination\n')
1685 1688 continue
1686 1689
1687 1690 emptyrebase = False
1688 1691 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1689 1692 if emptyrebase:
1690 1693 return None
1691 1694 for rev in sorted(state):
1692 1695 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1693 1696 # if all parents of this revision are done, then so is this revision
1694 1697 if parents and all((state.get(p) == p for p in parents)):
1695 1698 state[rev] = rev
1696 1699 return originalwd, destmap, state
1697 1700
1698 1701 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1699 1702 keepf=False, fm=None):
1700 1703 """dispose of rebased revision at the end of the rebase
1701 1704
1702 1705 If `collapsedas` is not None, the rebase was a collapse whose result if the
1703 1706 `collapsedas` node.
1704 1707
1705 1708 If `keepf` is not True, the rebase has --keep set and no nodes should be
1706 1709 removed (but bookmarks still need to be moved).
1707 1710 """
1708 1711 tonode = repo.changelog.node
1709 1712 replacements = {}
1710 1713 moves = {}
1711 1714 for rev, newrev in sorted(state.items()):
1712 1715 if newrev >= 0 and newrev != rev:
1713 1716 oldnode = tonode(rev)
1714 1717 newnode = collapsedas or tonode(newrev)
1715 1718 moves[oldnode] = newnode
1716 1719 if not keepf:
1717 1720 if rev in skipped:
1718 1721 succs = ()
1719 1722 else:
1720 1723 succs = (newnode,)
1721 1724 replacements[oldnode] = succs
1722 1725 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1723 1726 if fm:
1724 1727 hf = fm.hexfunc
1725 1728 fl = fm.formatlist
1726 1729 fd = fm.formatdict
1727 1730 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1728 1731 for oldn, newn in replacements.iteritems()},
1729 1732 key="oldnode", value="newnodes")
1730 1733 fm.data(nodechanges=nodechanges)
1731 1734
1732 1735 def pullrebase(orig, ui, repo, *args, **opts):
1733 1736 'Call rebase after pull if the latter has been invoked with --rebase'
1734 1737 ret = None
1735 1738 if opts.get(r'rebase'):
1736 1739 if ui.configbool('commands', 'rebase.requiredest'):
1737 1740 msg = _('rebase destination required by configuration')
1738 1741 hint = _('use hg pull followed by hg rebase -d DEST')
1739 1742 raise error.Abort(msg, hint=hint)
1740 1743
1741 1744 with repo.wlock(), repo.lock():
1742 1745 if opts.get(r'update'):
1743 1746 del opts[r'update']
1744 1747 ui.debug('--update and --rebase are not compatible, ignoring '
1745 1748 'the update flag\n')
1746 1749
1747 1750 cmdutil.checkunfinished(repo)
1748 1751 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1749 1752 'please commit or shelve your changes first'))
1750 1753
1751 1754 revsprepull = len(repo)
1752 1755 origpostincoming = commands.postincoming
1753 1756 def _dummy(*args, **kwargs):
1754 1757 pass
1755 1758 commands.postincoming = _dummy
1756 1759 try:
1757 1760 ret = orig(ui, repo, *args, **opts)
1758 1761 finally:
1759 1762 commands.postincoming = origpostincoming
1760 1763 revspostpull = len(repo)
1761 1764 if revspostpull > revsprepull:
1762 1765 # --rev option from pull conflict with rebase own --rev
1763 1766 # dropping it
1764 1767 if r'rev' in opts:
1765 1768 del opts[r'rev']
1766 1769 # positional argument from pull conflicts with rebase's own
1767 1770 # --source.
1768 1771 if r'source' in opts:
1769 1772 del opts[r'source']
1770 1773 # revsprepull is the len of the repo, not revnum of tip.
1771 1774 destspace = list(repo.changelog.revs(start=revsprepull))
1772 1775 opts[r'_destspace'] = destspace
1773 1776 try:
1774 1777 rebase(ui, repo, **opts)
1775 1778 except error.NoMergeDestAbort:
1776 1779 # we can maybe update instead
1777 1780 rev, _a, _b = destutil.destupdate(repo)
1778 1781 if rev == repo['.'].rev():
1779 1782 ui.status(_('nothing to rebase\n'))
1780 1783 else:
1781 1784 ui.status(_('nothing to rebase - updating instead\n'))
1782 1785 # not passing argument to get the bare update behavior
1783 1786 # with warning and trumpets
1784 1787 commands.update(ui, repo)
1785 1788 else:
1786 1789 if opts.get(r'tool'):
1787 1790 raise error.Abort(_('--tool can only be used with --rebase'))
1788 1791 ret = orig(ui, repo, *args, **opts)
1789 1792
1790 1793 return ret
1791 1794
1792 1795 def _filterobsoleterevs(repo, revs):
1793 1796 """returns a set of the obsolete revisions in revs"""
1794 1797 return set(r for r in revs if repo[r].obsolete())
1795 1798
1796 1799 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1797 1800 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1798 1801
1799 1802 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1800 1803 obsolete nodes to be rebased given in `rebaseobsrevs`.
1801 1804
1802 1805 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1803 1806 without a successor in destination.
1804 1807
1805 1808 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1806 1809 obsolete successors.
1807 1810 """
1808 1811 obsoletenotrebased = {}
1809 1812 obsoletewithoutsuccessorindestination = set([])
1810 1813 obsoleteextinctsuccessors = set([])
1811 1814
1812 1815 assert repo.filtername is None
1813 1816 cl = repo.changelog
1814 1817 nodemap = cl.nodemap
1815 1818 extinctnodes = set(cl.node(r) for r in repo.revs('extinct()'))
1816 1819 for srcrev in rebaseobsrevs:
1817 1820 srcnode = cl.node(srcrev)
1818 1821 destnode = cl.node(destmap[srcrev])
1819 1822 # XXX: more advanced APIs are required to handle split correctly
1820 1823 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1821 1824 # obsutil.allsuccessors includes node itself
1822 1825 successors.remove(srcnode)
1823 1826 if successors.issubset(extinctnodes):
1824 1827 # all successors are extinct
1825 1828 obsoleteextinctsuccessors.add(srcrev)
1826 1829 if not successors:
1827 1830 # no successor
1828 1831 obsoletenotrebased[srcrev] = None
1829 1832 else:
1830 1833 for succnode in successors:
1831 1834 if succnode not in nodemap:
1832 1835 continue
1833 1836 if cl.isancestor(succnode, destnode):
1834 1837 obsoletenotrebased[srcrev] = nodemap[succnode]
1835 1838 break
1836 1839 else:
1837 1840 # If 'srcrev' has a successor in rebase set but none in
1838 1841 # destination (which would be catched above), we shall skip it
1839 1842 # and its descendants to avoid divergence.
1840 1843 if any(nodemap[s] in destmap for s in successors
1841 1844 if s in nodemap):
1842 1845 obsoletewithoutsuccessorindestination.add(srcrev)
1843 1846
1844 1847 return (
1845 1848 obsoletenotrebased,
1846 1849 obsoletewithoutsuccessorindestination,
1847 1850 obsoleteextinctsuccessors,
1848 1851 )
1849 1852
1850 1853 def summaryhook(ui, repo):
1851 1854 if not repo.vfs.exists('rebasestate'):
1852 1855 return
1853 1856 try:
1854 1857 rbsrt = rebaseruntime(repo, ui, {})
1855 1858 rbsrt.restorestatus()
1856 1859 state = rbsrt.state
1857 1860 except error.RepoLookupError:
1858 1861 # i18n: column positioning for "hg summary"
1859 1862 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1860 1863 ui.write(msg)
1861 1864 return
1862 1865 numrebased = len([i for i in state.itervalues() if i >= 0])
1863 1866 # i18n: column positioning for "hg summary"
1864 1867 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1865 1868 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1866 1869 ui.label(_('%d remaining'), 'rebase.remaining') %
1867 1870 (len(state) - numrebased)))
1868 1871
1869 1872 def uisetup(ui):
1870 1873 #Replace pull with a decorator to provide --rebase option
1871 1874 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1872 1875 entry[1].append(('', 'rebase', None,
1873 1876 _("rebase working directory to branch head")))
1874 1877 entry[1].append(('t', 'tool', '',
1875 1878 _("specify merge tool for rebase")))
1876 1879 cmdutil.summaryhooks.add('rebase', summaryhook)
1877 1880 cmdutil.unfinishedstates.append(
1878 1881 ['rebasestate', False, False, _('rebase in progress'),
1879 1882 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1880 1883 cmdutil.afterresolvedstates.append(
1881 1884 ['rebasestate', _('hg rebase --continue')])
General Comments 0
You need to be logged in to leave comments. Login now