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