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