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