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