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