##// END OF EJS Templates
rebase: un-wrap function signature since it fits in 80 columns
Augie Fackler -
r30674:eb69f78c default
parent child Browse files
Show More
@@ -1,1462 +1,1459
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 patch,
44 44 phases,
45 45 registrar,
46 46 repair,
47 47 repoview,
48 48 revset,
49 49 scmutil,
50 50 util,
51 51 )
52 52
53 53 release = lock.release
54 54 templateopts = commands.templateopts
55 55
56 56 # The following constants are used throughout the rebase module. The ordering of
57 57 # their values must be maintained.
58 58
59 59 # Indicates that a revision needs to be rebased
60 60 revtodo = -1
61 61 nullmerge = -2
62 62 revignored = -3
63 63 # successor in rebase destination
64 64 revprecursor = -4
65 65 # plain prune (no successor)
66 66 revpruned = -5
67 67 revskipped = (revignored, revprecursor, revpruned)
68 68
69 69 cmdtable = {}
70 70 command = cmdutil.command(cmdtable)
71 71 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
72 72 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
73 73 # be specifying the version(s) of Mercurial they are tested with, or
74 74 # leave the attribute unspecified.
75 75 testedwith = 'ships-with-hg-core'
76 76
77 77 def _nothingtorebase():
78 78 return 1
79 79
80 80 def _savegraft(ctx, extra):
81 81 s = ctx.extra().get('source', None)
82 82 if s is not None:
83 83 extra['source'] = s
84 84 s = ctx.extra().get('intermediate-source', None)
85 85 if s is not None:
86 86 extra['intermediate-source'] = s
87 87
88 88 def _savebranch(ctx, extra):
89 89 extra['branch'] = ctx.branch()
90 90
91 91 def _makeextrafn(copiers):
92 92 """make an extrafn out of the given copy-functions.
93 93
94 94 A copy function takes a context and an extra dict, and mutates the
95 95 extra dict as needed based on the given context.
96 96 """
97 97 def extrafn(ctx, extra):
98 98 for c in copiers:
99 99 c(ctx, extra)
100 100 return extrafn
101 101
102 102 def _destrebase(repo, sourceset, destspace=None):
103 103 """small wrapper around destmerge to pass the right extra args
104 104
105 105 Please wrap destutil.destmerge instead."""
106 106 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
107 107 onheadcheck=False, destspace=destspace)
108 108
109 109 revsetpredicate = registrar.revsetpredicate()
110 110
111 111 @revsetpredicate('_destrebase')
112 112 def _revsetdestrebase(repo, subset, x):
113 113 # ``_rebasedefaultdest()``
114 114
115 115 # default destination for rebase.
116 116 # # XXX: Currently private because I expect the signature to change.
117 117 # # XXX: - bailing out in case of ambiguity vs returning all data.
118 118 # i18n: "_rebasedefaultdest" is a keyword
119 119 sourceset = None
120 120 if x is not None:
121 121 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
122 122 return subset & revset.baseset([_destrebase(repo, sourceset)])
123 123
124 124 class rebaseruntime(object):
125 125 """This class is a container for rebase runtime state"""
126 126 def __init__(self, repo, ui, opts=None):
127 127 if opts is None:
128 128 opts = {}
129 129
130 130 self.repo = repo
131 131 self.ui = ui
132 132 self.opts = opts
133 133 self.originalwd = None
134 134 self.external = nullrev
135 135 # Mapping between the old revision id and either what is the new rebased
136 136 # revision or what needs to be done with the old revision. The state
137 137 # dict will be what contains most of the rebase progress state.
138 138 self.state = {}
139 139 self.activebookmark = None
140 140 self.currentbookmarks = None
141 141 self.target = None
142 142 self.skipped = set()
143 143 self.targetancestors = set()
144 144
145 145 self.collapsef = opts.get('collapse', False)
146 146 self.collapsemsg = cmdutil.logmessage(ui, opts)
147 147 self.date = opts.get('date', None)
148 148
149 149 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
150 150 self.extrafns = [_savegraft]
151 151 if e:
152 152 self.extrafns = [e]
153 153
154 154 self.keepf = opts.get('keep', False)
155 155 self.keepbranchesf = opts.get('keepbranches', False)
156 156 # keepopen is not meant for use on the command line, but by
157 157 # other extensions
158 158 self.keepopen = opts.get('keepopen', False)
159 159 self.obsoletenotrebased = {}
160 160
161 161 def restorestatus(self):
162 162 """Restore a previously stored status"""
163 163 repo = self.repo
164 164 keepbranches = None
165 165 target = None
166 166 collapse = False
167 167 external = nullrev
168 168 activebookmark = None
169 169 state = {}
170 170
171 171 try:
172 172 f = repo.vfs("rebasestate")
173 173 for i, l in enumerate(f.read().splitlines()):
174 174 if i == 0:
175 175 originalwd = repo[l].rev()
176 176 elif i == 1:
177 177 target = repo[l].rev()
178 178 elif i == 2:
179 179 external = repo[l].rev()
180 180 elif i == 3:
181 181 collapse = bool(int(l))
182 182 elif i == 4:
183 183 keep = bool(int(l))
184 184 elif i == 5:
185 185 keepbranches = bool(int(l))
186 186 elif i == 6 and not (len(l) == 81 and ':' in l):
187 187 # line 6 is a recent addition, so for backwards
188 188 # compatibility check that the line doesn't look like the
189 189 # oldrev:newrev lines
190 190 activebookmark = l
191 191 else:
192 192 oldrev, newrev = l.split(':')
193 193 if newrev in (str(nullmerge), str(revignored),
194 194 str(revprecursor), str(revpruned)):
195 195 state[repo[oldrev].rev()] = int(newrev)
196 196 elif newrev == nullid:
197 197 state[repo[oldrev].rev()] = revtodo
198 198 # Legacy compat special case
199 199 else:
200 200 state[repo[oldrev].rev()] = repo[newrev].rev()
201 201
202 202 except IOError as err:
203 203 if err.errno != errno.ENOENT:
204 204 raise
205 205 cmdutil.wrongtooltocontinue(repo, _('rebase'))
206 206
207 207 if keepbranches is None:
208 208 raise error.Abort(_('.hg/rebasestate is incomplete'))
209 209
210 210 skipped = set()
211 211 # recompute the set of skipped revs
212 212 if not collapse:
213 213 seen = set([target])
214 214 for old, new in sorted(state.items()):
215 215 if new != revtodo and new in seen:
216 216 skipped.add(old)
217 217 seen.add(new)
218 218 repo.ui.debug('computed skipped revs: %s\n' %
219 219 (' '.join(str(r) for r in sorted(skipped)) or None))
220 220 repo.ui.debug('rebase status resumed\n')
221 221 _setrebasesetvisibility(repo, state.keys())
222 222
223 223 self.originalwd = originalwd
224 224 self.target = target
225 225 self.state = state
226 226 self.skipped = skipped
227 227 self.collapsef = collapse
228 228 self.keepf = keep
229 229 self.keepbranchesf = keepbranches
230 230 self.external = external
231 231 self.activebookmark = activebookmark
232 232
233 233 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, target):
234 234 """Compute structures necessary for skipping obsolete revisions
235 235
236 236 rebaserevs: iterable of all revisions that are to be rebased
237 237 obsoleterevs: iterable of all obsolete revisions in rebaseset
238 238 target: a destination revision for the rebase operation
239 239 """
240 240 self.obsoletenotrebased = {}
241 241 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
242 242 default=True):
243 243 return
244 244 rebaseset = set(rebaserevs)
245 245 obsoleteset = set(obsoleterevs)
246 246 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
247 247 obsoleteset, target)
248 248 skippedset = set(self.obsoletenotrebased)
249 249 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
250 250
251 251 def _prepareabortorcontinue(self, isabort):
252 252 try:
253 253 self.restorestatus()
254 254 self.collapsemsg = restorecollapsemsg(self.repo)
255 255 except error.RepoLookupError:
256 256 if isabort:
257 257 clearstatus(self.repo)
258 258 clearcollapsemsg(self.repo)
259 259 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
260 260 ' only broken state is cleared)\n'))
261 261 return 0
262 262 else:
263 263 msg = _('cannot continue inconsistent rebase')
264 264 hint = _('use "hg rebase --abort" to clear broken state')
265 265 raise error.Abort(msg, hint=hint)
266 266 if isabort:
267 267 return abort(self.repo, self.originalwd, self.target,
268 268 self.state, activebookmark=self.activebookmark)
269 269
270 270 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
271 271 self._handleskippingobsolete(self.state.keys(), obsrevs, self.target)
272 272
273 273 def _preparenewrebase(self, dest, rebaseset):
274 274 if dest is None:
275 275 return _nothingtorebase()
276 276
277 277 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
278 278 if (not (self.keepf or allowunstable)
279 279 and self.repo.revs('first(children(%ld) - %ld)',
280 280 rebaseset, rebaseset)):
281 281 raise error.Abort(
282 282 _("can't remove original changesets with"
283 283 " unrebased descendants"),
284 284 hint=_('use --keep to keep original changesets'))
285 285
286 286 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
287 287 self._handleskippingobsolete(rebaseset, obsrevs, dest)
288 288
289 289 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
290 290 self.obsoletenotrebased)
291 291
292 292 if not result:
293 293 # Empty state built, nothing to rebase
294 294 self.ui.status(_('nothing to rebase\n'))
295 295 return _nothingtorebase()
296 296
297 297 root = min(rebaseset)
298 298 if not self.keepf and not self.repo[root].mutable():
299 299 raise error.Abort(_("can't rebase public changeset %s")
300 300 % self.repo[root],
301 301 hint=_("see 'hg help phases' for details"))
302 302
303 303 (self.originalwd, self.target, self.state) = result
304 304 if self.collapsef:
305 305 self.targetancestors = self.repo.changelog.ancestors(
306 306 [self.target],
307 307 inclusive=True)
308 308 self.external = externalparent(self.repo, self.state,
309 309 self.targetancestors)
310 310
311 311 if dest.closesbranch() and not self.keepbranchesf:
312 312 self.ui.status(_('reopening closed branch head %s\n') % dest)
313 313
314 314 def _performrebase(self):
315 315 repo, ui, opts = self.repo, self.ui, self.opts
316 316 if self.keepbranchesf:
317 317 # insert _savebranch at the start of extrafns so if
318 318 # there's a user-provided extrafn it can clobber branch if
319 319 # desired
320 320 self.extrafns.insert(0, _savebranch)
321 321 if self.collapsef:
322 322 branches = set()
323 323 for rev in self.state:
324 324 branches.add(repo[rev].branch())
325 325 if len(branches) > 1:
326 326 raise error.Abort(_('cannot collapse multiple named '
327 327 'branches'))
328 328
329 329 # Rebase
330 330 if not self.targetancestors:
331 331 self.targetancestors = repo.changelog.ancestors([self.target],
332 332 inclusive=True)
333 333
334 334 # Keep track of the current bookmarks in order to reset them later
335 335 self.currentbookmarks = repo._bookmarks.copy()
336 336 self.activebookmark = self.activebookmark or repo._activebookmark
337 337 if self.activebookmark:
338 338 bookmarks.deactivate(repo)
339 339
340 340 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
341 341 cands = [k for k, v in self.state.iteritems() if v == revtodo]
342 342 total = len(cands)
343 343 pos = 0
344 344 for rev in sortedrevs:
345 345 ctx = repo[rev]
346 346 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
347 347 ctx.description().split('\n', 1)[0])
348 348 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
349 349 if names:
350 350 desc += ' (%s)' % ' '.join(names)
351 351 if self.state[rev] == revtodo:
352 352 pos += 1
353 353 ui.status(_('rebasing %s\n') % desc)
354 354 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
355 355 _('changesets'), total)
356 356 p1, p2, base = defineparents(repo, rev, self.target,
357 357 self.state,
358 358 self.targetancestors,
359 359 self.obsoletenotrebased)
360 360 storestatus(repo, self.originalwd, self.target,
361 361 self.state, self.collapsef, self.keepf,
362 362 self.keepbranchesf, self.external,
363 363 self.activebookmark)
364 364 storecollapsemsg(repo, self.collapsemsg)
365 365 if len(repo[None].parents()) == 2:
366 366 repo.ui.debug('resuming interrupted rebase\n')
367 367 else:
368 368 try:
369 369 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
370 370 'rebase')
371 371 stats = rebasenode(repo, rev, p1, base, self.state,
372 372 self.collapsef, self.target)
373 373 if stats and stats[3] > 0:
374 374 raise error.InterventionRequired(
375 375 _('unresolved conflicts (see hg '
376 376 'resolve, then hg rebase --continue)'))
377 377 finally:
378 378 ui.setconfig('ui', 'forcemerge', '', 'rebase')
379 379 if not self.collapsef:
380 380 merging = p2 != nullrev
381 381 editform = cmdutil.mergeeditform(merging, 'rebase')
382 382 editor = cmdutil.getcommiteditor(editform=editform, **opts)
383 383 newnode = concludenode(repo, rev, p1, p2,
384 384 extrafn=_makeextrafn(self.extrafns),
385 385 editor=editor,
386 386 keepbranches=self.keepbranchesf,
387 387 date=self.date)
388 388 else:
389 389 # Skip commit if we are collapsing
390 390 repo.dirstate.beginparentchange()
391 391 repo.setparents(repo[p1].node())
392 392 repo.dirstate.endparentchange()
393 393 newnode = None
394 394 # Update the state
395 395 if newnode is not None:
396 396 self.state[rev] = repo[newnode].rev()
397 397 ui.debug('rebased as %s\n' % short(newnode))
398 398 else:
399 399 if not self.collapsef:
400 400 ui.warn(_('note: rebase of %d:%s created no changes '
401 401 'to commit\n') % (rev, ctx))
402 402 self.skipped.add(rev)
403 403 self.state[rev] = p1
404 404 ui.debug('next revision set to %s\n' % p1)
405 405 elif self.state[rev] == nullmerge:
406 406 ui.debug('ignoring null merge rebase of %s\n' % rev)
407 407 elif self.state[rev] == revignored:
408 408 ui.status(_('not rebasing ignored %s\n') % desc)
409 409 elif self.state[rev] == revprecursor:
410 410 targetctx = repo[self.obsoletenotrebased[rev]]
411 411 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
412 412 targetctx.description().split('\n', 1)[0])
413 413 msg = _('note: not rebasing %s, already in destination as %s\n')
414 414 ui.status(msg % (desc, desctarget))
415 415 elif self.state[rev] == revpruned:
416 416 msg = _('note: not rebasing %s, it has no successor\n')
417 417 ui.status(msg % desc)
418 418 else:
419 419 ui.status(_('already rebased %s as %s\n') %
420 420 (desc, repo[self.state[rev]]))
421 421
422 422 ui.progress(_('rebasing'), None)
423 423 ui.note(_('rebase merging completed\n'))
424 424
425 425 def _finishrebase(self):
426 426 repo, ui, opts = self.repo, self.ui, self.opts
427 427 if self.collapsef and not self.keepopen:
428 428 p1, p2, _base = defineparents(repo, min(self.state),
429 429 self.target, self.state,
430 430 self.targetancestors,
431 431 self.obsoletenotrebased)
432 432 editopt = opts.get('edit')
433 433 editform = 'rebase.collapse'
434 434 if self.collapsemsg:
435 435 commitmsg = self.collapsemsg
436 436 else:
437 437 commitmsg = 'Collapsed revision'
438 438 for rebased in self.state:
439 439 if rebased not in self.skipped and\
440 440 self.state[rebased] > nullmerge:
441 441 commitmsg += '\n* %s' % repo[rebased].description()
442 442 editopt = True
443 443 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
444 444 revtoreuse = max(self.state)
445 445 newnode = concludenode(repo, revtoreuse, p1, self.external,
446 446 commitmsg=commitmsg,
447 447 extrafn=_makeextrafn(self.extrafns),
448 448 editor=editor,
449 449 keepbranches=self.keepbranchesf,
450 450 date=self.date)
451 451 if newnode is None:
452 452 newrev = self.target
453 453 else:
454 454 newrev = repo[newnode].rev()
455 455 for oldrev in self.state.iterkeys():
456 456 if self.state[oldrev] > nullmerge:
457 457 self.state[oldrev] = newrev
458 458
459 459 if 'qtip' in repo.tags():
460 460 updatemq(repo, self.state, self.skipped, **opts)
461 461
462 462 if self.currentbookmarks:
463 463 # Nodeids are needed to reset bookmarks
464 464 nstate = {}
465 465 for k, v in self.state.iteritems():
466 466 if v > nullmerge:
467 467 nstate[repo[k].node()] = repo[v].node()
468 468 elif v == revprecursor:
469 469 succ = self.obsoletenotrebased[k]
470 470 nstate[repo[k].node()] = repo[succ].node()
471 471 # XXX this is the same as dest.node() for the non-continue path --
472 472 # this should probably be cleaned up
473 473 targetnode = repo[self.target].node()
474 474
475 475 # restore original working directory
476 476 # (we do this before stripping)
477 477 newwd = self.state.get(self.originalwd, self.originalwd)
478 478 if newwd == revprecursor:
479 479 newwd = self.obsoletenotrebased[self.originalwd]
480 480 elif newwd < 0:
481 481 # original directory is a parent of rebase set root or ignored
482 482 newwd = self.originalwd
483 483 if newwd not in [c.rev() for c in repo[None].parents()]:
484 484 ui.note(_("update back to initial working directory parent\n"))
485 485 hg.updaterepo(repo, newwd, False)
486 486
487 487 if self.currentbookmarks:
488 488 with repo.transaction('bookmark') as tr:
489 489 updatebookmarks(repo, targetnode, nstate,
490 490 self.currentbookmarks, tr)
491 491 if self.activebookmark not in repo._bookmarks:
492 492 # active bookmark was divergent one and has been deleted
493 493 self.activebookmark = None
494 494
495 495 if not self.keepf:
496 496 collapsedas = None
497 497 if self.collapsef:
498 498 collapsedas = newnode
499 499 clearrebased(ui, repo, self.state, self.skipped, collapsedas)
500 500
501 501 clearstatus(repo)
502 502 clearcollapsemsg(repo)
503 503
504 504 ui.note(_("rebase completed\n"))
505 505 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
506 506 if self.skipped:
507 507 skippedlen = len(self.skipped)
508 508 ui.note(_("%d revisions have been skipped\n") % skippedlen)
509 509
510 510 if (self.activebookmark and
511 511 repo['.'].node() == repo._bookmarks[self.activebookmark]):
512 512 bookmarks.activate(repo, self.activebookmark)
513 513
514 514 @command('rebase',
515 515 [('s', 'source', '',
516 516 _('rebase the specified changeset and descendants'), _('REV')),
517 517 ('b', 'base', '',
518 518 _('rebase everything from branching point of specified changeset'),
519 519 _('REV')),
520 520 ('r', 'rev', [],
521 521 _('rebase these revisions'),
522 522 _('REV')),
523 523 ('d', 'dest', '',
524 524 _('rebase onto the specified changeset'), _('REV')),
525 525 ('', 'collapse', False, _('collapse the rebased changesets')),
526 526 ('m', 'message', '',
527 527 _('use text as collapse commit message'), _('TEXT')),
528 528 ('e', 'edit', False, _('invoke editor on commit messages')),
529 529 ('l', 'logfile', '',
530 530 _('read collapse commit message from file'), _('FILE')),
531 531 ('k', 'keep', False, _('keep original changesets')),
532 532 ('', 'keepbranches', False, _('keep original branch names')),
533 533 ('D', 'detach', False, _('(DEPRECATED)')),
534 534 ('i', 'interactive', False, _('(DEPRECATED)')),
535 535 ('t', 'tool', '', _('specify merge tool')),
536 536 ('c', 'continue', False, _('continue an interrupted rebase')),
537 537 ('a', 'abort', False, _('abort an interrupted rebase'))] +
538 538 templateopts,
539 539 _('[-s REV | -b REV] [-d REV] [OPTION]'))
540 540 def rebase(ui, repo, **opts):
541 541 """move changeset (and descendants) to a different branch
542 542
543 543 Rebase uses repeated merging to graft changesets from one part of
544 544 history (the source) onto another (the destination). This can be
545 545 useful for linearizing *local* changes relative to a master
546 546 development tree.
547 547
548 548 Published commits cannot be rebased (see :hg:`help phases`).
549 549 To copy commits, see :hg:`help graft`.
550 550
551 551 If you don't specify a destination changeset (``-d/--dest``), rebase
552 552 will use the same logic as :hg:`merge` to pick a destination. if
553 553 the current branch contains exactly one other head, the other head
554 554 is merged with by default. Otherwise, an explicit revision with
555 555 which to merge with must be provided. (destination changeset is not
556 556 modified by rebasing, but new changesets are added as its
557 557 descendants.)
558 558
559 559 Here are the ways to select changesets:
560 560
561 561 1. Explicitly select them using ``--rev``.
562 562
563 563 2. Use ``--source`` to select a root changeset and include all of its
564 564 descendants.
565 565
566 566 3. Use ``--base`` to select a changeset; rebase will find ancestors
567 567 and their descendants which are not also ancestors of the destination.
568 568
569 569 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
570 570 rebase will use ``--base .`` as above.
571 571
572 572 Rebase will destroy original changesets unless you use ``--keep``.
573 573 It will also move your bookmarks (even if you do).
574 574
575 575 Some changesets may be dropped if they do not contribute changes
576 576 (e.g. merges from the destination branch).
577 577
578 578 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
579 579 a named branch with two heads. You will need to explicitly specify source
580 580 and/or destination.
581 581
582 582 If you need to use a tool to automate merge/conflict decisions, you
583 583 can specify one with ``--tool``, see :hg:`help merge-tools`.
584 584 As a caveat: the tool will not be used to mediate when a file was
585 585 deleted, there is no hook presently available for this.
586 586
587 587 If a rebase is interrupted to manually resolve a conflict, it can be
588 588 continued with --continue/-c or aborted with --abort/-a.
589 589
590 590 .. container:: verbose
591 591
592 592 Examples:
593 593
594 594 - move "local changes" (current commit back to branching point)
595 595 to the current branch tip after a pull::
596 596
597 597 hg rebase
598 598
599 599 - move a single changeset to the stable branch::
600 600
601 601 hg rebase -r 5f493448 -d stable
602 602
603 603 - splice a commit and all its descendants onto another part of history::
604 604
605 605 hg rebase --source c0c3 --dest 4cf9
606 606
607 607 - rebase everything on a branch marked by a bookmark onto the
608 608 default branch::
609 609
610 610 hg rebase --base myfeature --dest default
611 611
612 612 - collapse a sequence of changes into a single commit::
613 613
614 614 hg rebase --collapse -r 1520:1525 -d .
615 615
616 616 - move a named branch while preserving its name::
617 617
618 618 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
619 619
620 620 Returns 0 on success, 1 if nothing to rebase or there are
621 621 unresolved conflicts.
622 622
623 623 """
624 624 rbsrt = rebaseruntime(repo, ui, opts)
625 625
626 626 lock = wlock = None
627 627 try:
628 628 wlock = repo.wlock()
629 629 lock = repo.lock()
630 630
631 631 # Validate input and define rebasing points
632 632 destf = opts.get('dest', None)
633 633 srcf = opts.get('source', None)
634 634 basef = opts.get('base', None)
635 635 revf = opts.get('rev', [])
636 636 # search default destination in this space
637 637 # used in the 'hg pull --rebase' case, see issue 5214.
638 638 destspace = opts.get('_destspace')
639 639 contf = opts.get('continue')
640 640 abortf = opts.get('abort')
641 641 if opts.get('interactive'):
642 642 try:
643 643 if extensions.find('histedit'):
644 644 enablehistedit = ''
645 645 except KeyError:
646 646 enablehistedit = " --config extensions.histedit="
647 647 help = "hg%s help -e histedit" % enablehistedit
648 648 msg = _("interactive history editing is supported by the "
649 649 "'histedit' extension (see \"%s\")") % help
650 650 raise error.Abort(msg)
651 651
652 652 if rbsrt.collapsemsg and not rbsrt.collapsef:
653 653 raise error.Abort(
654 654 _('message can only be specified with collapse'))
655 655
656 656 if contf or abortf:
657 657 if contf and abortf:
658 658 raise error.Abort(_('cannot use both abort and continue'))
659 659 if rbsrt.collapsef:
660 660 raise error.Abort(
661 661 _('cannot use collapse with continue or abort'))
662 662 if srcf or basef or destf:
663 663 raise error.Abort(
664 664 _('abort and continue do not allow specifying revisions'))
665 665 if abortf and opts.get('tool', False):
666 666 ui.warn(_('tool option will be ignored\n'))
667 667 if contf:
668 668 ms = mergemod.mergestate.read(repo)
669 669 mergeutil.checkunresolved(ms)
670 670
671 671 retcode = rbsrt._prepareabortorcontinue(abortf)
672 672 if retcode is not None:
673 673 return retcode
674 674 else:
675 675 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
676 676 destspace=destspace)
677 677 retcode = rbsrt._preparenewrebase(dest, rebaseset)
678 678 if retcode is not None:
679 679 return retcode
680 680
681 681 rbsrt._performrebase()
682 682 rbsrt._finishrebase()
683 683 finally:
684 684 release(lock, wlock)
685 685
686 686 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],
687 687 destspace=None):
688 688 """use revisions argument to define destination and rebase set
689 689 """
690 690 # destspace is here to work around issues with `hg pull --rebase` see
691 691 # issue5214 for details
692 692 if srcf and basef:
693 693 raise error.Abort(_('cannot specify both a source and a base'))
694 694 if revf and basef:
695 695 raise error.Abort(_('cannot specify both a revision and a base'))
696 696 if revf and srcf:
697 697 raise error.Abort(_('cannot specify both a revision and a source'))
698 698
699 699 cmdutil.checkunfinished(repo)
700 700 cmdutil.bailifchanged(repo)
701 701
702 702 if destf:
703 703 dest = scmutil.revsingle(repo, destf)
704 704
705 705 if revf:
706 706 rebaseset = scmutil.revrange(repo, revf)
707 707 if not rebaseset:
708 708 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
709 709 return None, None
710 710 elif srcf:
711 711 src = scmutil.revrange(repo, [srcf])
712 712 if not src:
713 713 ui.status(_('empty "source" revision set - nothing to rebase\n'))
714 714 return None, None
715 715 rebaseset = repo.revs('(%ld)::', src)
716 716 assert rebaseset
717 717 else:
718 718 base = scmutil.revrange(repo, [basef or '.'])
719 719 if not base:
720 720 ui.status(_('empty "base" revision set - '
721 721 "can't compute rebase set\n"))
722 722 return None, None
723 723 if not destf:
724 724 dest = repo[_destrebase(repo, base, destspace=destspace)]
725 725 destf = str(dest)
726 726
727 727 roots = [] # selected children of branching points
728 728 bpbase = {} # {branchingpoint: [origbase]}
729 729 for b in base: # group bases by branching points
730 730 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
731 731 bpbase[bp] = bpbase.get(bp, []) + [b]
732 732 if None in bpbase:
733 733 # emulate the old behavior, showing "nothing to rebase" (a better
734 734 # behavior may be abort with "cannot find branching point" error)
735 735 bpbase.clear()
736 736 for bp, bs in bpbase.iteritems(): # calculate roots
737 737 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
738 738
739 739 rebaseset = repo.revs('%ld::', roots)
740 740
741 741 if not rebaseset:
742 742 # transform to list because smartsets are not comparable to
743 743 # lists. This should be improved to honor laziness of
744 744 # smartset.
745 745 if list(base) == [dest.rev()]:
746 746 if basef:
747 747 ui.status(_('nothing to rebase - %s is both "base"'
748 748 ' and destination\n') % dest)
749 749 else:
750 750 ui.status(_('nothing to rebase - working directory '
751 751 'parent is also destination\n'))
752 752 elif not repo.revs('%ld - ::%d', base, dest):
753 753 if basef:
754 754 ui.status(_('nothing to rebase - "base" %s is '
755 755 'already an ancestor of destination '
756 756 '%s\n') %
757 757 ('+'.join(str(repo[r]) for r in base),
758 758 dest))
759 759 else:
760 760 ui.status(_('nothing to rebase - working '
761 761 'directory parent is already an '
762 762 'ancestor of destination %s\n') % dest)
763 763 else: # can it happen?
764 764 ui.status(_('nothing to rebase from %s to %s\n') %
765 765 ('+'.join(str(repo[r]) for r in base), dest))
766 766 return None, None
767 767
768 768 if not destf:
769 769 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
770 770 destf = str(dest)
771 771
772 772 return dest, rebaseset
773 773
774 774 def externalparent(repo, state, targetancestors):
775 775 """Return the revision that should be used as the second parent
776 776 when the revisions in state is collapsed on top of targetancestors.
777 777 Abort if there is more than one parent.
778 778 """
779 779 parents = set()
780 780 source = min(state)
781 781 for rev in state:
782 782 if rev == source:
783 783 continue
784 784 for p in repo[rev].parents():
785 785 if (p.rev() not in state
786 786 and p.rev() not in targetancestors):
787 787 parents.add(p.rev())
788 788 if not parents:
789 789 return nullrev
790 790 if len(parents) == 1:
791 791 return parents.pop()
792 792 raise error.Abort(_('unable to collapse on top of %s, there is more '
793 793 'than one external parent: %s') %
794 794 (max(targetancestors),
795 795 ', '.join(str(p) for p in sorted(parents))))
796 796
797 797 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
798 798 keepbranches=False, date=None):
799 799 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
800 800 but also store useful information in extra.
801 801 Return node of committed revision.'''
802 802 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
803 803 try:
804 804 repo.setparents(repo[p1].node(), repo[p2].node())
805 805 ctx = repo[rev]
806 806 if commitmsg is None:
807 807 commitmsg = ctx.description()
808 808 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
809 809 extra = {'rebase_source': ctx.hex()}
810 810 if extrafn:
811 811 extrafn(ctx, extra)
812 812
813 813 backup = repo.ui.backupconfig('phases', 'new-commit')
814 814 try:
815 815 targetphase = max(ctx.phase(), phases.draft)
816 816 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
817 817 if keepbranch:
818 818 repo.ui.setconfig('ui', 'allowemptycommit', True)
819 819 # Commit might fail if unresolved files exist
820 820 if date is None:
821 821 date = ctx.date()
822 822 newnode = repo.commit(text=commitmsg, user=ctx.user(),
823 823 date=date, extra=extra, editor=editor)
824 824 finally:
825 825 repo.ui.restoreconfig(backup)
826 826
827 827 repo.dirstate.setbranch(repo[newnode].branch())
828 828 dsguard.close()
829 829 return newnode
830 830 finally:
831 831 release(dsguard)
832 832
833 833 def rebasenode(repo, rev, p1, base, state, collapse, target):
834 834 'Rebase a single revision rev on top of p1 using base as merge ancestor'
835 835 # Merge phase
836 836 # Update to target and merge it with local
837 837 if repo['.'].rev() != p1:
838 838 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
839 839 mergemod.update(repo, p1, False, True)
840 840 else:
841 841 repo.ui.debug(" already in target\n")
842 842 repo.dirstate.write(repo.currenttransaction())
843 843 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
844 844 if base is not None:
845 845 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
846 846 # When collapsing in-place, the parent is the common ancestor, we
847 847 # have to allow merging with it.
848 848 stats = mergemod.update(repo, rev, True, True, base, collapse,
849 849 labels=['dest', 'source'])
850 850 if collapse:
851 851 copies.duplicatecopies(repo, rev, target)
852 852 else:
853 853 # If we're not using --collapse, we need to
854 854 # duplicate copies between the revision we're
855 855 # rebasing and its first parent, but *not*
856 856 # duplicate any copies that have already been
857 857 # performed in the destination.
858 858 p1rev = repo[rev].p1().rev()
859 859 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
860 860 return stats
861 861
862 862 def nearestrebased(repo, rev, state):
863 863 """return the nearest ancestors of rev in the rebase result"""
864 864 rebased = [r for r in state if state[r] > nullmerge]
865 865 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
866 866 if candidates:
867 867 return state[candidates.first()]
868 868 else:
869 869 return None
870 870
871 def _checkobsrebase(repo, ui,
872 rebaseobsrevs,
873 rebasesetrevs,
874 rebaseobsskipped):
871 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
875 872 """
876 873 Abort if rebase will create divergence or rebase is noop because of markers
877 874
878 875 `rebaseobsrevs`: set of obsolete revision in source
879 876 `rebasesetrevs`: set of revisions to be rebased from source
880 877 `rebaseobsskipped`: set of revisions from source skipped because they have
881 878 successors in destination
882 879 """
883 880 # Obsolete node with successors not in dest leads to divergence
884 881 divergenceok = ui.configbool('experimental',
885 882 'allowdivergence')
886 883 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
887 884
888 885 if divergencebasecandidates and not divergenceok:
889 886 divhashes = (str(repo[r])
890 887 for r in divergencebasecandidates)
891 888 msg = _("this rebase will cause "
892 889 "divergences from: %s")
893 890 h = _("to force the rebase please set "
894 891 "experimental.allowdivergence=True")
895 892 raise error.Abort(msg % (",".join(divhashes),), hint=h)
896 893
897 894 def defineparents(repo, rev, target, state, targetancestors,
898 895 obsoletenotrebased):
899 896 'Return the new parent relationship of the revision that will be rebased'
900 897 parents = repo[rev].parents()
901 898 p1 = p2 = nullrev
902 899 rp1 = None
903 900
904 901 p1n = parents[0].rev()
905 902 if p1n in targetancestors:
906 903 p1 = target
907 904 elif p1n in state:
908 905 if state[p1n] == nullmerge:
909 906 p1 = target
910 907 elif state[p1n] in revskipped:
911 908 p1 = nearestrebased(repo, p1n, state)
912 909 if p1 is None:
913 910 p1 = target
914 911 else:
915 912 p1 = state[p1n]
916 913 else: # p1n external
917 914 p1 = target
918 915 p2 = p1n
919 916
920 917 if len(parents) == 2 and parents[1].rev() not in targetancestors:
921 918 p2n = parents[1].rev()
922 919 # interesting second parent
923 920 if p2n in state:
924 921 if p1 == target: # p1n in targetancestors or external
925 922 p1 = state[p2n]
926 923 if p1 == revprecursor:
927 924 rp1 = obsoletenotrebased[p2n]
928 925 elif state[p2n] in revskipped:
929 926 p2 = nearestrebased(repo, p2n, state)
930 927 if p2 is None:
931 928 # no ancestors rebased yet, detach
932 929 p2 = target
933 930 else:
934 931 p2 = state[p2n]
935 932 else: # p2n external
936 933 if p2 != nullrev: # p1n external too => rev is a merged revision
937 934 raise error.Abort(_('cannot use revision %d as base, result '
938 935 'would have 3 parents') % rev)
939 936 p2 = p2n
940 937 repo.ui.debug(" future parents are %d and %d\n" %
941 938 (repo[rp1 or p1].rev(), repo[p2].rev()))
942 939
943 940 if not any(p.rev() in state for p in parents):
944 941 # Case (1) root changeset of a non-detaching rebase set.
945 942 # Let the merge mechanism find the base itself.
946 943 base = None
947 944 elif not repo[rev].p2():
948 945 # Case (2) detaching the node with a single parent, use this parent
949 946 base = repo[rev].p1().rev()
950 947 else:
951 948 # Assuming there is a p1, this is the case where there also is a p2.
952 949 # We are thus rebasing a merge and need to pick the right merge base.
953 950 #
954 951 # Imagine we have:
955 952 # - M: current rebase revision in this step
956 953 # - A: one parent of M
957 954 # - B: other parent of M
958 955 # - D: destination of this merge step (p1 var)
959 956 #
960 957 # Consider the case where D is a descendant of A or B and the other is
961 958 # 'outside'. In this case, the right merge base is the D ancestor.
962 959 #
963 960 # An informal proof, assuming A is 'outside' and B is the D ancestor:
964 961 #
965 962 # If we pick B as the base, the merge involves:
966 963 # - changes from B to M (actual changeset payload)
967 964 # - changes from B to D (induced by rebase) as D is a rebased
968 965 # version of B)
969 966 # Which exactly represent the rebase operation.
970 967 #
971 968 # If we pick A as the base, the merge involves:
972 969 # - changes from A to M (actual changeset payload)
973 970 # - changes from A to D (with include changes between unrelated A and B
974 971 # plus changes induced by rebase)
975 972 # Which does not represent anything sensible and creates a lot of
976 973 # conflicts. A is thus not the right choice - B is.
977 974 #
978 975 # Note: The base found in this 'proof' is only correct in the specified
979 976 # case. This base does not make sense if is not D a descendant of A or B
980 977 # or if the other is not parent 'outside' (especially not if the other
981 978 # parent has been rebased). The current implementation does not
982 979 # make it feasible to consider different cases separately. In these
983 980 # other cases we currently just leave it to the user to correctly
984 981 # resolve an impossible merge using a wrong ancestor.
985 982 #
986 983 # xx, p1 could be -4, and both parents could probably be -4...
987 984 for p in repo[rev].parents():
988 985 if state.get(p.rev()) == p1:
989 986 base = p.rev()
990 987 break
991 988 else: # fallback when base not found
992 989 base = None
993 990
994 991 # Raise because this function is called wrong (see issue 4106)
995 992 raise AssertionError('no base found to rebase on '
996 993 '(defineparents called wrong)')
997 994 return rp1 or p1, p2, base
998 995
999 996 def isagitpatch(repo, patchname):
1000 997 'Return true if the given patch is in git format'
1001 998 mqpatch = os.path.join(repo.mq.path, patchname)
1002 999 for line in patch.linereader(file(mqpatch, 'rb')):
1003 1000 if line.startswith('diff --git'):
1004 1001 return True
1005 1002 return False
1006 1003
1007 1004 def updatemq(repo, state, skipped, **opts):
1008 1005 'Update rebased mq patches - finalize and then import them'
1009 1006 mqrebase = {}
1010 1007 mq = repo.mq
1011 1008 original_series = mq.fullseries[:]
1012 1009 skippedpatches = set()
1013 1010
1014 1011 for p in mq.applied:
1015 1012 rev = repo[p.node].rev()
1016 1013 if rev in state:
1017 1014 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1018 1015 (rev, p.name))
1019 1016 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1020 1017 else:
1021 1018 # Applied but not rebased, not sure this should happen
1022 1019 skippedpatches.add(p.name)
1023 1020
1024 1021 if mqrebase:
1025 1022 mq.finish(repo, mqrebase.keys())
1026 1023
1027 1024 # We must start import from the newest revision
1028 1025 for rev in sorted(mqrebase, reverse=True):
1029 1026 if rev not in skipped:
1030 1027 name, isgit = mqrebase[rev]
1031 1028 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1032 1029 (name, state[rev], repo[state[rev]]))
1033 1030 mq.qimport(repo, (), patchname=name, git=isgit,
1034 1031 rev=[str(state[rev])])
1035 1032 else:
1036 1033 # Rebased and skipped
1037 1034 skippedpatches.add(mqrebase[rev][0])
1038 1035
1039 1036 # Patches were either applied and rebased and imported in
1040 1037 # order, applied and removed or unapplied. Discard the removed
1041 1038 # ones while preserving the original series order and guards.
1042 1039 newseries = [s for s in original_series
1043 1040 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1044 1041 mq.fullseries[:] = newseries
1045 1042 mq.seriesdirty = True
1046 1043 mq.savedirty()
1047 1044
1048 1045 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1049 1046 'Move bookmarks to their correct changesets, and delete divergent ones'
1050 1047 marks = repo._bookmarks
1051 1048 for k, v in originalbookmarks.iteritems():
1052 1049 if v in nstate:
1053 1050 # update the bookmarks for revs that have moved
1054 1051 marks[k] = nstate[v]
1055 1052 bookmarks.deletedivergent(repo, [targetnode], k)
1056 1053 marks.recordchange(tr)
1057 1054
1058 1055 def storecollapsemsg(repo, collapsemsg):
1059 1056 'Store the collapse message to allow recovery'
1060 1057 collapsemsg = collapsemsg or ''
1061 1058 f = repo.vfs("last-message.txt", "w")
1062 1059 f.write("%s\n" % collapsemsg)
1063 1060 f.close()
1064 1061
1065 1062 def clearcollapsemsg(repo):
1066 1063 'Remove collapse message file'
1067 1064 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
1068 1065
1069 1066 def restorecollapsemsg(repo):
1070 1067 'Restore previously stored collapse message'
1071 1068 try:
1072 1069 f = repo.vfs("last-message.txt")
1073 1070 collapsemsg = f.readline().strip()
1074 1071 f.close()
1075 1072 except IOError as err:
1076 1073 if err.errno != errno.ENOENT:
1077 1074 raise
1078 1075 raise error.Abort(_('no rebase in progress'))
1079 1076 return collapsemsg
1080 1077
1081 1078 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
1082 1079 external, activebookmark):
1083 1080 'Store the current status to allow recovery'
1084 1081 f = repo.vfs("rebasestate", "w")
1085 1082 f.write(repo[originalwd].hex() + '\n')
1086 1083 f.write(repo[target].hex() + '\n')
1087 1084 f.write(repo[external].hex() + '\n')
1088 1085 f.write('%d\n' % int(collapse))
1089 1086 f.write('%d\n' % int(keep))
1090 1087 f.write('%d\n' % int(keepbranches))
1091 1088 f.write('%s\n' % (activebookmark or ''))
1092 1089 for d, v in state.iteritems():
1093 1090 oldrev = repo[d].hex()
1094 1091 if v >= 0:
1095 1092 newrev = repo[v].hex()
1096 1093 elif v == revtodo:
1097 1094 # To maintain format compatibility, we have to use nullid.
1098 1095 # Please do remove this special case when upgrading the format.
1099 1096 newrev = hex(nullid)
1100 1097 else:
1101 1098 newrev = v
1102 1099 f.write("%s:%s\n" % (oldrev, newrev))
1103 1100 f.close()
1104 1101 repo.ui.debug('rebase status stored\n')
1105 1102
1106 1103 def clearstatus(repo):
1107 1104 'Remove the status files'
1108 1105 _clearrebasesetvisibiliy(repo)
1109 1106 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
1110 1107
1111 1108 def needupdate(repo, state):
1112 1109 '''check whether we should `update --clean` away from a merge, or if
1113 1110 somehow the working dir got forcibly updated, e.g. by older hg'''
1114 1111 parents = [p.rev() for p in repo[None].parents()]
1115 1112
1116 1113 # Are we in a merge state at all?
1117 1114 if len(parents) < 2:
1118 1115 return False
1119 1116
1120 1117 # We should be standing on the first as-of-yet unrebased commit.
1121 1118 firstunrebased = min([old for old, new in state.iteritems()
1122 1119 if new == nullrev])
1123 1120 if firstunrebased in parents:
1124 1121 return True
1125 1122
1126 1123 return False
1127 1124
1128 1125 def abort(repo, originalwd, target, state, activebookmark=None):
1129 1126 '''Restore the repository to its original state. Additional args:
1130 1127
1131 1128 activebookmark: the name of the bookmark that should be active after the
1132 1129 restore'''
1133 1130
1134 1131 try:
1135 1132 # If the first commits in the rebased set get skipped during the rebase,
1136 1133 # their values within the state mapping will be the target rev id. The
1137 1134 # dstates list must must not contain the target rev (issue4896)
1138 1135 dstates = [s for s in state.values() if s >= 0 and s != target]
1139 1136 immutable = [d for d in dstates if not repo[d].mutable()]
1140 1137 cleanup = True
1141 1138 if immutable:
1142 1139 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1143 1140 % ', '.join(str(repo[r]) for r in immutable),
1144 1141 hint=_("see 'hg help phases' for details"))
1145 1142 cleanup = False
1146 1143
1147 1144 descendants = set()
1148 1145 if dstates:
1149 1146 descendants = set(repo.changelog.descendants(dstates))
1150 1147 if descendants - set(dstates):
1151 1148 repo.ui.warn(_("warning: new changesets detected on target branch, "
1152 1149 "can't strip\n"))
1153 1150 cleanup = False
1154 1151
1155 1152 if cleanup:
1156 1153 shouldupdate = False
1157 1154 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1158 1155 if rebased:
1159 1156 strippoints = [
1160 1157 c.node() for c in repo.set('roots(%ld)', rebased)]
1161 1158 shouldupdate = len([
1162 1159 c.node() for c in repo.set('. & (%ld)', rebased)]) > 0
1163 1160
1164 1161 # Update away from the rebase if necessary
1165 1162 if shouldupdate or needupdate(repo, state):
1166 1163 mergemod.update(repo, originalwd, False, True)
1167 1164
1168 1165 # Strip from the first rebased revision
1169 1166 if rebased:
1170 1167 # no backup of rebased cset versions needed
1171 1168 repair.strip(repo.ui, repo, strippoints)
1172 1169
1173 1170 if activebookmark and activebookmark in repo._bookmarks:
1174 1171 bookmarks.activate(repo, activebookmark)
1175 1172
1176 1173 finally:
1177 1174 clearstatus(repo)
1178 1175 clearcollapsemsg(repo)
1179 1176 repo.ui.warn(_('rebase aborted\n'))
1180 1177 return 0
1181 1178
1182 1179 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1183 1180 '''Define which revisions are going to be rebased and where
1184 1181
1185 1182 repo: repo
1186 1183 dest: context
1187 1184 rebaseset: set of rev
1188 1185 '''
1189 1186 _setrebasesetvisibility(repo, rebaseset)
1190 1187
1191 1188 # This check isn't strictly necessary, since mq detects commits over an
1192 1189 # applied patch. But it prevents messing up the working directory when
1193 1190 # a partially completed rebase is blocked by mq.
1194 1191 if 'qtip' in repo.tags() and (dest.node() in
1195 1192 [s.node for s in repo.mq.applied]):
1196 1193 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1197 1194
1198 1195 roots = list(repo.set('roots(%ld)', rebaseset))
1199 1196 if not roots:
1200 1197 raise error.Abort(_('no matching revisions'))
1201 1198 roots.sort()
1202 1199 state = {}
1203 1200 detachset = set()
1204 1201 for root in roots:
1205 1202 commonbase = root.ancestor(dest)
1206 1203 if commonbase == root:
1207 1204 raise error.Abort(_('source is ancestor of destination'))
1208 1205 if commonbase == dest:
1209 1206 samebranch = root.branch() == dest.branch()
1210 1207 if not collapse and samebranch and root in dest.children():
1211 1208 repo.ui.debug('source is a child of destination\n')
1212 1209 return None
1213 1210
1214 1211 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1215 1212 state.update(dict.fromkeys(rebaseset, revtodo))
1216 1213 # Rebase tries to turn <dest> into a parent of <root> while
1217 1214 # preserving the number of parents of rebased changesets:
1218 1215 #
1219 1216 # - A changeset with a single parent will always be rebased as a
1220 1217 # changeset with a single parent.
1221 1218 #
1222 1219 # - A merge will be rebased as merge unless its parents are both
1223 1220 # ancestors of <dest> or are themselves in the rebased set and
1224 1221 # pruned while rebased.
1225 1222 #
1226 1223 # If one parent of <root> is an ancestor of <dest>, the rebased
1227 1224 # version of this parent will be <dest>. This is always true with
1228 1225 # --base option.
1229 1226 #
1230 1227 # Otherwise, we need to *replace* the original parents with
1231 1228 # <dest>. This "detaches" the rebased set from its former location
1232 1229 # and rebases it onto <dest>. Changes introduced by ancestors of
1233 1230 # <root> not common with <dest> (the detachset, marked as
1234 1231 # nullmerge) are "removed" from the rebased changesets.
1235 1232 #
1236 1233 # - If <root> has a single parent, set it to <dest>.
1237 1234 #
1238 1235 # - If <root> is a merge, we cannot decide which parent to
1239 1236 # replace, the rebase operation is not clearly defined.
1240 1237 #
1241 1238 # The table below sums up this behavior:
1242 1239 #
1243 1240 # +------------------+----------------------+-------------------------+
1244 1241 # | | one parent | merge |
1245 1242 # +------------------+----------------------+-------------------------+
1246 1243 # | parent in | new parent is <dest> | parents in ::<dest> are |
1247 1244 # | ::<dest> | | remapped to <dest> |
1248 1245 # +------------------+----------------------+-------------------------+
1249 1246 # | unrelated source | new parent is <dest> | ambiguous, abort |
1250 1247 # +------------------+----------------------+-------------------------+
1251 1248 #
1252 1249 # The actual abort is handled by `defineparents`
1253 1250 if len(root.parents()) <= 1:
1254 1251 # ancestors of <root> not ancestors of <dest>
1255 1252 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1256 1253 [root.rev()]))
1257 1254 for r in detachset:
1258 1255 if r not in state:
1259 1256 state[r] = nullmerge
1260 1257 if len(roots) > 1:
1261 1258 # If we have multiple roots, we may have "hole" in the rebase set.
1262 1259 # Rebase roots that descend from those "hole" should not be detached as
1263 1260 # other root are. We use the special `revignored` to inform rebase that
1264 1261 # the revision should be ignored but that `defineparents` should search
1265 1262 # a rebase destination that make sense regarding rebased topology.
1266 1263 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1267 1264 for ignored in set(rebasedomain) - set(rebaseset):
1268 1265 state[ignored] = revignored
1269 1266 for r in obsoletenotrebased:
1270 1267 if obsoletenotrebased[r] is None:
1271 1268 state[r] = revpruned
1272 1269 else:
1273 1270 state[r] = revprecursor
1274 1271 return repo['.'].rev(), dest.rev(), state
1275 1272
1276 1273 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1277 1274 """dispose of rebased revision at the end of the rebase
1278 1275
1279 1276 If `collapsedas` is not None, the rebase was a collapse whose result if the
1280 1277 `collapsedas` node."""
1281 1278 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1282 1279 markers = []
1283 1280 for rev, newrev in sorted(state.items()):
1284 1281 if newrev >= 0:
1285 1282 if rev in skipped:
1286 1283 succs = ()
1287 1284 elif collapsedas is not None:
1288 1285 succs = (repo[collapsedas],)
1289 1286 else:
1290 1287 succs = (repo[newrev],)
1291 1288 markers.append((repo[rev], succs))
1292 1289 if markers:
1293 1290 obsolete.createmarkers(repo, markers)
1294 1291 else:
1295 1292 rebased = [rev for rev in state if state[rev] > nullmerge]
1296 1293 if rebased:
1297 1294 stripped = []
1298 1295 for root in repo.set('roots(%ld)', rebased):
1299 1296 if set(repo.changelog.descendants([root.rev()])) - set(state):
1300 1297 ui.warn(_("warning: new changesets detected "
1301 1298 "on source branch, not stripping\n"))
1302 1299 else:
1303 1300 stripped.append(root.node())
1304 1301 if stripped:
1305 1302 # backup the old csets by default
1306 1303 repair.strip(ui, repo, stripped, "all")
1307 1304
1308 1305
1309 1306 def pullrebase(orig, ui, repo, *args, **opts):
1310 1307 'Call rebase after pull if the latter has been invoked with --rebase'
1311 1308 ret = None
1312 1309 if opts.get('rebase'):
1313 1310 wlock = lock = None
1314 1311 try:
1315 1312 wlock = repo.wlock()
1316 1313 lock = repo.lock()
1317 1314 if opts.get('update'):
1318 1315 del opts['update']
1319 1316 ui.debug('--update and --rebase are not compatible, ignoring '
1320 1317 'the update flag\n')
1321 1318
1322 1319 revsprepull = len(repo)
1323 1320 origpostincoming = commands.postincoming
1324 1321 def _dummy(*args, **kwargs):
1325 1322 pass
1326 1323 commands.postincoming = _dummy
1327 1324 try:
1328 1325 ret = orig(ui, repo, *args, **opts)
1329 1326 finally:
1330 1327 commands.postincoming = origpostincoming
1331 1328 revspostpull = len(repo)
1332 1329 if revspostpull > revsprepull:
1333 1330 # --rev option from pull conflict with rebase own --rev
1334 1331 # dropping it
1335 1332 if 'rev' in opts:
1336 1333 del opts['rev']
1337 1334 # positional argument from pull conflicts with rebase's own
1338 1335 # --source.
1339 1336 if 'source' in opts:
1340 1337 del opts['source']
1341 1338 # revsprepull is the len of the repo, not revnum of tip.
1342 1339 destspace = list(repo.changelog.revs(start=revsprepull))
1343 1340 opts['_destspace'] = destspace
1344 1341 try:
1345 1342 rebase(ui, repo, **opts)
1346 1343 except error.NoMergeDestAbort:
1347 1344 # we can maybe update instead
1348 1345 rev, _a, _b = destutil.destupdate(repo)
1349 1346 if rev == repo['.'].rev():
1350 1347 ui.status(_('nothing to rebase\n'))
1351 1348 else:
1352 1349 ui.status(_('nothing to rebase - updating instead\n'))
1353 1350 # not passing argument to get the bare update behavior
1354 1351 # with warning and trumpets
1355 1352 commands.update(ui, repo)
1356 1353 finally:
1357 1354 release(lock, wlock)
1358 1355 else:
1359 1356 if opts.get('tool'):
1360 1357 raise error.Abort(_('--tool can only be used with --rebase'))
1361 1358 ret = orig(ui, repo, *args, **opts)
1362 1359
1363 1360 return ret
1364 1361
1365 1362 def _setrebasesetvisibility(repo, revs):
1366 1363 """store the currently rebased set on the repo object
1367 1364
1368 1365 This is used by another function to prevent rebased revision to because
1369 1366 hidden (see issue4505)"""
1370 1367 repo = repo.unfiltered()
1371 1368 revs = set(revs)
1372 1369 repo._rebaseset = revs
1373 1370 # invalidate cache if visibility changes
1374 1371 hiddens = repo.filteredrevcache.get('visible', set())
1375 1372 if revs & hiddens:
1376 1373 repo.invalidatevolatilesets()
1377 1374
1378 1375 def _clearrebasesetvisibiliy(repo):
1379 1376 """remove rebaseset data from the repo"""
1380 1377 repo = repo.unfiltered()
1381 1378 if '_rebaseset' in vars(repo):
1382 1379 del repo._rebaseset
1383 1380
1384 1381 def _rebasedvisible(orig, repo):
1385 1382 """ensure rebased revs stay visible (see issue4505)"""
1386 1383 blockers = orig(repo)
1387 1384 blockers.update(getattr(repo, '_rebaseset', ()))
1388 1385 return blockers
1389 1386
1390 1387 def _filterobsoleterevs(repo, revs):
1391 1388 """returns a set of the obsolete revisions in revs"""
1392 1389 return set(r for r in revs if repo[r].obsolete())
1393 1390
1394 1391 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1395 1392 """return a mapping obsolete => successor for all obsolete nodes to be
1396 1393 rebased that have a successors in the destination
1397 1394
1398 1395 obsolete => None entries in the mapping indicate nodes with no successor"""
1399 1396 obsoletenotrebased = {}
1400 1397
1401 1398 # Build a mapping successor => obsolete nodes for the obsolete
1402 1399 # nodes to be rebased
1403 1400 allsuccessors = {}
1404 1401 cl = repo.changelog
1405 1402 for r in rebaseobsrevs:
1406 1403 node = cl.node(r)
1407 1404 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1408 1405 try:
1409 1406 allsuccessors[cl.rev(s)] = cl.rev(node)
1410 1407 except LookupError:
1411 1408 pass
1412 1409
1413 1410 if allsuccessors:
1414 1411 # Look for successors of obsolete nodes to be rebased among
1415 1412 # the ancestors of dest
1416 1413 ancs = cl.ancestors([repo[dest].rev()],
1417 1414 stoprev=min(allsuccessors),
1418 1415 inclusive=True)
1419 1416 for s in allsuccessors:
1420 1417 if s in ancs:
1421 1418 obsoletenotrebased[allsuccessors[s]] = s
1422 1419 elif (s == allsuccessors[s] and
1423 1420 allsuccessors.values().count(s) == 1):
1424 1421 # plain prune
1425 1422 obsoletenotrebased[s] = None
1426 1423
1427 1424 return obsoletenotrebased
1428 1425
1429 1426 def summaryhook(ui, repo):
1430 1427 if not os.path.exists(repo.join('rebasestate')):
1431 1428 return
1432 1429 try:
1433 1430 rbsrt = rebaseruntime(repo, ui, {})
1434 1431 rbsrt.restorestatus()
1435 1432 state = rbsrt.state
1436 1433 except error.RepoLookupError:
1437 1434 # i18n: column positioning for "hg summary"
1438 1435 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1439 1436 ui.write(msg)
1440 1437 return
1441 1438 numrebased = len([i for i in state.itervalues() if i >= 0])
1442 1439 # i18n: column positioning for "hg summary"
1443 1440 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1444 1441 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1445 1442 ui.label(_('%d remaining'), 'rebase.remaining') %
1446 1443 (len(state) - numrebased)))
1447 1444
1448 1445 def uisetup(ui):
1449 1446 #Replace pull with a decorator to provide --rebase option
1450 1447 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1451 1448 entry[1].append(('', 'rebase', None,
1452 1449 _("rebase working directory to branch head")))
1453 1450 entry[1].append(('t', 'tool', '',
1454 1451 _("specify merge tool for rebase")))
1455 1452 cmdutil.summaryhooks.add('rebase', summaryhook)
1456 1453 cmdutil.unfinishedstates.append(
1457 1454 ['rebasestate', False, False, _('rebase in progress'),
1458 1455 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1459 1456 cmdutil.afterresolvedstates.append(
1460 1457 ['rebasestate', _('hg rebase --continue')])
1461 1458 # ensure rebased rev are not hidden
1462 1459 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
General Comments 0
You need to be logged in to leave comments. Login now