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