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