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