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