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