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