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