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