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