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