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