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