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