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