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