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