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