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