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