##// END OF EJS Templates
rebase: make dry-run return 1 or 0 according to result...
Sushil khanchi -
r38462:e6b643cc @53 default
parent child Browse files
Show More
@@ -1,1874 +1,1876 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from __future__ import absolute_import
18 18
19 19 import errno
20 20 import os
21 21
22 22 from mercurial.i18n import _
23 23 from mercurial.node import (
24 24 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 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 try:
822 822 overrides = {('rebase', 'singletransaction'): True}
823 823 with ui.configoverride(overrides, 'rebase'):
824 824 _origrebase(ui, repo, inmemory=True, leaveunfinished=True,
825 825 **opts)
826 826 except error.InMemoryMergeConflictsError:
827 827 ui.status(_('hit a merge conflict\n'))
828 return 1
828 829 else:
829 830 ui.status(_('there will be no conflict, you can rebase\n'))
831 return 0
830 832 finally:
831 833 _origrebase(ui, repo, abort=True)
832 834 elif inmemory:
833 835 try:
834 836 # in-memory merge doesn't support conflicts, so if we hit any, abort
835 837 # and re-run as an on-disk merge.
836 838 overrides = {('rebase', 'singletransaction'): True}
837 839 with ui.configoverride(overrides, 'rebase'):
838 840 return _origrebase(ui, repo, inmemory=inmemory, **opts)
839 841 except error.InMemoryMergeConflictsError:
840 842 ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
841 843 ' merge\n'))
842 844 _origrebase(ui, repo, abort=True)
843 845 return _origrebase(ui, repo, inmemory=False, **opts)
844 846 else:
845 847 return _origrebase(ui, repo, **opts)
846 848
847 849 def _origrebase(ui, repo, inmemory=False, leaveunfinished=False, **opts):
848 850 opts = pycompat.byteskwargs(opts)
849 851 rbsrt = rebaseruntime(repo, ui, inmemory, opts)
850 852
851 853 with repo.wlock(), repo.lock():
852 854 # Validate input and define rebasing points
853 855 destf = opts.get('dest', None)
854 856 srcf = opts.get('source', None)
855 857 basef = opts.get('base', None)
856 858 revf = opts.get('rev', [])
857 859 # search default destination in this space
858 860 # used in the 'hg pull --rebase' case, see issue 5214.
859 861 destspace = opts.get('_destspace')
860 862 contf = opts.get('continue')
861 863 abortf = opts.get('abort')
862 864 if opts.get('interactive'):
863 865 try:
864 866 if extensions.find('histedit'):
865 867 enablehistedit = ''
866 868 except KeyError:
867 869 enablehistedit = " --config extensions.histedit="
868 870 help = "hg%s help -e histedit" % enablehistedit
869 871 msg = _("interactive history editing is supported by the "
870 872 "'histedit' extension (see \"%s\")") % help
871 873 raise error.Abort(msg)
872 874
873 875 if rbsrt.collapsemsg and not rbsrt.collapsef:
874 876 raise error.Abort(
875 877 _('message can only be specified with collapse'))
876 878
877 879 if contf or abortf:
878 880 if contf and abortf:
879 881 raise error.Abort(_('cannot use both abort and continue'))
880 882 if rbsrt.collapsef:
881 883 raise error.Abort(
882 884 _('cannot use collapse with continue or abort'))
883 885 if srcf or basef or destf:
884 886 raise error.Abort(
885 887 _('abort and continue do not allow specifying revisions'))
886 888 if abortf and opts.get('tool', False):
887 889 ui.warn(_('tool option will be ignored\n'))
888 890 if contf:
889 891 ms = mergemod.mergestate.read(repo)
890 892 mergeutil.checkunresolved(ms)
891 893
892 894 retcode = rbsrt._prepareabortorcontinue(abortf)
893 895 if retcode is not None:
894 896 return retcode
895 897 else:
896 898 destmap = _definedestmap(ui, repo, inmemory, destf, srcf, basef,
897 899 revf, destspace=destspace)
898 900 retcode = rbsrt._preparenewrebase(destmap)
899 901 if retcode is not None:
900 902 return retcode
901 903 storecollapsemsg(repo, rbsrt.collapsemsg)
902 904
903 905 tr = None
904 906
905 907 singletr = ui.configbool('rebase', 'singletransaction')
906 908 if singletr:
907 909 tr = repo.transaction('rebase')
908 910
909 911 # If `rebase.singletransaction` is enabled, wrap the entire operation in
910 912 # one transaction here. Otherwise, transactions are obtained when
911 913 # committing each node, which is slower but allows partial success.
912 914 with util.acceptintervention(tr):
913 915 # Same logic for the dirstate guard, except we don't create one when
914 916 # rebasing in-memory (it's not needed).
915 917 dsguard = None
916 918 if singletr and not inmemory:
917 919 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
918 920 with util.acceptintervention(dsguard):
919 921 rbsrt._performrebase(tr)
920 922 if not leaveunfinished:
921 923 rbsrt._finishrebase()
922 924
923 925 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
924 926 revf=None, destspace=None):
925 927 """use revisions argument to define destmap {srcrev: destrev}"""
926 928 if revf is None:
927 929 revf = []
928 930
929 931 # destspace is here to work around issues with `hg pull --rebase` see
930 932 # issue5214 for details
931 933 if srcf and basef:
932 934 raise error.Abort(_('cannot specify both a source and a base'))
933 935 if revf and basef:
934 936 raise error.Abort(_('cannot specify both a revision and a base'))
935 937 if revf and srcf:
936 938 raise error.Abort(_('cannot specify both a revision and a source'))
937 939
938 940 if not inmemory:
939 941 cmdutil.checkunfinished(repo)
940 942 cmdutil.bailifchanged(repo)
941 943
942 944 if ui.configbool('commands', 'rebase.requiredest') and not destf:
943 945 raise error.Abort(_('you must specify a destination'),
944 946 hint=_('use: hg rebase -d REV'))
945 947
946 948 dest = None
947 949
948 950 if revf:
949 951 rebaseset = scmutil.revrange(repo, revf)
950 952 if not rebaseset:
951 953 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
952 954 return None
953 955 elif srcf:
954 956 src = scmutil.revrange(repo, [srcf])
955 957 if not src:
956 958 ui.status(_('empty "source" revision set - nothing to rebase\n'))
957 959 return None
958 960 rebaseset = repo.revs('(%ld)::', src)
959 961 assert rebaseset
960 962 else:
961 963 base = scmutil.revrange(repo, [basef or '.'])
962 964 if not base:
963 965 ui.status(_('empty "base" revision set - '
964 966 "can't compute rebase set\n"))
965 967 return None
966 968 if destf:
967 969 # --base does not support multiple destinations
968 970 dest = scmutil.revsingle(repo, destf)
969 971 else:
970 972 dest = repo[_destrebase(repo, base, destspace=destspace)]
971 973 destf = bytes(dest)
972 974
973 975 roots = [] # selected children of branching points
974 976 bpbase = {} # {branchingpoint: [origbase]}
975 977 for b in base: # group bases by branching points
976 978 bp = repo.revs('ancestor(%d, %d)', b, dest.rev()).first()
977 979 bpbase[bp] = bpbase.get(bp, []) + [b]
978 980 if None in bpbase:
979 981 # emulate the old behavior, showing "nothing to rebase" (a better
980 982 # behavior may be abort with "cannot find branching point" error)
981 983 bpbase.clear()
982 984 for bp, bs in bpbase.iteritems(): # calculate roots
983 985 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
984 986
985 987 rebaseset = repo.revs('%ld::', roots)
986 988
987 989 if not rebaseset:
988 990 # transform to list because smartsets are not comparable to
989 991 # lists. This should be improved to honor laziness of
990 992 # smartset.
991 993 if list(base) == [dest.rev()]:
992 994 if basef:
993 995 ui.status(_('nothing to rebase - %s is both "base"'
994 996 ' and destination\n') % dest)
995 997 else:
996 998 ui.status(_('nothing to rebase - working directory '
997 999 'parent is also destination\n'))
998 1000 elif not repo.revs('%ld - ::%d', base, dest.rev()):
999 1001 if basef:
1000 1002 ui.status(_('nothing to rebase - "base" %s is '
1001 1003 'already an ancestor of destination '
1002 1004 '%s\n') %
1003 1005 ('+'.join(bytes(repo[r]) for r in base),
1004 1006 dest))
1005 1007 else:
1006 1008 ui.status(_('nothing to rebase - working '
1007 1009 'directory parent is already an '
1008 1010 'ancestor of destination %s\n') % dest)
1009 1011 else: # can it happen?
1010 1012 ui.status(_('nothing to rebase from %s to %s\n') %
1011 1013 ('+'.join(bytes(repo[r]) for r in base), dest))
1012 1014 return None
1013 1015
1014 1016 rebasingwcp = repo['.'].rev() in rebaseset
1015 1017 ui.log("rebase", "", rebase_rebasing_wcp=rebasingwcp)
1016 1018 if inmemory and rebasingwcp:
1017 1019 # Check these since we did not before.
1018 1020 cmdutil.checkunfinished(repo)
1019 1021 cmdutil.bailifchanged(repo)
1020 1022
1021 1023 if not destf:
1022 1024 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1023 1025 destf = bytes(dest)
1024 1026
1025 1027 allsrc = revsetlang.formatspec('%ld', rebaseset)
1026 1028 alias = {'ALLSRC': allsrc}
1027 1029
1028 1030 if dest is None:
1029 1031 try:
1030 1032 # fast path: try to resolve dest without SRC alias
1031 1033 dest = scmutil.revsingle(repo, destf, localalias=alias)
1032 1034 except error.RepoLookupError:
1033 1035 # multi-dest path: resolve dest for each SRC separately
1034 1036 destmap = {}
1035 1037 for r in rebaseset:
1036 1038 alias['SRC'] = revsetlang.formatspec('%d', r)
1037 1039 # use repo.anyrevs instead of scmutil.revsingle because we
1038 1040 # don't want to abort if destset is empty.
1039 1041 destset = repo.anyrevs([destf], user=True, localalias=alias)
1040 1042 size = len(destset)
1041 1043 if size == 1:
1042 1044 destmap[r] = destset.first()
1043 1045 elif size == 0:
1044 1046 ui.note(_('skipping %s - empty destination\n') % repo[r])
1045 1047 else:
1046 1048 raise error.Abort(_('rebase destination for %s is not '
1047 1049 'unique') % repo[r])
1048 1050
1049 1051 if dest is not None:
1050 1052 # single-dest case: assign dest to each rev in rebaseset
1051 1053 destrev = dest.rev()
1052 1054 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1053 1055
1054 1056 if not destmap:
1055 1057 ui.status(_('nothing to rebase - empty destination\n'))
1056 1058 return None
1057 1059
1058 1060 return destmap
1059 1061
1060 1062 def externalparent(repo, state, destancestors):
1061 1063 """Return the revision that should be used as the second parent
1062 1064 when the revisions in state is collapsed on top of destancestors.
1063 1065 Abort if there is more than one parent.
1064 1066 """
1065 1067 parents = set()
1066 1068 source = min(state)
1067 1069 for rev in state:
1068 1070 if rev == source:
1069 1071 continue
1070 1072 for p in repo[rev].parents():
1071 1073 if (p.rev() not in state
1072 1074 and p.rev() not in destancestors):
1073 1075 parents.add(p.rev())
1074 1076 if not parents:
1075 1077 return nullrev
1076 1078 if len(parents) == 1:
1077 1079 return parents.pop()
1078 1080 raise error.Abort(_('unable to collapse on top of %d, there is more '
1079 1081 'than one external parent: %s') %
1080 1082 (max(destancestors),
1081 1083 ', '.join("%d" % p for p in sorted(parents))))
1082 1084
1083 1085 def commitmemorynode(repo, p1, p2, wctx, editor, extra, user, date, commitmsg):
1084 1086 '''Commit the memory changes with parents p1 and p2.
1085 1087 Return node of committed revision.'''
1086 1088 # Replicates the empty check in ``repo.commit``.
1087 1089 if wctx.isempty() and not repo.ui.configbool('ui', 'allowemptycommit'):
1088 1090 return None
1089 1091
1090 1092 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1091 1093 # ``branch`` (used when passing ``--keepbranches``).
1092 1094 branch = repo[p1].branch()
1093 1095 if 'branch' in extra:
1094 1096 branch = extra['branch']
1095 1097
1096 1098 memctx = wctx.tomemctx(commitmsg, parents=(p1, p2), date=date,
1097 1099 extra=extra, user=user, branch=branch, editor=editor)
1098 1100 commitres = repo.commitctx(memctx)
1099 1101 wctx.clean() # Might be reused
1100 1102 return commitres
1101 1103
1102 1104 def commitnode(repo, p1, p2, editor, extra, user, date, commitmsg):
1103 1105 '''Commit the wd changes with parents p1 and p2.
1104 1106 Return node of committed revision.'''
1105 1107 dsguard = util.nullcontextmanager()
1106 1108 if not repo.ui.configbool('rebase', 'singletransaction'):
1107 1109 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
1108 1110 with dsguard:
1109 1111 repo.setparents(repo[p1].node(), repo[p2].node())
1110 1112
1111 1113 # Commit might fail if unresolved files exist
1112 1114 newnode = repo.commit(text=commitmsg, user=user, date=date,
1113 1115 extra=extra, editor=editor)
1114 1116
1115 1117 repo.dirstate.setbranch(repo[newnode].branch())
1116 1118 return newnode
1117 1119
1118 1120 def rebasenode(repo, rev, p1, base, collapse, dest, wctx):
1119 1121 'Rebase a single revision rev on top of p1 using base as merge ancestor'
1120 1122 # Merge phase
1121 1123 # Update to destination and merge it with local
1122 1124 if wctx.isinmemory():
1123 1125 wctx.setbase(repo[p1])
1124 1126 else:
1125 1127 if repo['.'].rev() != p1:
1126 1128 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
1127 1129 mergemod.update(repo, p1, False, True)
1128 1130 else:
1129 1131 repo.ui.debug(" already in destination\n")
1130 1132 # This is, alas, necessary to invalidate workingctx's manifest cache,
1131 1133 # as well as other data we litter on it in other places.
1132 1134 wctx = repo[None]
1133 1135 repo.dirstate.write(repo.currenttransaction())
1134 1136 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
1135 1137 if base is not None:
1136 1138 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
1137 1139 # When collapsing in-place, the parent is the common ancestor, we
1138 1140 # have to allow merging with it.
1139 1141 stats = mergemod.update(repo, rev, True, True, base, collapse,
1140 1142 labels=['dest', 'source'], wc=wctx)
1141 1143 if collapse:
1142 1144 copies.duplicatecopies(repo, wctx, rev, dest)
1143 1145 else:
1144 1146 # If we're not using --collapse, we need to
1145 1147 # duplicate copies between the revision we're
1146 1148 # rebasing and its first parent, but *not*
1147 1149 # duplicate any copies that have already been
1148 1150 # performed in the destination.
1149 1151 p1rev = repo[rev].p1().rev()
1150 1152 copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
1151 1153 return stats
1152 1154
1153 1155 def adjustdest(repo, rev, destmap, state, skipped):
1154 1156 """adjust rebase destination given the current rebase state
1155 1157
1156 1158 rev is what is being rebased. Return a list of two revs, which are the
1157 1159 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1158 1160 nullrev, return dest without adjustment for it.
1159 1161
1160 1162 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1161 1163 to B1, and E's destination will be adjusted from F to B1.
1162 1164
1163 1165 B1 <- written during rebasing B
1164 1166 |
1165 1167 F <- original destination of B, E
1166 1168 |
1167 1169 | E <- rev, which is being rebased
1168 1170 | |
1169 1171 | D <- prev, one parent of rev being checked
1170 1172 | |
1171 1173 | x <- skipped, ex. no successor or successor in (::dest)
1172 1174 | |
1173 1175 | C <- rebased as C', different destination
1174 1176 | |
1175 1177 | B <- rebased as B1 C'
1176 1178 |/ |
1177 1179 A G <- destination of C, different
1178 1180
1179 1181 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1180 1182 first move C to C1, G to G1, and when it's checking H, the adjusted
1181 1183 destinations will be [C1, G1].
1182 1184
1183 1185 H C1 G1
1184 1186 /| | /
1185 1187 F G |/
1186 1188 K | | -> K
1187 1189 | C D |
1188 1190 | |/ |
1189 1191 | B | ...
1190 1192 |/ |/
1191 1193 A A
1192 1194
1193 1195 Besides, adjust dest according to existing rebase information. For example,
1194 1196
1195 1197 B C D B needs to be rebased on top of C, C needs to be rebased on top
1196 1198 \|/ of D. We will rebase C first.
1197 1199 A
1198 1200
1199 1201 C' After rebasing C, when considering B's destination, use C'
1200 1202 | instead of the original C.
1201 1203 B D
1202 1204 \ /
1203 1205 A
1204 1206 """
1205 1207 # pick already rebased revs with same dest from state as interesting source
1206 1208 dest = destmap[rev]
1207 1209 source = [s for s, d in state.items()
1208 1210 if d > 0 and destmap[s] == dest and s not in skipped]
1209 1211
1210 1212 result = []
1211 1213 for prev in repo.changelog.parentrevs(rev):
1212 1214 adjusted = dest
1213 1215 if prev != nullrev:
1214 1216 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
1215 1217 if candidate is not None:
1216 1218 adjusted = state[candidate]
1217 1219 if adjusted == dest and dest in state:
1218 1220 adjusted = state[dest]
1219 1221 if adjusted == revtodo:
1220 1222 # sortsource should produce an order that makes this impossible
1221 1223 raise error.ProgrammingError(
1222 1224 'rev %d should be rebased already at this time' % dest)
1223 1225 result.append(adjusted)
1224 1226 return result
1225 1227
1226 1228 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1227 1229 """
1228 1230 Abort if rebase will create divergence or rebase is noop because of markers
1229 1231
1230 1232 `rebaseobsrevs`: set of obsolete revision in source
1231 1233 `rebaseobsskipped`: set of revisions from source skipped because they have
1232 1234 successors in destination or no non-obsolete successor.
1233 1235 """
1234 1236 # Obsolete node with successors not in dest leads to divergence
1235 1237 divergenceok = ui.configbool('experimental',
1236 1238 'evolution.allowdivergence')
1237 1239 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1238 1240
1239 1241 if divergencebasecandidates and not divergenceok:
1240 1242 divhashes = (bytes(repo[r])
1241 1243 for r in divergencebasecandidates)
1242 1244 msg = _("this rebase will cause "
1243 1245 "divergences from: %s")
1244 1246 h = _("to force the rebase please set "
1245 1247 "experimental.evolution.allowdivergence=True")
1246 1248 raise error.Abort(msg % (",".join(divhashes),), hint=h)
1247 1249
1248 1250 def successorrevs(unfi, rev):
1249 1251 """yield revision numbers for successors of rev"""
1250 1252 assert unfi.filtername is None
1251 1253 nodemap = unfi.changelog.nodemap
1252 1254 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1253 1255 if s in nodemap:
1254 1256 yield nodemap[s]
1255 1257
1256 1258 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1257 1259 """Return new parents and optionally a merge base for rev being rebased
1258 1260
1259 1261 The destination specified by "dest" cannot always be used directly because
1260 1262 previously rebase result could affect destination. For example,
1261 1263
1262 1264 D E rebase -r C+D+E -d B
1263 1265 |/ C will be rebased to C'
1264 1266 B C D's new destination will be C' instead of B
1265 1267 |/ E's new destination will be C' instead of B
1266 1268 A
1267 1269
1268 1270 The new parents of a merge is slightly more complicated. See the comment
1269 1271 block below.
1270 1272 """
1271 1273 # use unfiltered changelog since successorrevs may return filtered nodes
1272 1274 assert repo.filtername is None
1273 1275 cl = repo.changelog
1274 1276 def isancestor(a, b):
1275 1277 # take revision numbers instead of nodes
1276 1278 if a == b:
1277 1279 return True
1278 1280 elif a > b:
1279 1281 return False
1280 1282 return cl.isancestor(cl.node(a), cl.node(b))
1281 1283
1282 1284 dest = destmap[rev]
1283 1285 oldps = repo.changelog.parentrevs(rev) # old parents
1284 1286 newps = [nullrev, nullrev] # new parents
1285 1287 dests = adjustdest(repo, rev, destmap, state, skipped)
1286 1288 bases = list(oldps) # merge base candidates, initially just old parents
1287 1289
1288 1290 if all(r == nullrev for r in oldps[1:]):
1289 1291 # For non-merge changeset, just move p to adjusted dest as requested.
1290 1292 newps[0] = dests[0]
1291 1293 else:
1292 1294 # For merge changeset, if we move p to dests[i] unconditionally, both
1293 1295 # parents may change and the end result looks like "the merge loses a
1294 1296 # parent", which is a surprise. This is a limit because "--dest" only
1295 1297 # accepts one dest per src.
1296 1298 #
1297 1299 # Therefore, only move p with reasonable conditions (in this order):
1298 1300 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1299 1301 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1300 1302 #
1301 1303 # Comparing with adjustdest, the logic here does some additional work:
1302 1304 # 1. decide which parents will not be moved towards dest
1303 1305 # 2. if the above decision is "no", should a parent still be moved
1304 1306 # because it was rebased?
1305 1307 #
1306 1308 # For example:
1307 1309 #
1308 1310 # C # "rebase -r C -d D" is an error since none of the parents
1309 1311 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1310 1312 # A B D # B (using rule "2."), since B will be rebased.
1311 1313 #
1312 1314 # The loop tries to be not rely on the fact that a Mercurial node has
1313 1315 # at most 2 parents.
1314 1316 for i, p in enumerate(oldps):
1315 1317 np = p # new parent
1316 1318 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1317 1319 np = dests[i]
1318 1320 elif p in state and state[p] > 0:
1319 1321 np = state[p]
1320 1322
1321 1323 # "bases" only record "special" merge bases that cannot be
1322 1324 # calculated from changelog DAG (i.e. isancestor(p, np) is False).
1323 1325 # For example:
1324 1326 #
1325 1327 # B' # rebase -s B -d D, when B was rebased to B'. dest for C
1326 1328 # | C # is B', but merge base for C is B, instead of
1327 1329 # D | # changelog.ancestor(C, B') == A. If changelog DAG and
1328 1330 # | B # "state" edges are merged (so there will be an edge from
1329 1331 # |/ # B to B'), the merge base is still ancestor(C, B') in
1330 1332 # A # the merged graph.
1331 1333 #
1332 1334 # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
1333 1335 # which uses "virtual null merge" to explain this situation.
1334 1336 if isancestor(p, np):
1335 1337 bases[i] = nullrev
1336 1338
1337 1339 # If one parent becomes an ancestor of the other, drop the ancestor
1338 1340 for j, x in enumerate(newps[:i]):
1339 1341 if x == nullrev:
1340 1342 continue
1341 1343 if isancestor(np, x): # CASE-1
1342 1344 np = nullrev
1343 1345 elif isancestor(x, np): # CASE-2
1344 1346 newps[j] = np
1345 1347 np = nullrev
1346 1348 # New parents forming an ancestor relationship does not
1347 1349 # mean the old parents have a similar relationship. Do not
1348 1350 # set bases[x] to nullrev.
1349 1351 bases[j], bases[i] = bases[i], bases[j]
1350 1352
1351 1353 newps[i] = np
1352 1354
1353 1355 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1354 1356 # base. If only p2 changes, merging using unchanged p1 as merge base is
1355 1357 # suboptimal. Therefore swap parents to make the merge sane.
1356 1358 if newps[1] != nullrev and oldps[0] == newps[0]:
1357 1359 assert len(newps) == 2 and len(oldps) == 2
1358 1360 newps.reverse()
1359 1361 bases.reverse()
1360 1362
1361 1363 # No parent change might be an error because we fail to make rev a
1362 1364 # descendent of requested dest. This can happen, for example:
1363 1365 #
1364 1366 # C # rebase -r C -d D
1365 1367 # /| # None of A and B will be changed to D and rebase fails.
1366 1368 # A B D
1367 1369 if set(newps) == set(oldps) and dest not in newps:
1368 1370 raise error.Abort(_('cannot rebase %d:%s without '
1369 1371 'moving at least one of its parents')
1370 1372 % (rev, repo[rev]))
1371 1373
1372 1374 # Source should not be ancestor of dest. The check here guarantees it's
1373 1375 # impossible. With multi-dest, the initial check does not cover complex
1374 1376 # cases since we don't have abstractions to dry-run rebase cheaply.
1375 1377 if any(p != nullrev and isancestor(rev, p) for p in newps):
1376 1378 raise error.Abort(_('source is ancestor of destination'))
1377 1379
1378 1380 # "rebasenode" updates to new p1, use the corresponding merge base.
1379 1381 if bases[0] != nullrev:
1380 1382 base = bases[0]
1381 1383 else:
1382 1384 base = None
1383 1385
1384 1386 # Check if the merge will contain unwanted changes. That may happen if
1385 1387 # there are multiple special (non-changelog ancestor) merge bases, which
1386 1388 # cannot be handled well by the 3-way merge algorithm. For example:
1387 1389 #
1388 1390 # F
1389 1391 # /|
1390 1392 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1391 1393 # | | # as merge base, the difference between D and F will include
1392 1394 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1393 1395 # |/ # chosen, the rebased F will contain B.
1394 1396 # A Z
1395 1397 #
1396 1398 # But our merge base candidates (D and E in above case) could still be
1397 1399 # better than the default (ancestor(F, Z) == null). Therefore still
1398 1400 # pick one (so choose p1 above).
1399 1401 if sum(1 for b in bases if b != nullrev) > 1:
1400 1402 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1401 1403 for i, base in enumerate(bases):
1402 1404 if base == nullrev:
1403 1405 continue
1404 1406 # Revisions in the side (not chosen as merge base) branch that
1405 1407 # might contain "surprising" contents
1406 1408 siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
1407 1409 bases, base, base, dest))
1408 1410
1409 1411 # If those revisions are covered by rebaseset, the result is good.
1410 1412 # A merge in rebaseset would be considered to cover its ancestors.
1411 1413 if siderevs:
1412 1414 rebaseset = [r for r, d in state.items()
1413 1415 if d > 0 and r not in obsskipped]
1414 1416 merges = [r for r in rebaseset
1415 1417 if cl.parentrevs(r)[1] != nullrev]
1416 1418 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1417 1419 siderevs, merges, rebaseset))
1418 1420
1419 1421 # Choose a merge base that has a minimal number of unwanted revs.
1420 1422 l, i = min((len(revs), i)
1421 1423 for i, revs in enumerate(unwanted) if revs is not None)
1422 1424 base = bases[i]
1423 1425
1424 1426 # newps[0] should match merge base if possible. Currently, if newps[i]
1425 1427 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1426 1428 # the other's ancestor. In that case, it's fine to not swap newps here.
1427 1429 # (see CASE-1 and CASE-2 above)
1428 1430 if i != 0 and newps[i] != nullrev:
1429 1431 newps[0], newps[i] = newps[i], newps[0]
1430 1432
1431 1433 # The merge will include unwanted revisions. Abort now. Revisit this if
1432 1434 # we have a more advanced merge algorithm that handles multiple bases.
1433 1435 if l > 0:
1434 1436 unwanteddesc = _(' or ').join(
1435 1437 (', '.join('%d:%s' % (r, repo[r]) for r in revs)
1436 1438 for revs in unwanted if revs is not None))
1437 1439 raise error.Abort(
1438 1440 _('rebasing %d:%s will include unwanted changes from %s')
1439 1441 % (rev, repo[rev], unwanteddesc))
1440 1442
1441 1443 repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
1442 1444
1443 1445 return newps[0], newps[1], base
1444 1446
1445 1447 def isagitpatch(repo, patchname):
1446 1448 'Return true if the given patch is in git format'
1447 1449 mqpatch = os.path.join(repo.mq.path, patchname)
1448 1450 for line in patch.linereader(open(mqpatch, 'rb')):
1449 1451 if line.startswith('diff --git'):
1450 1452 return True
1451 1453 return False
1452 1454
1453 1455 def updatemq(repo, state, skipped, **opts):
1454 1456 'Update rebased mq patches - finalize and then import them'
1455 1457 mqrebase = {}
1456 1458 mq = repo.mq
1457 1459 original_series = mq.fullseries[:]
1458 1460 skippedpatches = set()
1459 1461
1460 1462 for p in mq.applied:
1461 1463 rev = repo[p.node].rev()
1462 1464 if rev in state:
1463 1465 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1464 1466 (rev, p.name))
1465 1467 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1466 1468 else:
1467 1469 # Applied but not rebased, not sure this should happen
1468 1470 skippedpatches.add(p.name)
1469 1471
1470 1472 if mqrebase:
1471 1473 mq.finish(repo, mqrebase.keys())
1472 1474
1473 1475 # We must start import from the newest revision
1474 1476 for rev in sorted(mqrebase, reverse=True):
1475 1477 if rev not in skipped:
1476 1478 name, isgit = mqrebase[rev]
1477 1479 repo.ui.note(_('updating mq patch %s to %d:%s\n') %
1478 1480 (name, state[rev], repo[state[rev]]))
1479 1481 mq.qimport(repo, (), patchname=name, git=isgit,
1480 1482 rev=["%d" % state[rev]])
1481 1483 else:
1482 1484 # Rebased and skipped
1483 1485 skippedpatches.add(mqrebase[rev][0])
1484 1486
1485 1487 # Patches were either applied and rebased and imported in
1486 1488 # order, applied and removed or unapplied. Discard the removed
1487 1489 # ones while preserving the original series order and guards.
1488 1490 newseries = [s for s in original_series
1489 1491 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1490 1492 mq.fullseries[:] = newseries
1491 1493 mq.seriesdirty = True
1492 1494 mq.savedirty()
1493 1495
1494 1496 def storecollapsemsg(repo, collapsemsg):
1495 1497 'Store the collapse message to allow recovery'
1496 1498 collapsemsg = collapsemsg or ''
1497 1499 f = repo.vfs("last-message.txt", "w")
1498 1500 f.write("%s\n" % collapsemsg)
1499 1501 f.close()
1500 1502
1501 1503 def clearcollapsemsg(repo):
1502 1504 'Remove collapse message file'
1503 1505 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1504 1506
1505 1507 def restorecollapsemsg(repo, isabort):
1506 1508 'Restore previously stored collapse message'
1507 1509 try:
1508 1510 f = repo.vfs("last-message.txt")
1509 1511 collapsemsg = f.readline().strip()
1510 1512 f.close()
1511 1513 except IOError as err:
1512 1514 if err.errno != errno.ENOENT:
1513 1515 raise
1514 1516 if isabort:
1515 1517 # Oh well, just abort like normal
1516 1518 collapsemsg = ''
1517 1519 else:
1518 1520 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1519 1521 return collapsemsg
1520 1522
1521 1523 def clearstatus(repo):
1522 1524 'Remove the status files'
1523 1525 # Make sure the active transaction won't write the state file
1524 1526 tr = repo.currenttransaction()
1525 1527 if tr:
1526 1528 tr.removefilegenerator('rebasestate')
1527 1529 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1528 1530
1529 1531 def needupdate(repo, state):
1530 1532 '''check whether we should `update --clean` away from a merge, or if
1531 1533 somehow the working dir got forcibly updated, e.g. by older hg'''
1532 1534 parents = [p.rev() for p in repo[None].parents()]
1533 1535
1534 1536 # Are we in a merge state at all?
1535 1537 if len(parents) < 2:
1536 1538 return False
1537 1539
1538 1540 # We should be standing on the first as-of-yet unrebased commit.
1539 1541 firstunrebased = min([old for old, new in state.iteritems()
1540 1542 if new == nullrev])
1541 1543 if firstunrebased in parents:
1542 1544 return True
1543 1545
1544 1546 return False
1545 1547
1546 1548 def abort(repo, originalwd, destmap, state, activebookmark=None):
1547 1549 '''Restore the repository to its original state. Additional args:
1548 1550
1549 1551 activebookmark: the name of the bookmark that should be active after the
1550 1552 restore'''
1551 1553
1552 1554 try:
1553 1555 # If the first commits in the rebased set get skipped during the rebase,
1554 1556 # their values within the state mapping will be the dest rev id. The
1555 1557 # rebased list must must not contain the dest rev (issue4896)
1556 1558 rebased = [s for r, s in state.items()
1557 1559 if s >= 0 and s != r and s != destmap[r]]
1558 1560 immutable = [d for d in rebased if not repo[d].mutable()]
1559 1561 cleanup = True
1560 1562 if immutable:
1561 1563 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1562 1564 % ', '.join(bytes(repo[r]) for r in immutable),
1563 1565 hint=_("see 'hg help phases' for details"))
1564 1566 cleanup = False
1565 1567
1566 1568 descendants = set()
1567 1569 if rebased:
1568 1570 descendants = set(repo.changelog.descendants(rebased))
1569 1571 if descendants - set(rebased):
1570 1572 repo.ui.warn(_("warning: new changesets detected on destination "
1571 1573 "branch, can't strip\n"))
1572 1574 cleanup = False
1573 1575
1574 1576 if cleanup:
1575 1577 shouldupdate = False
1576 1578 if rebased:
1577 1579 strippoints = [
1578 1580 c.node() for c in repo.set('roots(%ld)', rebased)]
1579 1581
1580 1582 updateifonnodes = set(rebased)
1581 1583 updateifonnodes.update(destmap.values())
1582 1584 updateifonnodes.add(originalwd)
1583 1585 shouldupdate = repo['.'].rev() in updateifonnodes
1584 1586
1585 1587 # Update away from the rebase if necessary
1586 1588 if shouldupdate or needupdate(repo, state):
1587 1589 mergemod.update(repo, originalwd, False, True)
1588 1590
1589 1591 # Strip from the first rebased revision
1590 1592 if rebased:
1591 1593 repair.strip(repo.ui, repo, strippoints)
1592 1594
1593 1595 if activebookmark and activebookmark in repo._bookmarks:
1594 1596 bookmarks.activate(repo, activebookmark)
1595 1597
1596 1598 finally:
1597 1599 clearstatus(repo)
1598 1600 clearcollapsemsg(repo)
1599 1601 repo.ui.warn(_('rebase aborted\n'))
1600 1602 return 0
1601 1603
1602 1604 def sortsource(destmap):
1603 1605 """yield source revisions in an order that we only rebase things once
1604 1606
1605 1607 If source and destination overlaps, we should filter out revisions
1606 1608 depending on other revisions which hasn't been rebased yet.
1607 1609
1608 1610 Yield a sorted list of revisions each time.
1609 1611
1610 1612 For example, when rebasing A to B, B to C. This function yields [B], then
1611 1613 [A], indicating B needs to be rebased first.
1612 1614
1613 1615 Raise if there is a cycle so the rebase is impossible.
1614 1616 """
1615 1617 srcset = set(destmap)
1616 1618 while srcset:
1617 1619 srclist = sorted(srcset)
1618 1620 result = []
1619 1621 for r in srclist:
1620 1622 if destmap[r] not in srcset:
1621 1623 result.append(r)
1622 1624 if not result:
1623 1625 raise error.Abort(_('source and destination form a cycle'))
1624 1626 srcset -= set(result)
1625 1627 yield result
1626 1628
1627 1629 def buildstate(repo, destmap, collapse):
1628 1630 '''Define which revisions are going to be rebased and where
1629 1631
1630 1632 repo: repo
1631 1633 destmap: {srcrev: destrev}
1632 1634 '''
1633 1635 rebaseset = destmap.keys()
1634 1636 originalwd = repo['.'].rev()
1635 1637
1636 1638 # This check isn't strictly necessary, since mq detects commits over an
1637 1639 # applied patch. But it prevents messing up the working directory when
1638 1640 # a partially completed rebase is blocked by mq.
1639 1641 if 'qtip' in repo.tags():
1640 1642 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
1641 1643 if set(destmap.values()) & mqapplied:
1642 1644 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1643 1645
1644 1646 # Get "cycle" error early by exhausting the generator.
1645 1647 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1646 1648 if not sortedsrc:
1647 1649 raise error.Abort(_('no matching revisions'))
1648 1650
1649 1651 # Only check the first batch of revisions to rebase not depending on other
1650 1652 # rebaseset. This means "source is ancestor of destination" for the second
1651 1653 # (and following) batches of revisions are not checked here. We rely on
1652 1654 # "defineparents" to do that check.
1653 1655 roots = list(repo.set('roots(%ld)', sortedsrc[0]))
1654 1656 if not roots:
1655 1657 raise error.Abort(_('no matching revisions'))
1656 1658 def revof(r):
1657 1659 return r.rev()
1658 1660 roots = sorted(roots, key=revof)
1659 1661 state = dict.fromkeys(rebaseset, revtodo)
1660 1662 emptyrebase = (len(sortedsrc) == 1)
1661 1663 for root in roots:
1662 1664 dest = repo[destmap[root.rev()]]
1663 1665 commonbase = root.ancestor(dest)
1664 1666 if commonbase == root:
1665 1667 raise error.Abort(_('source is ancestor of destination'))
1666 1668 if commonbase == dest:
1667 1669 wctx = repo[None]
1668 1670 if dest == wctx.p1():
1669 1671 # when rebasing to '.', it will use the current wd branch name
1670 1672 samebranch = root.branch() == wctx.branch()
1671 1673 else:
1672 1674 samebranch = root.branch() == dest.branch()
1673 1675 if not collapse and samebranch and dest in root.parents():
1674 1676 # mark the revision as done by setting its new revision
1675 1677 # equal to its old (current) revisions
1676 1678 state[root.rev()] = root.rev()
1677 1679 repo.ui.debug('source is a child of destination\n')
1678 1680 continue
1679 1681
1680 1682 emptyrebase = False
1681 1683 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1682 1684 if emptyrebase:
1683 1685 return None
1684 1686 for rev in sorted(state):
1685 1687 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1686 1688 # if all parents of this revision are done, then so is this revision
1687 1689 if parents and all((state.get(p) == p for p in parents)):
1688 1690 state[rev] = rev
1689 1691 return originalwd, destmap, state
1690 1692
1691 1693 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
1692 1694 keepf=False, fm=None):
1693 1695 """dispose of rebased revision at the end of the rebase
1694 1696
1695 1697 If `collapsedas` is not None, the rebase was a collapse whose result if the
1696 1698 `collapsedas` node.
1697 1699
1698 1700 If `keepf` is not True, the rebase has --keep set and no nodes should be
1699 1701 removed (but bookmarks still need to be moved).
1700 1702 """
1701 1703 tonode = repo.changelog.node
1702 1704 replacements = {}
1703 1705 moves = {}
1704 1706 for rev, newrev in sorted(state.items()):
1705 1707 if newrev >= 0 and newrev != rev:
1706 1708 oldnode = tonode(rev)
1707 1709 newnode = collapsedas or tonode(newrev)
1708 1710 moves[oldnode] = newnode
1709 1711 if not keepf:
1710 1712 if rev in skipped:
1711 1713 succs = ()
1712 1714 else:
1713 1715 succs = (newnode,)
1714 1716 replacements[oldnode] = succs
1715 1717 scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
1716 1718 if fm:
1717 1719 hf = fm.hexfunc
1718 1720 fl = fm.formatlist
1719 1721 fd = fm.formatdict
1720 1722 nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
1721 1723 for oldn, newn in replacements.iteritems()},
1722 1724 key="oldnode", value="newnodes")
1723 1725 fm.data(nodechanges=nodechanges)
1724 1726
1725 1727 def pullrebase(orig, ui, repo, *args, **opts):
1726 1728 'Call rebase after pull if the latter has been invoked with --rebase'
1727 1729 ret = None
1728 1730 if opts.get(r'rebase'):
1729 1731 if ui.configbool('commands', 'rebase.requiredest'):
1730 1732 msg = _('rebase destination required by configuration')
1731 1733 hint = _('use hg pull followed by hg rebase -d DEST')
1732 1734 raise error.Abort(msg, hint=hint)
1733 1735
1734 1736 with repo.wlock(), repo.lock():
1735 1737 if opts.get(r'update'):
1736 1738 del opts[r'update']
1737 1739 ui.debug('--update and --rebase are not compatible, ignoring '
1738 1740 'the update flag\n')
1739 1741
1740 1742 cmdutil.checkunfinished(repo)
1741 1743 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1742 1744 'please commit or shelve your changes first'))
1743 1745
1744 1746 revsprepull = len(repo)
1745 1747 origpostincoming = commands.postincoming
1746 1748 def _dummy(*args, **kwargs):
1747 1749 pass
1748 1750 commands.postincoming = _dummy
1749 1751 try:
1750 1752 ret = orig(ui, repo, *args, **opts)
1751 1753 finally:
1752 1754 commands.postincoming = origpostincoming
1753 1755 revspostpull = len(repo)
1754 1756 if revspostpull > revsprepull:
1755 1757 # --rev option from pull conflict with rebase own --rev
1756 1758 # dropping it
1757 1759 if r'rev' in opts:
1758 1760 del opts[r'rev']
1759 1761 # positional argument from pull conflicts with rebase's own
1760 1762 # --source.
1761 1763 if r'source' in opts:
1762 1764 del opts[r'source']
1763 1765 # revsprepull is the len of the repo, not revnum of tip.
1764 1766 destspace = list(repo.changelog.revs(start=revsprepull))
1765 1767 opts[r'_destspace'] = destspace
1766 1768 try:
1767 1769 rebase(ui, repo, **opts)
1768 1770 except error.NoMergeDestAbort:
1769 1771 # we can maybe update instead
1770 1772 rev, _a, _b = destutil.destupdate(repo)
1771 1773 if rev == repo['.'].rev():
1772 1774 ui.status(_('nothing to rebase\n'))
1773 1775 else:
1774 1776 ui.status(_('nothing to rebase - updating instead\n'))
1775 1777 # not passing argument to get the bare update behavior
1776 1778 # with warning and trumpets
1777 1779 commands.update(ui, repo)
1778 1780 else:
1779 1781 if opts.get(r'tool'):
1780 1782 raise error.Abort(_('--tool can only be used with --rebase'))
1781 1783 ret = orig(ui, repo, *args, **opts)
1782 1784
1783 1785 return ret
1784 1786
1785 1787 def _filterobsoleterevs(repo, revs):
1786 1788 """returns a set of the obsolete revisions in revs"""
1787 1789 return set(r for r in revs if repo[r].obsolete())
1788 1790
1789 1791 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
1790 1792 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
1791 1793
1792 1794 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
1793 1795 obsolete nodes to be rebased given in `rebaseobsrevs`.
1794 1796
1795 1797 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
1796 1798 without a successor in destination.
1797 1799
1798 1800 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
1799 1801 obsolete successors.
1800 1802 """
1801 1803 obsoletenotrebased = {}
1802 1804 obsoletewithoutsuccessorindestination = set([])
1803 1805 obsoleteextinctsuccessors = set([])
1804 1806
1805 1807 assert repo.filtername is None
1806 1808 cl = repo.changelog
1807 1809 nodemap = cl.nodemap
1808 1810 extinctnodes = set(cl.node(r) for r in repo.revs('extinct()'))
1809 1811 for srcrev in rebaseobsrevs:
1810 1812 srcnode = cl.node(srcrev)
1811 1813 destnode = cl.node(destmap[srcrev])
1812 1814 # XXX: more advanced APIs are required to handle split correctly
1813 1815 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
1814 1816 # obsutil.allsuccessors includes node itself
1815 1817 successors.remove(srcnode)
1816 1818 if successors.issubset(extinctnodes):
1817 1819 # all successors are extinct
1818 1820 obsoleteextinctsuccessors.add(srcrev)
1819 1821 if not successors:
1820 1822 # no successor
1821 1823 obsoletenotrebased[srcrev] = None
1822 1824 else:
1823 1825 for succnode in successors:
1824 1826 if succnode not in nodemap:
1825 1827 continue
1826 1828 if cl.isancestor(succnode, destnode):
1827 1829 obsoletenotrebased[srcrev] = nodemap[succnode]
1828 1830 break
1829 1831 else:
1830 1832 # If 'srcrev' has a successor in rebase set but none in
1831 1833 # destination (which would be catched above), we shall skip it
1832 1834 # and its descendants to avoid divergence.
1833 1835 if any(nodemap[s] in destmap for s in successors
1834 1836 if s in nodemap):
1835 1837 obsoletewithoutsuccessorindestination.add(srcrev)
1836 1838
1837 1839 return (
1838 1840 obsoletenotrebased,
1839 1841 obsoletewithoutsuccessorindestination,
1840 1842 obsoleteextinctsuccessors,
1841 1843 )
1842 1844
1843 1845 def summaryhook(ui, repo):
1844 1846 if not repo.vfs.exists('rebasestate'):
1845 1847 return
1846 1848 try:
1847 1849 rbsrt = rebaseruntime(repo, ui, {})
1848 1850 rbsrt.restorestatus()
1849 1851 state = rbsrt.state
1850 1852 except error.RepoLookupError:
1851 1853 # i18n: column positioning for "hg summary"
1852 1854 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1853 1855 ui.write(msg)
1854 1856 return
1855 1857 numrebased = len([i for i in state.itervalues() if i >= 0])
1856 1858 # i18n: column positioning for "hg summary"
1857 1859 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1858 1860 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1859 1861 ui.label(_('%d remaining'), 'rebase.remaining') %
1860 1862 (len(state) - numrebased)))
1861 1863
1862 1864 def uisetup(ui):
1863 1865 #Replace pull with a decorator to provide --rebase option
1864 1866 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1865 1867 entry[1].append(('', 'rebase', None,
1866 1868 _("rebase working directory to branch head")))
1867 1869 entry[1].append(('t', 'tool', '',
1868 1870 _("specify merge tool for rebase")))
1869 1871 cmdutil.summaryhooks.add('rebase', summaryhook)
1870 1872 cmdutil.unfinishedstates.append(
1871 1873 ['rebasestate', False, False, _('rebase in progress'),
1872 1874 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1873 1875 cmdutil.afterresolvedstates.append(
1874 1876 ['rebasestate', _('hg rebase --continue')])
@@ -1,324 +1,326 b''
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 215 saved backup bundle to $TESTTMP/repo1/repo2/skrepo/.hg/strip-backup/c83b1da5b1ae-f1e0beb9-backup.hg
216 216 rebase aborted
217 217
218 218 $ hg diff
219 219 $ hg status
220 220
221 221 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
222 222 @ 6:baf10c5166d4 test
223 223 | g
224 224 |
225 225 o 5:6343ca3eff20 test
226 226 | f
227 227 |
228 228 | o 4:e860deea161a test
229 229 | | e
230 230 | |
231 231 | o 3:055a42cdd887 test
232 232 | | d
233 233 | |
234 234 | o 2:177f92b77385 test
235 235 |/ c
236 236 |
237 237 o 1:d2ae7f538514 test
238 238 | b
239 239 |
240 240 o 0:cb9a9f314b8b test
241 241 a
242 242
243 243 Check dryrun working with --collapse when there is no conflict
244 244 $ hg rebase -s 2 -d 6 -n --collapse
245 245 rebasing 2:177f92b77385 "c"
246 246 rebasing 3:055a42cdd887 "d"
247 247 rebasing 4:e860deea161a "e"
248 248 there will be no conflict, you can rebase
249 249 rebase aborted
250 250
251 251 Check dryrun gives correct results when there is conflict in rebasing
252 252 Make a conflict:
253 253 $ hg up 6 -q
254 254 $ echo conflict>e
255 255 $ hg ci -Aqm "conflict with e"
256 256 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
257 257 @ 7:d2c195b28050 test
258 258 | conflict with e
259 259 |
260 260 o 6:baf10c5166d4 test
261 261 | g
262 262 |
263 263 o 5:6343ca3eff20 test
264 264 | f
265 265 |
266 266 | o 4:e860deea161a test
267 267 | | e
268 268 | |
269 269 | o 3:055a42cdd887 test
270 270 | | d
271 271 | |
272 272 | o 2:177f92b77385 test
273 273 |/ c
274 274 |
275 275 o 1:d2ae7f538514 test
276 276 | b
277 277 |
278 278 o 0:cb9a9f314b8b test
279 279 a
280 280
281 281 $ hg rebase -s 2 -d 7 -n
282 282 rebasing 2:177f92b77385 "c"
283 283 rebasing 3:055a42cdd887 "d"
284 284 rebasing 4:e860deea161a "e"
285 285 merging e
286 286 transaction abort!
287 287 rollback completed
288 288 hit a merge conflict
289 289 rebase aborted
290 [1]
290 291 $ hg diff
291 292 $ hg status
292 293 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
293 294 @ 7:d2c195b28050 test
294 295 | conflict with e
295 296 |
296 297 o 6:baf10c5166d4 test
297 298 | g
298 299 |
299 300 o 5:6343ca3eff20 test
300 301 | f
301 302 |
302 303 | o 4:e860deea161a test
303 304 | | e
304 305 | |
305 306 | o 3:055a42cdd887 test
306 307 | | d
307 308 | |
308 309 | o 2:177f92b77385 test
309 310 |/ c
310 311 |
311 312 o 1:d2ae7f538514 test
312 313 | b
313 314 |
314 315 o 0:cb9a9f314b8b test
315 316 a
316 317
317 318 Check dryrun working with --collapse when there is conflicts
318 319 $ hg rebase -s 2 -d 7 -n --collapse
319 320 rebasing 2:177f92b77385 "c"
320 321 rebasing 3:055a42cdd887 "d"
321 322 rebasing 4:e860deea161a "e"
322 323 merging e
323 324 hit a merge conflict
324 325 rebase aborted
326 [1]
General Comments 0
You need to be logged in to leave comments. Login now