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