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