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