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