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