##// END OF EJS Templates
rebase: backed out changeset cf8ad0e6c0e4 (issue5610)...
marmoute -
r33136:a5abaa81 stable
parent child Browse files
Show More
@@ -1,1507 +1,1502 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from __future__ import absolute_import
18 18
19 19 import errno
20 20 import os
21 21
22 22 from mercurial.i18n import _
23 23 from mercurial.node import (
24 24 hex,
25 25 nullid,
26 26 nullrev,
27 27 short,
28 28 )
29 29 from mercurial import (
30 30 bookmarks,
31 31 cmdutil,
32 32 commands,
33 33 copies,
34 34 destutil,
35 35 dirstateguard,
36 36 error,
37 37 extensions,
38 38 hg,
39 39 lock,
40 40 merge as mergemod,
41 41 mergeutil,
42 42 obsolete,
43 43 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.unfiltered()
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 def _performrebase(self, tr):
346 def _performrebase(self):
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 self.storestatus(tr=tr)
396 self.storestatus()
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 Configuration Options:
654 654
655 655 You can make rebase require a destination if you set the following config
656 656 option::
657 657
658 658 [commands]
659 659 rebase.requiredest = True
660 660
661 661 Return Values:
662 662
663 663 Returns 0 on success, 1 if nothing to rebase or there are
664 664 unresolved conflicts.
665 665
666 666 """
667 667 rbsrt = rebaseruntime(repo, ui, opts)
668 668
669 669 lock = wlock = None
670 670 try:
671 671 wlock = repo.wlock()
672 672 lock = repo.lock()
673 673
674 674 # Validate input and define rebasing points
675 675 destf = opts.get('dest', None)
676 676 srcf = opts.get('source', None)
677 677 basef = opts.get('base', None)
678 678 revf = opts.get('rev', [])
679 679 # search default destination in this space
680 680 # used in the 'hg pull --rebase' case, see issue 5214.
681 681 destspace = opts.get('_destspace')
682 682 contf = opts.get('continue')
683 683 abortf = opts.get('abort')
684 684 if opts.get('interactive'):
685 685 try:
686 686 if extensions.find('histedit'):
687 687 enablehistedit = ''
688 688 except KeyError:
689 689 enablehistedit = " --config extensions.histedit="
690 690 help = "hg%s help -e histedit" % enablehistedit
691 691 msg = _("interactive history editing is supported by the "
692 692 "'histedit' extension (see \"%s\")") % help
693 693 raise error.Abort(msg)
694 694
695 695 if rbsrt.collapsemsg and not rbsrt.collapsef:
696 696 raise error.Abort(
697 697 _('message can only be specified with collapse'))
698 698
699 699 if contf or abortf:
700 700 if contf and abortf:
701 701 raise error.Abort(_('cannot use both abort and continue'))
702 702 if rbsrt.collapsef:
703 703 raise error.Abort(
704 704 _('cannot use collapse with continue or abort'))
705 705 if srcf or basef or destf:
706 706 raise error.Abort(
707 707 _('abort and continue do not allow specifying revisions'))
708 708 if abortf and opts.get('tool', False):
709 709 ui.warn(_('tool option will be ignored\n'))
710 710 if contf:
711 711 ms = mergemod.mergestate.read(repo)
712 712 mergeutil.checkunresolved(ms)
713 713
714 714 retcode = rbsrt._prepareabortorcontinue(abortf)
715 715 if retcode is not None:
716 716 return retcode
717 717 else:
718 718 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
719 719 destspace=destspace)
720 720 retcode = rbsrt._preparenewrebase(dest, rebaseset)
721 721 if retcode is not None:
722 722 return retcode
723 723
724 with repo.transaction('rebase') as tr:
725 try:
726 rbsrt._performrebase(tr)
727 except error.InterventionRequired:
728 tr.close()
729 raise
724 rbsrt._performrebase()
730 725 rbsrt._finishrebase()
731 726 finally:
732 727 release(lock, wlock)
733 728
734 729 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
735 730 destspace=None):
736 731 """use revisions argument to define destination and rebase set
737 732 """
738 733 if revf is None:
739 734 revf = []
740 735
741 736 # destspace is here to work around issues with `hg pull --rebase` see
742 737 # issue5214 for details
743 738 if srcf and basef:
744 739 raise error.Abort(_('cannot specify both a source and a base'))
745 740 if revf and basef:
746 741 raise error.Abort(_('cannot specify both a revision and a base'))
747 742 if revf and srcf:
748 743 raise error.Abort(_('cannot specify both a revision and a source'))
749 744
750 745 cmdutil.checkunfinished(repo)
751 746 cmdutil.bailifchanged(repo)
752 747
753 748 if ui.configbool('commands', 'rebase.requiredest') and not destf:
754 749 raise error.Abort(_('you must specify a destination'),
755 750 hint=_('use: hg rebase -d REV'))
756 751
757 752 if destf:
758 753 dest = scmutil.revsingle(repo, destf)
759 754
760 755 if revf:
761 756 rebaseset = scmutil.revrange(repo, revf)
762 757 if not rebaseset:
763 758 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
764 759 return None, None
765 760 elif srcf:
766 761 src = scmutil.revrange(repo, [srcf])
767 762 if not src:
768 763 ui.status(_('empty "source" revision set - nothing to rebase\n'))
769 764 return None, None
770 765 rebaseset = repo.revs('(%ld)::', src)
771 766 assert rebaseset
772 767 else:
773 768 base = scmutil.revrange(repo, [basef or '.'])
774 769 if not base:
775 770 ui.status(_('empty "base" revision set - '
776 771 "can't compute rebase set\n"))
777 772 return None, None
778 773 if not destf:
779 774 dest = repo[_destrebase(repo, base, destspace=destspace)]
780 775 destf = str(dest)
781 776
782 777 roots = [] # selected children of branching points
783 778 bpbase = {} # {branchingpoint: [origbase]}
784 779 for b in base: # group bases by branching points
785 780 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
786 781 bpbase[bp] = bpbase.get(bp, []) + [b]
787 782 if None in bpbase:
788 783 # emulate the old behavior, showing "nothing to rebase" (a better
789 784 # behavior may be abort with "cannot find branching point" error)
790 785 bpbase.clear()
791 786 for bp, bs in bpbase.iteritems(): # calculate roots
792 787 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
793 788
794 789 rebaseset = repo.revs('%ld::', roots)
795 790
796 791 if not rebaseset:
797 792 # transform to list because smartsets are not comparable to
798 793 # lists. This should be improved to honor laziness of
799 794 # smartset.
800 795 if list(base) == [dest.rev()]:
801 796 if basef:
802 797 ui.status(_('nothing to rebase - %s is both "base"'
803 798 ' and destination\n') % dest)
804 799 else:
805 800 ui.status(_('nothing to rebase - working directory '
806 801 'parent is also destination\n'))
807 802 elif not repo.revs('%ld - ::%d', base, dest):
808 803 if basef:
809 804 ui.status(_('nothing to rebase - "base" %s is '
810 805 'already an ancestor of destination '
811 806 '%s\n') %
812 807 ('+'.join(str(repo[r]) for r in base),
813 808 dest))
814 809 else:
815 810 ui.status(_('nothing to rebase - working '
816 811 'directory parent is already an '
817 812 'ancestor of destination %s\n') % dest)
818 813 else: # can it happen?
819 814 ui.status(_('nothing to rebase from %s to %s\n') %
820 815 ('+'.join(str(repo[r]) for r in base), dest))
821 816 return None, None
822 817
823 818 if not destf:
824 819 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
825 820 destf = str(dest)
826 821
827 822 return dest, rebaseset
828 823
829 824 def externalparent(repo, state, targetancestors):
830 825 """Return the revision that should be used as the second parent
831 826 when the revisions in state is collapsed on top of targetancestors.
832 827 Abort if there is more than one parent.
833 828 """
834 829 parents = set()
835 830 source = min(state)
836 831 for rev in state:
837 832 if rev == source:
838 833 continue
839 834 for p in repo[rev].parents():
840 835 if (p.rev() not in state
841 836 and p.rev() not in targetancestors):
842 837 parents.add(p.rev())
843 838 if not parents:
844 839 return nullrev
845 840 if len(parents) == 1:
846 841 return parents.pop()
847 842 raise error.Abort(_('unable to collapse on top of %s, there is more '
848 843 'than one external parent: %s') %
849 844 (max(targetancestors),
850 845 ', '.join(str(p) for p in sorted(parents))))
851 846
852 847 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
853 848 keepbranches=False, date=None):
854 849 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
855 850 but also store useful information in extra.
856 851 Return node of committed revision.'''
857 852 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
858 853 try:
859 854 repo.setparents(repo[p1].node(), repo[p2].node())
860 855 ctx = repo[rev]
861 856 if commitmsg is None:
862 857 commitmsg = ctx.description()
863 858 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
864 859 extra = {'rebase_source': ctx.hex()}
865 860 if extrafn:
866 861 extrafn(ctx, extra)
867 862
868 863 targetphase = max(ctx.phase(), phases.draft)
869 864 overrides = {('phases', 'new-commit'): targetphase}
870 865 with repo.ui.configoverride(overrides, 'rebase'):
871 866 if keepbranch:
872 867 repo.ui.setconfig('ui', 'allowemptycommit', True)
873 868 # Commit might fail if unresolved files exist
874 869 if date is None:
875 870 date = ctx.date()
876 871 newnode = repo.commit(text=commitmsg, user=ctx.user(),
877 872 date=date, extra=extra, editor=editor)
878 873
879 874 repo.dirstate.setbranch(repo[newnode].branch())
880 875 dsguard.close()
881 876 return newnode
882 877 finally:
883 878 release(dsguard)
884 879
885 880 def rebasenode(repo, rev, p1, base, state, collapse, target):
886 881 'Rebase a single revision rev on top of p1 using base as merge ancestor'
887 882 # Merge phase
888 883 # Update to target and merge it with local
889 884 if repo['.'].rev() != p1:
890 885 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
891 886 mergemod.update(repo, p1, False, True)
892 887 else:
893 888 repo.ui.debug(" already in target\n")
894 889 repo.dirstate.write(repo.currenttransaction())
895 890 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
896 891 if base is not None:
897 892 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
898 893 # When collapsing in-place, the parent is the common ancestor, we
899 894 # have to allow merging with it.
900 895 stats = mergemod.update(repo, rev, True, True, base, collapse,
901 896 labels=['dest', 'source'])
902 897 if collapse:
903 898 copies.duplicatecopies(repo, rev, target)
904 899 else:
905 900 # If we're not using --collapse, we need to
906 901 # duplicate copies between the revision we're
907 902 # rebasing and its first parent, but *not*
908 903 # duplicate any copies that have already been
909 904 # performed in the destination.
910 905 p1rev = repo[rev].p1().rev()
911 906 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
912 907 return stats
913 908
914 909 def nearestrebased(repo, rev, state):
915 910 """return the nearest ancestors of rev in the rebase result"""
916 911 rebased = [r for r in state if state[r] > nullmerge]
917 912 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
918 913 if candidates:
919 914 return state[candidates.first()]
920 915 else:
921 916 return None
922 917
923 918 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
924 919 """
925 920 Abort if rebase will create divergence or rebase is noop because of markers
926 921
927 922 `rebaseobsrevs`: set of obsolete revision in source
928 923 `rebasesetrevs`: set of revisions to be rebased from source
929 924 `rebaseobsskipped`: set of revisions from source skipped because they have
930 925 successors in destination
931 926 """
932 927 # Obsolete node with successors not in dest leads to divergence
933 928 divergenceok = ui.configbool('experimental',
934 929 'allowdivergence')
935 930 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
936 931
937 932 if divergencebasecandidates and not divergenceok:
938 933 divhashes = (str(repo[r])
939 934 for r in divergencebasecandidates)
940 935 msg = _("this rebase will cause "
941 936 "divergences from: %s")
942 937 h = _("to force the rebase please set "
943 938 "experimental.allowdivergence=True")
944 939 raise error.Abort(msg % (",".join(divhashes),), hint=h)
945 940
946 941 def defineparents(repo, rev, target, state, targetancestors,
947 942 obsoletenotrebased):
948 943 'Return the new parent relationship of the revision that will be rebased'
949 944 parents = repo[rev].parents()
950 945 p1 = p2 = nullrev
951 946 rp1 = None
952 947
953 948 p1n = parents[0].rev()
954 949 if p1n in targetancestors:
955 950 p1 = target
956 951 elif p1n in state:
957 952 if state[p1n] == nullmerge:
958 953 p1 = target
959 954 elif state[p1n] in revskipped:
960 955 p1 = nearestrebased(repo, p1n, state)
961 956 if p1 is None:
962 957 p1 = target
963 958 else:
964 959 p1 = state[p1n]
965 960 else: # p1n external
966 961 p1 = target
967 962 p2 = p1n
968 963
969 964 if len(parents) == 2 and parents[1].rev() not in targetancestors:
970 965 p2n = parents[1].rev()
971 966 # interesting second parent
972 967 if p2n in state:
973 968 if p1 == target: # p1n in targetancestors or external
974 969 p1 = state[p2n]
975 970 if p1 == revprecursor:
976 971 rp1 = obsoletenotrebased[p2n]
977 972 elif state[p2n] in revskipped:
978 973 p2 = nearestrebased(repo, p2n, state)
979 974 if p2 is None:
980 975 # no ancestors rebased yet, detach
981 976 p2 = target
982 977 else:
983 978 p2 = state[p2n]
984 979 else: # p2n external
985 980 if p2 != nullrev: # p1n external too => rev is a merged revision
986 981 raise error.Abort(_('cannot use revision %d as base, result '
987 982 'would have 3 parents') % rev)
988 983 p2 = p2n
989 984 repo.ui.debug(" future parents are %d and %d\n" %
990 985 (repo[rp1 or p1].rev(), repo[p2].rev()))
991 986
992 987 if not any(p.rev() in state for p in parents):
993 988 # Case (1) root changeset of a non-detaching rebase set.
994 989 # Let the merge mechanism find the base itself.
995 990 base = None
996 991 elif not repo[rev].p2():
997 992 # Case (2) detaching the node with a single parent, use this parent
998 993 base = repo[rev].p1().rev()
999 994 else:
1000 995 # Assuming there is a p1, this is the case where there also is a p2.
1001 996 # We are thus rebasing a merge and need to pick the right merge base.
1002 997 #
1003 998 # Imagine we have:
1004 999 # - M: current rebase revision in this step
1005 1000 # - A: one parent of M
1006 1001 # - B: other parent of M
1007 1002 # - D: destination of this merge step (p1 var)
1008 1003 #
1009 1004 # Consider the case where D is a descendant of A or B and the other is
1010 1005 # 'outside'. In this case, the right merge base is the D ancestor.
1011 1006 #
1012 1007 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1013 1008 #
1014 1009 # If we pick B as the base, the merge involves:
1015 1010 # - changes from B to M (actual changeset payload)
1016 1011 # - changes from B to D (induced by rebase) as D is a rebased
1017 1012 # version of B)
1018 1013 # Which exactly represent the rebase operation.
1019 1014 #
1020 1015 # If we pick A as the base, the merge involves:
1021 1016 # - changes from A to M (actual changeset payload)
1022 1017 # - changes from A to D (with include changes between unrelated A and B
1023 1018 # plus changes induced by rebase)
1024 1019 # Which does not represent anything sensible and creates a lot of
1025 1020 # conflicts. A is thus not the right choice - B is.
1026 1021 #
1027 1022 # Note: The base found in this 'proof' is only correct in the specified
1028 1023 # case. This base does not make sense if is not D a descendant of A or B
1029 1024 # or if the other is not parent 'outside' (especially not if the other
1030 1025 # parent has been rebased). The current implementation does not
1031 1026 # make it feasible to consider different cases separately. In these
1032 1027 # other cases we currently just leave it to the user to correctly
1033 1028 # resolve an impossible merge using a wrong ancestor.
1034 1029 #
1035 1030 # xx, p1 could be -4, and both parents could probably be -4...
1036 1031 for p in repo[rev].parents():
1037 1032 if state.get(p.rev()) == p1:
1038 1033 base = p.rev()
1039 1034 break
1040 1035 else: # fallback when base not found
1041 1036 base = None
1042 1037
1043 1038 # Raise because this function is called wrong (see issue 4106)
1044 1039 raise AssertionError('no base found to rebase on '
1045 1040 '(defineparents called wrong)')
1046 1041 return rp1 or p1, p2, base
1047 1042
1048 1043 def isagitpatch(repo, patchname):
1049 1044 'Return true if the given patch is in git format'
1050 1045 mqpatch = os.path.join(repo.mq.path, patchname)
1051 1046 for line in patch.linereader(file(mqpatch, 'rb')):
1052 1047 if line.startswith('diff --git'):
1053 1048 return True
1054 1049 return False
1055 1050
1056 1051 def updatemq(repo, state, skipped, **opts):
1057 1052 'Update rebased mq patches - finalize and then import them'
1058 1053 mqrebase = {}
1059 1054 mq = repo.mq
1060 1055 original_series = mq.fullseries[:]
1061 1056 skippedpatches = set()
1062 1057
1063 1058 for p in mq.applied:
1064 1059 rev = repo[p.node].rev()
1065 1060 if rev in state:
1066 1061 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1067 1062 (rev, p.name))
1068 1063 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1069 1064 else:
1070 1065 # Applied but not rebased, not sure this should happen
1071 1066 skippedpatches.add(p.name)
1072 1067
1073 1068 if mqrebase:
1074 1069 mq.finish(repo, mqrebase.keys())
1075 1070
1076 1071 # We must start import from the newest revision
1077 1072 for rev in sorted(mqrebase, reverse=True):
1078 1073 if rev not in skipped:
1079 1074 name, isgit = mqrebase[rev]
1080 1075 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1081 1076 (name, state[rev], repo[state[rev]]))
1082 1077 mq.qimport(repo, (), patchname=name, git=isgit,
1083 1078 rev=[str(state[rev])])
1084 1079 else:
1085 1080 # Rebased and skipped
1086 1081 skippedpatches.add(mqrebase[rev][0])
1087 1082
1088 1083 # Patches were either applied and rebased and imported in
1089 1084 # order, applied and removed or unapplied. Discard the removed
1090 1085 # ones while preserving the original series order and guards.
1091 1086 newseries = [s for s in original_series
1092 1087 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1093 1088 mq.fullseries[:] = newseries
1094 1089 mq.seriesdirty = True
1095 1090 mq.savedirty()
1096 1091
1097 1092 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
1098 1093 'Move bookmarks to their correct changesets, and delete divergent ones'
1099 1094 marks = repo._bookmarks
1100 1095 for k, v in originalbookmarks.iteritems():
1101 1096 if v in nstate:
1102 1097 # update the bookmarks for revs that have moved
1103 1098 marks[k] = nstate[v]
1104 1099 bookmarks.deletedivergent(repo, [targetnode], k)
1105 1100 marks.recordchange(tr)
1106 1101
1107 1102 def storecollapsemsg(repo, collapsemsg):
1108 1103 'Store the collapse message to allow recovery'
1109 1104 collapsemsg = collapsemsg or ''
1110 1105 f = repo.vfs("last-message.txt", "w")
1111 1106 f.write("%s\n" % collapsemsg)
1112 1107 f.close()
1113 1108
1114 1109 def clearcollapsemsg(repo):
1115 1110 'Remove collapse message file'
1116 1111 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1117 1112
1118 1113 def restorecollapsemsg(repo, isabort):
1119 1114 'Restore previously stored collapse message'
1120 1115 try:
1121 1116 f = repo.vfs("last-message.txt")
1122 1117 collapsemsg = f.readline().strip()
1123 1118 f.close()
1124 1119 except IOError as err:
1125 1120 if err.errno != errno.ENOENT:
1126 1121 raise
1127 1122 if isabort:
1128 1123 # Oh well, just abort like normal
1129 1124 collapsemsg = ''
1130 1125 else:
1131 1126 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1132 1127 return collapsemsg
1133 1128
1134 1129 def clearstatus(repo):
1135 1130 'Remove the status files'
1136 1131 _clearrebasesetvisibiliy(repo)
1137 1132 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1138 1133
1139 1134 def needupdate(repo, state):
1140 1135 '''check whether we should `update --clean` away from a merge, or if
1141 1136 somehow the working dir got forcibly updated, e.g. by older hg'''
1142 1137 parents = [p.rev() for p in repo[None].parents()]
1143 1138
1144 1139 # Are we in a merge state at all?
1145 1140 if len(parents) < 2:
1146 1141 return False
1147 1142
1148 1143 # We should be standing on the first as-of-yet unrebased commit.
1149 1144 firstunrebased = min([old for old, new in state.iteritems()
1150 1145 if new == nullrev])
1151 1146 if firstunrebased in parents:
1152 1147 return True
1153 1148
1154 1149 return False
1155 1150
1156 1151 def abort(repo, originalwd, target, state, activebookmark=None):
1157 1152 '''Restore the repository to its original state. Additional args:
1158 1153
1159 1154 activebookmark: the name of the bookmark that should be active after the
1160 1155 restore'''
1161 1156
1162 1157 try:
1163 1158 # If the first commits in the rebased set get skipped during the rebase,
1164 1159 # their values within the state mapping will be the target rev id. The
1165 1160 # dstates list must must not contain the target rev (issue4896)
1166 1161 dstates = [s for s in state.values() if s >= 0 and s != target]
1167 1162 immutable = [d for d in dstates if not repo[d].mutable()]
1168 1163 cleanup = True
1169 1164 if immutable:
1170 1165 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1171 1166 % ', '.join(str(repo[r]) for r in immutable),
1172 1167 hint=_("see 'hg help phases' for details"))
1173 1168 cleanup = False
1174 1169
1175 1170 descendants = set()
1176 1171 if dstates:
1177 1172 descendants = set(repo.changelog.descendants(dstates))
1178 1173 if descendants - set(dstates):
1179 1174 repo.ui.warn(_("warning: new changesets detected on target branch, "
1180 1175 "can't strip\n"))
1181 1176 cleanup = False
1182 1177
1183 1178 if cleanup:
1184 1179 shouldupdate = False
1185 1180 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1186 1181 if rebased:
1187 1182 strippoints = [
1188 1183 c.node() for c in repo.set('roots(%ld)', rebased)]
1189 1184
1190 1185 updateifonnodes = set(rebased)
1191 1186 updateifonnodes.add(target)
1192 1187 updateifonnodes.add(originalwd)
1193 1188 shouldupdate = repo['.'].rev() in updateifonnodes
1194 1189
1195 1190 # Update away from the rebase if necessary
1196 1191 if shouldupdate or needupdate(repo, state):
1197 1192 mergemod.update(repo, originalwd, False, True)
1198 1193
1199 1194 # Strip from the first rebased revision
1200 1195 if rebased:
1201 1196 # no backup of rebased cset versions needed
1202 1197 repair.strip(repo.ui, repo, strippoints)
1203 1198
1204 1199 if activebookmark and activebookmark in repo._bookmarks:
1205 1200 bookmarks.activate(repo, activebookmark)
1206 1201
1207 1202 finally:
1208 1203 clearstatus(repo)
1209 1204 clearcollapsemsg(repo)
1210 1205 repo.ui.warn(_('rebase aborted\n'))
1211 1206 return 0
1212 1207
1213 1208 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1214 1209 '''Define which revisions are going to be rebased and where
1215 1210
1216 1211 repo: repo
1217 1212 dest: context
1218 1213 rebaseset: set of rev
1219 1214 '''
1220 1215 originalwd = repo['.'].rev()
1221 1216 _setrebasesetvisibility(repo, set(rebaseset) | set([originalwd]))
1222 1217
1223 1218 # This check isn't strictly necessary, since mq detects commits over an
1224 1219 # applied patch. But it prevents messing up the working directory when
1225 1220 # a partially completed rebase is blocked by mq.
1226 1221 if 'qtip' in repo.tags() and (dest.node() in
1227 1222 [s.node for s in repo.mq.applied]):
1228 1223 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1229 1224
1230 1225 roots = list(repo.set('roots(%ld)', rebaseset))
1231 1226 if not roots:
1232 1227 raise error.Abort(_('no matching revisions'))
1233 1228 roots.sort()
1234 1229 state = {}
1235 1230 detachset = set()
1236 1231 for root in roots:
1237 1232 commonbase = root.ancestor(dest)
1238 1233 if commonbase == root:
1239 1234 raise error.Abort(_('source is ancestor of destination'))
1240 1235 if commonbase == dest:
1241 1236 wctx = repo[None]
1242 1237 if dest == wctx.p1():
1243 1238 # when rebasing to '.', it will use the current wd branch name
1244 1239 samebranch = root.branch() == wctx.branch()
1245 1240 else:
1246 1241 samebranch = root.branch() == dest.branch()
1247 1242 if not collapse and samebranch and root in dest.children():
1248 1243 repo.ui.debug('source is a child of destination\n')
1249 1244 return None
1250 1245
1251 1246 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1252 1247 state.update(dict.fromkeys(rebaseset, revtodo))
1253 1248 # Rebase tries to turn <dest> into a parent of <root> while
1254 1249 # preserving the number of parents of rebased changesets:
1255 1250 #
1256 1251 # - A changeset with a single parent will always be rebased as a
1257 1252 # changeset with a single parent.
1258 1253 #
1259 1254 # - A merge will be rebased as merge unless its parents are both
1260 1255 # ancestors of <dest> or are themselves in the rebased set and
1261 1256 # pruned while rebased.
1262 1257 #
1263 1258 # If one parent of <root> is an ancestor of <dest>, the rebased
1264 1259 # version of this parent will be <dest>. This is always true with
1265 1260 # --base option.
1266 1261 #
1267 1262 # Otherwise, we need to *replace* the original parents with
1268 1263 # <dest>. This "detaches" the rebased set from its former location
1269 1264 # and rebases it onto <dest>. Changes introduced by ancestors of
1270 1265 # <root> not common with <dest> (the detachset, marked as
1271 1266 # nullmerge) are "removed" from the rebased changesets.
1272 1267 #
1273 1268 # - If <root> has a single parent, set it to <dest>.
1274 1269 #
1275 1270 # - If <root> is a merge, we cannot decide which parent to
1276 1271 # replace, the rebase operation is not clearly defined.
1277 1272 #
1278 1273 # The table below sums up this behavior:
1279 1274 #
1280 1275 # +------------------+----------------------+-------------------------+
1281 1276 # | | one parent | merge |
1282 1277 # +------------------+----------------------+-------------------------+
1283 1278 # | parent in | new parent is <dest> | parents in ::<dest> are |
1284 1279 # | ::<dest> | | remapped to <dest> |
1285 1280 # +------------------+----------------------+-------------------------+
1286 1281 # | unrelated source | new parent is <dest> | ambiguous, abort |
1287 1282 # +------------------+----------------------+-------------------------+
1288 1283 #
1289 1284 # The actual abort is handled by `defineparents`
1290 1285 if len(root.parents()) <= 1:
1291 1286 # ancestors of <root> not ancestors of <dest>
1292 1287 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1293 1288 [root.rev()]))
1294 1289 for r in detachset:
1295 1290 if r not in state:
1296 1291 state[r] = nullmerge
1297 1292 if len(roots) > 1:
1298 1293 # If we have multiple roots, we may have "hole" in the rebase set.
1299 1294 # Rebase roots that descend from those "hole" should not be detached as
1300 1295 # other root are. We use the special `revignored` to inform rebase that
1301 1296 # the revision should be ignored but that `defineparents` should search
1302 1297 # a rebase destination that make sense regarding rebased topology.
1303 1298 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1304 1299 for ignored in set(rebasedomain) - set(rebaseset):
1305 1300 state[ignored] = revignored
1306 1301 for r in obsoletenotrebased:
1307 1302 if obsoletenotrebased[r] is None:
1308 1303 state[r] = revpruned
1309 1304 else:
1310 1305 state[r] = revprecursor
1311 1306 return originalwd, dest.rev(), state
1312 1307
1313 1308 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1314 1309 """dispose of rebased revision at the end of the rebase
1315 1310
1316 1311 If `collapsedas` is not None, the rebase was a collapse whose result if the
1317 1312 `collapsedas` node."""
1318 1313 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1319 1314 markers = []
1320 1315 for rev, newrev in sorted(state.items()):
1321 1316 if newrev >= 0:
1322 1317 if rev in skipped:
1323 1318 succs = ()
1324 1319 elif collapsedas is not None:
1325 1320 succs = (repo[collapsedas],)
1326 1321 else:
1327 1322 succs = (repo[newrev],)
1328 1323 markers.append((repo[rev], succs))
1329 1324 if markers:
1330 1325 obsolete.createmarkers(repo, markers)
1331 1326 else:
1332 1327 rebased = [rev for rev in state if state[rev] > nullmerge]
1333 1328 if rebased:
1334 1329 stripped = []
1335 1330 for root in repo.set('roots(%ld)', rebased):
1336 1331 if set(repo.changelog.descendants([root.rev()])) - set(state):
1337 1332 ui.warn(_("warning: new changesets detected "
1338 1333 "on source branch, not stripping\n"))
1339 1334 else:
1340 1335 stripped.append(root.node())
1341 1336 if stripped:
1342 1337 # backup the old csets by default
1343 1338 repair.strip(ui, repo, stripped, "all")
1344 1339
1345 1340
1346 1341 def pullrebase(orig, ui, repo, *args, **opts):
1347 1342 'Call rebase after pull if the latter has been invoked with --rebase'
1348 1343 ret = None
1349 1344 if opts.get('rebase'):
1350 1345 if ui.configbool('commands', 'rebase.requiredest'):
1351 1346 msg = _('rebase destination required by configuration')
1352 1347 hint = _('use hg pull followed by hg rebase -d DEST')
1353 1348 raise error.Abort(msg, hint=hint)
1354 1349
1355 1350 wlock = lock = None
1356 1351 try:
1357 1352 wlock = repo.wlock()
1358 1353 lock = repo.lock()
1359 1354 if opts.get('update'):
1360 1355 del opts['update']
1361 1356 ui.debug('--update and --rebase are not compatible, ignoring '
1362 1357 'the update flag\n')
1363 1358
1364 1359 cmdutil.checkunfinished(repo)
1365 1360 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1366 1361 'please commit or shelve your changes first'))
1367 1362
1368 1363 revsprepull = len(repo)
1369 1364 origpostincoming = commands.postincoming
1370 1365 def _dummy(*args, **kwargs):
1371 1366 pass
1372 1367 commands.postincoming = _dummy
1373 1368 try:
1374 1369 ret = orig(ui, repo, *args, **opts)
1375 1370 finally:
1376 1371 commands.postincoming = origpostincoming
1377 1372 revspostpull = len(repo)
1378 1373 if revspostpull > revsprepull:
1379 1374 # --rev option from pull conflict with rebase own --rev
1380 1375 # dropping it
1381 1376 if 'rev' in opts:
1382 1377 del opts['rev']
1383 1378 # positional argument from pull conflicts with rebase's own
1384 1379 # --source.
1385 1380 if 'source' in opts:
1386 1381 del opts['source']
1387 1382 # revsprepull is the len of the repo, not revnum of tip.
1388 1383 destspace = list(repo.changelog.revs(start=revsprepull))
1389 1384 opts['_destspace'] = destspace
1390 1385 try:
1391 1386 rebase(ui, repo, **opts)
1392 1387 except error.NoMergeDestAbort:
1393 1388 # we can maybe update instead
1394 1389 rev, _a, _b = destutil.destupdate(repo)
1395 1390 if rev == repo['.'].rev():
1396 1391 ui.status(_('nothing to rebase\n'))
1397 1392 else:
1398 1393 ui.status(_('nothing to rebase - updating instead\n'))
1399 1394 # not passing argument to get the bare update behavior
1400 1395 # with warning and trumpets
1401 1396 commands.update(ui, repo)
1402 1397 finally:
1403 1398 release(lock, wlock)
1404 1399 else:
1405 1400 if opts.get('tool'):
1406 1401 raise error.Abort(_('--tool can only be used with --rebase'))
1407 1402 ret = orig(ui, repo, *args, **opts)
1408 1403
1409 1404 return ret
1410 1405
1411 1406 def _setrebasesetvisibility(repo, revs):
1412 1407 """store the currently rebased set on the repo object
1413 1408
1414 1409 This is used by another function to prevent rebased revision to because
1415 1410 hidden (see issue4504)"""
1416 1411 repo = repo.unfiltered()
1417 1412 repo._rebaseset = revs
1418 1413 # invalidate cache if visibility changes
1419 1414 hiddens = repo.filteredrevcache.get('visible', set())
1420 1415 if revs & hiddens:
1421 1416 repo.invalidatevolatilesets()
1422 1417
1423 1418 def _clearrebasesetvisibiliy(repo):
1424 1419 """remove rebaseset data from the repo"""
1425 1420 repo = repo.unfiltered()
1426 1421 if '_rebaseset' in vars(repo):
1427 1422 del repo._rebaseset
1428 1423
1429 1424 def _rebasedvisible(orig, repo):
1430 1425 """ensure rebased revs stay visible (see issue4504)"""
1431 1426 blockers = orig(repo)
1432 1427 blockers.update(getattr(repo, '_rebaseset', ()))
1433 1428 return blockers
1434 1429
1435 1430 def _filterobsoleterevs(repo, revs):
1436 1431 """returns a set of the obsolete revisions in revs"""
1437 1432 return set(r for r in revs if repo[r].obsolete())
1438 1433
1439 1434 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1440 1435 """return a mapping obsolete => successor for all obsolete nodes to be
1441 1436 rebased that have a successors in the destination
1442 1437
1443 1438 obsolete => None entries in the mapping indicate nodes with no successor"""
1444 1439 obsoletenotrebased = {}
1445 1440
1446 1441 # Build a mapping successor => obsolete nodes for the obsolete
1447 1442 # nodes to be rebased
1448 1443 allsuccessors = {}
1449 1444 cl = repo.changelog
1450 1445 for r in rebaseobsrevs:
1451 1446 node = cl.node(r)
1452 1447 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1453 1448 try:
1454 1449 allsuccessors[cl.rev(s)] = cl.rev(node)
1455 1450 except LookupError:
1456 1451 pass
1457 1452
1458 1453 if allsuccessors:
1459 1454 # Look for successors of obsolete nodes to be rebased among
1460 1455 # the ancestors of dest
1461 1456 ancs = cl.ancestors([repo[dest].rev()],
1462 1457 stoprev=min(allsuccessors),
1463 1458 inclusive=True)
1464 1459 for s in allsuccessors:
1465 1460 if s in ancs:
1466 1461 obsoletenotrebased[allsuccessors[s]] = s
1467 1462 elif (s == allsuccessors[s] and
1468 1463 allsuccessors.values().count(s) == 1):
1469 1464 # plain prune
1470 1465 obsoletenotrebased[s] = None
1471 1466
1472 1467 return obsoletenotrebased
1473 1468
1474 1469 def summaryhook(ui, repo):
1475 1470 if not repo.vfs.exists('rebasestate'):
1476 1471 return
1477 1472 try:
1478 1473 rbsrt = rebaseruntime(repo, ui, {})
1479 1474 rbsrt.restorestatus()
1480 1475 state = rbsrt.state
1481 1476 except error.RepoLookupError:
1482 1477 # i18n: column positioning for "hg summary"
1483 1478 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1484 1479 ui.write(msg)
1485 1480 return
1486 1481 numrebased = len([i for i in state.itervalues() if i >= 0])
1487 1482 # i18n: column positioning for "hg summary"
1488 1483 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1489 1484 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1490 1485 ui.label(_('%d remaining'), 'rebase.remaining') %
1491 1486 (len(state) - numrebased)))
1492 1487
1493 1488 def uisetup(ui):
1494 1489 #Replace pull with a decorator to provide --rebase option
1495 1490 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1496 1491 entry[1].append(('', 'rebase', None,
1497 1492 _("rebase working directory to branch head")))
1498 1493 entry[1].append(('t', 'tool', '',
1499 1494 _("specify merge tool for rebase")))
1500 1495 cmdutil.summaryhooks.add('rebase', summaryhook)
1501 1496 cmdutil.unfinishedstates.append(
1502 1497 ['rebasestate', False, False, _('rebase in progress'),
1503 1498 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1504 1499 cmdutil.afterresolvedstates.append(
1505 1500 ['rebasestate', _('hg rebase --continue')])
1506 1501 # ensure rebased rev are not hidden
1507 1502 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
@@ -1,475 +1,474 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 >
5 5 > [phases]
6 6 > publish=False
7 7 >
8 8 > [alias]
9 9 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
10 10 > EOF
11 11
12 12
13 13 $ hg init a
14 14 $ cd a
15 15
16 16 $ touch .hg/rebasestate
17 17 $ hg sum
18 18 parent: -1:000000000000 tip (empty repository)
19 19 branch: default
20 20 commit: (clean)
21 21 update: (current)
22 22 abort: .hg/rebasestate is incomplete
23 23 [255]
24 24 $ rm .hg/rebasestate
25 25
26 26 $ echo c1 > common
27 27 $ hg add common
28 28 $ hg ci -m C1
29 29
30 30 $ echo c2 >> common
31 31 $ hg ci -m C2
32 32
33 33 $ echo c3 >> common
34 34 $ hg ci -m C3
35 35
36 36 $ hg up -q -C 1
37 37
38 38 $ echo l1 >> extra
39 39 $ hg add extra
40 40 $ hg ci -m L1
41 41 created new head
42 42
43 43 $ sed -e 's/c2/l2/' common > common.new
44 44 $ mv common.new common
45 45 $ hg ci -m L2
46 46
47 47 $ hg phase --force --secret 2
48 48
49 49 $ hg tglog
50 50 @ 4:draft 'L2'
51 51 |
52 52 o 3:draft 'L1'
53 53 |
54 54 | o 2:secret 'C3'
55 55 |/
56 56 o 1:draft 'C2'
57 57 |
58 58 o 0:draft 'C1'
59 59
60 60
61 61 Conflicting rebase:
62 62
63 63 $ hg rebase -s 3 -d 2
64 64 rebasing 3:3163e20567cc "L1"
65 65 rebasing 4:46f0b057b5c0 "L2" (tip)
66 66 merging common
67 67 warning: conflicts while merging common! (edit, then use 'hg resolve --mark')
68 68 unresolved conflicts (see hg resolve, then hg rebase --continue)
69 69 [1]
70 70
71 71 Insert unsupported advisory merge record:
72 72
73 73 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
74 74 $ hg debugmergestate
75 75 * version 2 records
76 76 local: 3e046f2ecedb793b97ed32108086edd1a162f8bc
77 77 other: 46f0b057b5c061d276b91491c22151f78698abd2
78 78 labels:
79 79 local: dest
80 80 other: source
81 81 unrecognized entry: x advisory record
82 82 file extras: common (ancestorlinknode = 3163e20567cc93074fbb7a53c8b93312e59dbf2c)
83 83 file: common (record type "F", state "u", hash 94c8c21d08740f5da9eaa38d1f175c592692f0d1)
84 84 local path: common (flags "")
85 85 ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6)
86 86 other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5)
87 87 $ hg resolve -l
88 88 U common
89 89
90 90 Insert unsupported mandatory merge record:
91 91
92 92 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
93 93 $ hg debugmergestate
94 94 * version 2 records
95 95 local: 3e046f2ecedb793b97ed32108086edd1a162f8bc
96 96 other: 46f0b057b5c061d276b91491c22151f78698abd2
97 97 labels:
98 98 local: dest
99 99 other: source
100 100 file extras: common (ancestorlinknode = 3163e20567cc93074fbb7a53c8b93312e59dbf2c)
101 101 file: common (record type "F", state "u", hash 94c8c21d08740f5da9eaa38d1f175c592692f0d1)
102 102 local path: common (flags "")
103 103 ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6)
104 104 other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5)
105 105 unrecognized entry: X mandatory record
106 106 $ hg resolve -l
107 107 abort: unsupported merge state records: X
108 108 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
109 109 [255]
110 110 $ hg resolve -ma
111 111 abort: unsupported merge state records: X
112 112 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
113 113 [255]
114 114
115 115 Abort (should clear out unsupported merge state):
116 116
117 117 $ hg rebase --abort
118 118 saved backup bundle to $TESTTMP/a/.hg/strip-backup/3e046f2ecedb-6beef7d5-backup.hg (glob)
119 119 rebase aborted
120 120 $ hg debugmergestate
121 121 no merge state found
122 122
123 123 $ hg tglog
124 124 @ 4:draft 'L2'
125 125 |
126 126 o 3:draft 'L1'
127 127 |
128 128 | o 2:secret 'C3'
129 129 |/
130 130 o 1:draft 'C2'
131 131 |
132 132 o 0:draft 'C1'
133 133
134 134 Test safety for inconsistent rebase state, which may be created (and
135 135 forgotten) by Mercurial earlier than 2.7. This emulates Mercurial
136 136 earlier than 2.7 by renaming ".hg/rebasestate" temporarily.
137 137
138 138 $ hg rebase -s 3 -d 2
139 139 rebasing 3:3163e20567cc "L1"
140 140 rebasing 4:46f0b057b5c0 "L2" (tip)
141 141 merging common
142 142 warning: conflicts while merging common! (edit, then use 'hg resolve --mark')
143 143 unresolved conflicts (see hg resolve, then hg rebase --continue)
144 144 [1]
145 145
146 146 $ mv .hg/rebasestate .hg/rebasestate.back
147 147 $ hg update --quiet --clean 2
148 148 $ hg --config extensions.mq= strip --quiet "destination()"
149 149 $ mv .hg/rebasestate.back .hg/rebasestate
150 150
151 151 $ hg rebase --continue
152 152 abort: cannot continue inconsistent rebase
153 153 (use "hg rebase --abort" to clear broken state)
154 154 [255]
155 155 $ hg summary | grep '^rebase: '
156 156 rebase: (use "hg rebase --abort" to clear broken state)
157 157 $ hg rebase --abort
158 158 rebase aborted (no revision is removed, only broken state is cleared)
159 159
160 160 $ cd ..
161 161
162 162
163 163 Construct new repo:
164 164
165 165 $ hg init b
166 166 $ cd b
167 167
168 168 $ echo a > a
169 169 $ hg ci -Am A
170 170 adding a
171 171
172 172 $ echo b > b
173 173 $ hg ci -Am B
174 174 adding b
175 175
176 176 $ echo c > c
177 177 $ hg ci -Am C
178 178 adding c
179 179
180 180 $ hg up -q 0
181 181
182 182 $ echo b > b
183 183 $ hg ci -Am 'B bis'
184 184 adding b
185 185 created new head
186 186
187 187 $ echo c1 > c
188 188 $ hg ci -Am C1
189 189 adding c
190 190
191 191 $ hg phase --force --secret 1
192 192 $ hg phase --public 1
193 193
194 194 Rebase and abort without generating new changesets:
195 195
196 196 $ hg tglog
197 197 @ 4:draft 'C1'
198 198 |
199 199 o 3:draft 'B bis'
200 200 |
201 201 | o 2:secret 'C'
202 202 | |
203 203 | o 1:public 'B'
204 204 |/
205 205 o 0:public 'A'
206 206
207 207 $ hg rebase -b 4 -d 2
208 208 rebasing 3:a6484957d6b9 "B bis"
209 209 note: rebase of 3:a6484957d6b9 created no changes to commit
210 210 rebasing 4:145842775fec "C1" (tip)
211 211 merging c
212 212 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
213 213 unresolved conflicts (see hg resolve, then hg rebase --continue)
214 214 [1]
215 215
216 216 $ hg tglog
217 217 @ 4:draft 'C1'
218 218 |
219 219 o 3:draft 'B bis'
220 220 |
221 221 | @ 2:secret 'C'
222 222 | |
223 223 | o 1:public 'B'
224 224 |/
225 225 o 0:public 'A'
226 226
227 227 $ hg rebase -a
228 228 rebase aborted
229 229
230 230 $ hg tglog
231 231 @ 4:draft 'C1'
232 232 |
233 233 o 3:draft 'B bis'
234 234 |
235 235 | o 2:secret 'C'
236 236 | |
237 237 | o 1:public 'B'
238 238 |/
239 239 o 0:public 'A'
240 240
241 241
242 242 $ cd ..
243 243
244 244 rebase abort should not leave working copy in a merge state if tip-1 is public
245 245 (issue4082)
246 246
247 247 $ hg init abortpublic
248 248 $ cd abortpublic
249 249 $ echo a > a && hg ci -Aqm a
250 250 $ hg book master
251 251 $ hg book foo
252 252 $ echo b > b && hg ci -Aqm b
253 253 $ hg up -q master
254 254 $ echo c > c && hg ci -Aqm c
255 255 $ hg phase -p -r .
256 256 $ hg up -q foo
257 257 $ echo C > c && hg ci -Aqm C
258 258 $ hg log -G --template "{rev} {desc} {bookmarks}"
259 259 @ 3 C foo
260 260 |
261 261 | o 2 c master
262 262 | |
263 263 o | 1 b
264 264 |/
265 265 o 0 a
266 266
267 267
268 268 $ hg rebase -d master -r foo
269 269 rebasing 3:6c0f977a22d8 "C" (tip foo)
270 270 merging c
271 271 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
272 272 unresolved conflicts (see hg resolve, then hg rebase --continue)
273 273 [1]
274 274 $ hg rebase --abort
275 275 rebase aborted
276 276 $ hg log -G --template "{rev} {desc} {bookmarks}"
277 277 @ 3 C foo
278 278 |
279 279 | o 2 c master
280 280 | |
281 281 o | 1 b
282 282 |/
283 283 o 0 a
284 284
285 285 $ cd ..
286 286
287 287 Make sure we don't clobber changes in the working directory when the
288 288 user has somehow managed to update to a different revision (issue4009)
289 289
290 290 $ hg init noupdate
291 291 $ cd noupdate
292 292 $ hg book @
293 293 $ echo original > a
294 294 $ hg add a
295 295 $ hg commit -m a
296 296 $ echo x > b
297 297 $ hg add b
298 298 $ hg commit -m b1
299 299 $ hg up 0
300 300 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
301 301 (leaving bookmark @)
302 302 $ hg book foo
303 303 $ echo y > b
304 304 $ hg add b
305 305 $ hg commit -m b2
306 306 created new head
307 307
308 308 $ hg rebase -d @ -b foo --tool=internal:fail
309 309 rebasing 2:070cf4580bb5 "b2" (tip foo)
310 310 unresolved conflicts (see hg resolve, then hg rebase --continue)
311 311 [1]
312 312
313 313 $ mv .hg/rebasestate ./ # so we're allowed to hg up like in mercurial <2.6.3
314 314 $ hg up -C 0 # user does other stuff in the repo
315 315 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
316 316
317 317 $ mv rebasestate .hg/ # user upgrades to 2.7
318 318
319 319 $ echo new > a
320 320 $ hg up 1 # user gets an error saying to run hg rebase --abort
321 321 abort: rebase in progress
322 322 (use 'hg rebase --continue' or 'hg rebase --abort')
323 323 [255]
324 324
325 325 $ cat a
326 326 new
327 327 $ hg rebase --abort
328 328 rebase aborted
329 329 $ cat a
330 330 new
331 331
332 332 $ cd ..
333 333
334 334 test aborting an interrupted series (issue5084)
335 335 $ hg init interrupted
336 336 $ cd interrupted
337 337 $ touch base
338 338 $ hg add base
339 339 $ hg commit -m base
340 340 $ touch a
341 341 $ hg add a
342 342 $ hg commit -m a
343 343 $ echo 1 > a
344 344 $ hg commit -m 1
345 345 $ touch b
346 346 $ hg add b
347 347 $ hg commit -m b
348 348 $ echo 2 >> a
349 349 $ hg commit -m c
350 350 $ touch d
351 351 $ hg add d
352 352 $ hg commit -m d
353 353 $ hg co -q 1
354 354 $ hg rm a
355 355 $ hg commit -m no-a
356 356 created new head
357 357 $ hg co 0
358 358 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
359 359 $ hg log -G --template "{rev} {desc} {bookmarks}"
360 360 o 6 no-a
361 361 |
362 362 | o 5 d
363 363 | |
364 364 | o 4 c
365 365 | |
366 366 | o 3 b
367 367 | |
368 368 | o 2 1
369 369 |/
370 370 o 1 a
371 371 |
372 372 @ 0 base
373 373
374 374 $ hg --config extensions.n=$TESTDIR/failfilemerge.py rebase -s 3 -d tip
375 375 rebasing 3:3a71550954f1 "b"
376 376 rebasing 4:e80b69427d80 "c"
377 transaction abort!
378 rollback completed
379 377 abort: ^C
380 378 [255]
381 379 $ hg rebase --abort
380 saved backup bundle to $TESTTMP/interrupted/.hg/strip-backup/3d8812cf300d-93041a90-backup.hg (glob)
382 381 rebase aborted
383 382 $ hg log -G --template "{rev} {desc} {bookmarks}"
384 383 o 6 no-a
385 384 |
386 385 | o 5 d
387 386 | |
388 387 | o 4 c
389 388 | |
390 389 | o 3 b
391 390 | |
392 391 | o 2 1
393 392 |/
394 393 o 1 a
395 394 |
396 395 @ 0 base
397 396
398 397 $ hg summary
399 398 parent: 0:df4f53cec30a
400 399 base
401 400 branch: default
402 commit: 1 unknown (clean)
401 commit: (clean)
403 402 update: 6 new changesets (update)
404 403 phases: 7 draft
405 404
406 405 $ cd ..
407 406 On the other hand, make sure we *do* clobber changes whenever we
408 407 haven't somehow managed to update the repo to a different revision
409 408 during a rebase (issue4661)
410 409
411 410 $ hg ini yesupdate
412 411 $ cd yesupdate
413 412 $ echo "initial data" > foo.txt
414 413 $ hg add
415 414 adding foo.txt
416 415 $ hg ci -m "initial checkin"
417 416 $ echo "change 1" > foo.txt
418 417 $ hg ci -m "change 1"
419 418 $ hg up 0
420 419 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
421 420 $ echo "conflicting change 1" > foo.txt
422 421 $ hg ci -m "conflicting 1"
423 422 created new head
424 423 $ echo "conflicting change 2" > foo.txt
425 424 $ hg ci -m "conflicting 2"
426 425
427 426 $ hg rebase -d 1 --tool 'internal:fail'
428 427 rebasing 2:e4ea5cdc9789 "conflicting 1"
429 428 unresolved conflicts (see hg resolve, then hg rebase --continue)
430 429 [1]
431 430 $ hg rebase --abort
432 431 rebase aborted
433 432 $ hg summary
434 433 parent: 3:b16646383533 tip
435 434 conflicting 2
436 435 branch: default
437 436 commit: (clean)
438 437 update: 1 new changesets, 2 branch heads (merge)
439 438 phases: 4 draft
440 439 $ cd ..
441 440
442 441 test aborting a rebase succeeds after rebasing with skipped commits onto a
443 442 public changeset (issue4896)
444 443
445 444 $ hg init succeedonpublic
446 445 $ cd succeedonpublic
447 446 $ echo 'content' > root
448 447 $ hg commit -A -m 'root' -q
449 448
450 449 set up public branch
451 450 $ echo 'content' > disappear
452 451 $ hg commit -A -m 'disappear public' -q
453 452 commit will cause merge conflict on rebase
454 453 $ echo '' > root
455 454 $ hg commit -m 'remove content public' -q
456 455 $ hg phase --public
457 456
458 457 setup the draft branch that will be rebased onto public commit
459 458 $ hg up -r 0 -q
460 459 $ echo 'content' > disappear
461 460 commit will disappear
462 461 $ hg commit -A -m 'disappear draft' -q
463 462 $ echo 'addedcontADDEDentadded' > root
464 463 commit will cause merge conflict on rebase
465 464 $ hg commit -m 'add content draft' -q
466 465
467 466 $ hg rebase -d 'public()' --tool :merge -q
468 467 note: rebase of 3:0682fd3dabf5 created no changes to commit
469 468 warning: conflicts while merging root! (edit, then use 'hg resolve --mark')
470 469 unresolved conflicts (see hg resolve, then hg rebase --continue)
471 470 [1]
472 471 $ hg rebase --abort
473 472 rebase aborted
474 473 $ cd ..
475 474
@@ -1,361 +1,362 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [format]
3 3 > usegeneraldelta=yes
4 4 > [extensions]
5 5 > rebase=
6 6 >
7 7 > [phases]
8 8 > publish=False
9 9 >
10 10 > [alias]
11 11 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches} {bookmarks}\n"
12 12 > EOF
13 13
14 14 $ hg init a
15 15 $ cd a
16 16 $ echo c1 >common
17 17 $ hg add common
18 18 $ hg ci -m C1
19 19
20 20 $ echo c2 >>common
21 21 $ hg ci -m C2
22 22
23 23 $ echo c3 >>common
24 24 $ hg ci -m C3
25 25
26 26 $ hg up -q -C 1
27 27
28 28 $ echo l1 >>extra
29 29 $ hg add extra
30 30 $ hg ci -m L1
31 31 created new head
32 32
33 33 $ sed -e 's/c2/l2/' common > common.new
34 34 $ mv common.new common
35 35 $ hg ci -m L2
36 36
37 37 $ echo l3 >> extra2
38 38 $ hg add extra2
39 39 $ hg ci -m L3
40 40 $ hg bookmark mybook
41 41
42 42 $ hg phase --force --secret 4
43 43
44 44 $ hg tglog
45 45 @ 5:secret 'L3' mybook
46 46 |
47 47 o 4:secret 'L2'
48 48 |
49 49 o 3:draft 'L1'
50 50 |
51 51 | o 2:draft 'C3'
52 52 |/
53 53 o 1:draft 'C2'
54 54 |
55 55 o 0:draft 'C1'
56 56
57 57 Try to call --continue:
58 58
59 59 $ hg rebase --continue
60 60 abort: no rebase in progress
61 61 [255]
62 62
63 63 Conflicting rebase:
64 64
65 65 $ hg rebase -s 3 -d 2
66 66 rebasing 3:3163e20567cc "L1"
67 67 rebasing 4:46f0b057b5c0 "L2"
68 68 merging common
69 69 warning: conflicts while merging common! (edit, then use 'hg resolve --mark')
70 70 unresolved conflicts (see hg resolve, then hg rebase --continue)
71 71 [1]
72 72
73 73 Try to continue without solving the conflict:
74 74
75 75 $ hg rebase --continue
76 76 abort: unresolved merge conflicts (see 'hg help resolve')
77 77 [255]
78 78
79 79 Conclude rebase:
80 80
81 81 $ echo 'resolved merge' >common
82 82 $ hg resolve -m common
83 83 (no more unresolved files)
84 84 continue: hg rebase --continue
85 85 $ hg rebase --continue
86 86 already rebased 3:3163e20567cc "L1" as 3e046f2ecedb
87 87 rebasing 4:46f0b057b5c0 "L2"
88 88 rebasing 5:8029388f38dc "L3" (mybook)
89 89 saved backup bundle to $TESTTMP/a/.hg/strip-backup/3163e20567cc-5ca4656e-backup.hg (glob)
90 90
91 91 $ hg tglog
92 92 @ 5:secret 'L3' mybook
93 93 |
94 94 o 4:secret 'L2'
95 95 |
96 96 o 3:draft 'L1'
97 97 |
98 98 o 2:draft 'C3'
99 99 |
100 100 o 1:draft 'C2'
101 101 |
102 102 o 0:draft 'C1'
103 103
104 104 Check correctness:
105 105
106 106 $ hg cat -r 0 common
107 107 c1
108 108
109 109 $ hg cat -r 1 common
110 110 c1
111 111 c2
112 112
113 113 $ hg cat -r 2 common
114 114 c1
115 115 c2
116 116 c3
117 117
118 118 $ hg cat -r 3 common
119 119 c1
120 120 c2
121 121 c3
122 122
123 123 $ hg cat -r 4 common
124 124 resolved merge
125 125
126 126 $ hg cat -r 5 common
127 127 resolved merge
128 128
129 129 Bookmark stays active after --continue
130 130 $ hg bookmarks
131 131 * mybook 5:d67b21408fc0
132 132
133 133 $ cd ..
134 134
135 135 Check that the right ancestors is used while rebasing a merge (issue4041)
136 136
137 137 $ hg clone "$TESTDIR/bundles/issue4041.hg" issue4041
138 138 requesting all changes
139 139 adding changesets
140 140 adding manifests
141 141 adding file changes
142 142 added 11 changesets with 8 changes to 3 files (+1 heads)
143 143 updating to branch default
144 144 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
145 145 $ cd issue4041
146 146 $ hg log -G
147 147 o changeset: 10:2f2496ddf49d
148 148 |\ branch: f1
149 149 | | tag: tip
150 150 | | parent: 7:4c9fbe56a16f
151 151 | | parent: 9:e31216eec445
152 152 | | user: szhang
153 153 | | date: Thu Sep 05 12:59:39 2013 -0400
154 154 | | summary: merge
155 155 | |
156 156 | o changeset: 9:e31216eec445
157 157 | | branch: f1
158 158 | | user: szhang
159 159 | | date: Thu Sep 05 12:59:10 2013 -0400
160 160 | | summary: more changes to f1
161 161 | |
162 162 | o changeset: 8:8e4e2c1a07ae
163 163 | |\ branch: f1
164 164 | | | parent: 2:4bc80088dc6b
165 165 | | | parent: 6:400110238667
166 166 | | | user: szhang
167 167 | | | date: Thu Sep 05 12:57:59 2013 -0400
168 168 | | | summary: bad merge
169 169 | | |
170 170 o | | changeset: 7:4c9fbe56a16f
171 171 |/ / branch: f1
172 172 | | parent: 2:4bc80088dc6b
173 173 | | user: szhang
174 174 | | date: Thu Sep 05 12:54:00 2013 -0400
175 175 | | summary: changed f1
176 176 | |
177 177 | o changeset: 6:400110238667
178 178 | | branch: f2
179 179 | | parent: 4:12e8ec6bb010
180 180 | | user: szhang
181 181 | | date: Tue Sep 03 13:58:02 2013 -0400
182 182 | | summary: changed f2 on f2
183 183 | |
184 184 | | @ changeset: 5:d79e2059b5c0
185 185 | | | parent: 3:8a951942e016
186 186 | | | user: szhang
187 187 | | | date: Tue Sep 03 13:57:39 2013 -0400
188 188 | | | summary: changed f2 on default
189 189 | | |
190 190 | o | changeset: 4:12e8ec6bb010
191 191 | |/ branch: f2
192 192 | | user: szhang
193 193 | | date: Tue Sep 03 13:57:18 2013 -0400
194 194 | | summary: created f2 branch
195 195 | |
196 196 | o changeset: 3:8a951942e016
197 197 | | parent: 0:24797d4f68de
198 198 | | user: szhang
199 199 | | date: Tue Sep 03 13:57:11 2013 -0400
200 200 | | summary: added f2.txt
201 201 | |
202 202 o | changeset: 2:4bc80088dc6b
203 203 | | branch: f1
204 204 | | user: szhang
205 205 | | date: Tue Sep 03 13:56:20 2013 -0400
206 206 | | summary: added f1.txt
207 207 | |
208 208 o | changeset: 1:ef53c9e6b608
209 209 |/ branch: f1
210 210 | user: szhang
211 211 | date: Tue Sep 03 13:55:26 2013 -0400
212 212 | summary: created f1 branch
213 213 |
214 214 o changeset: 0:24797d4f68de
215 215 user: szhang
216 216 date: Tue Sep 03 13:55:08 2013 -0400
217 217 summary: added default.txt
218 218
219 219 $ hg rebase -s9 -d2 --debug # use debug to really check merge base used
220 220 rebase onto 4bc80088dc6b starting from e31216eec445
221 221 rebase status stored
222 222 ignoring null merge rebase of 3
223 223 ignoring null merge rebase of 4
224 224 ignoring null merge rebase of 6
225 225 ignoring null merge rebase of 8
226 226 rebasing 9:e31216eec445 "more changes to f1"
227 227 future parents are 2 and -1
228 rebase status stored
228 229 update to 2:4bc80088dc6b
229 230 resolving manifests
230 231 branchmerge: False, force: True, partial: False
231 232 ancestor: d79e2059b5c0+, local: d79e2059b5c0+, remote: 4bc80088dc6b
232 233 f2.txt: other deleted -> r
233 234 removing f2.txt
234 235 f1.txt: remote created -> g
235 236 getting f1.txt
236 237 merge against 9:e31216eec445
237 238 detach base 8:8e4e2c1a07ae
238 239 searching for copies back to rev 3
239 240 unmatched files in other (from topological common ancestor):
240 241 f2.txt
241 242 resolving manifests
242 243 branchmerge: True, force: True, partial: False
243 244 ancestor: 8e4e2c1a07ae, local: 4bc80088dc6b+, remote: e31216eec445
244 245 f1.txt: remote is newer -> g
245 246 getting f1.txt
246 247 committing files:
247 248 f1.txt
248 249 committing manifest
249 250 committing changelog
250 251 rebased as 19c888675e13
251 252 rebasing 10:2f2496ddf49d "merge" (tip)
252 253 future parents are 11 and 7
254 rebase status stored
253 255 already in target
254 256 merge against 10:2f2496ddf49d
255 257 detach base 9:e31216eec445
256 258 searching for copies back to rev 3
257 259 unmatched files in other (from topological common ancestor):
258 260 f2.txt
259 261 resolving manifests
260 262 branchmerge: True, force: True, partial: False
261 263 ancestor: e31216eec445, local: 19c888675e13+, remote: 2f2496ddf49d
262 264 f1.txt: remote is newer -> g
263 265 getting f1.txt
264 266 committing files:
265 267 f1.txt
266 268 committing manifest
267 269 committing changelog
268 270 rebased as 2a7f09cac94c
269 271 rebase merging completed
270 rebase status stored
271 272 update back to initial working directory parent
272 273 resolving manifests
273 274 branchmerge: False, force: False, partial: False
274 275 ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0
275 276 f1.txt: other deleted -> r
276 277 removing f1.txt
277 278 f2.txt: remote created -> g
278 279 getting f2.txt
279 280 2 changesets found
280 281 list of changesets:
281 282 e31216eec445e44352c5f01588856059466a24c9
282 283 2f2496ddf49d69b5ef23ad8cf9fb2e0e4faf0ac2
283 284 bundle2-output-bundle: "HG20", (1 params) 1 parts total
284 285 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
285 286 saved backup bundle to $TESTTMP/issue4041/.hg/strip-backup/e31216eec445-15f7a814-backup.hg (glob)
286 287 3 changesets found
287 288 list of changesets:
288 289 4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c
289 290 19c888675e133ab5dff84516926a65672eaf04d9
290 291 2a7f09cac94c7f4b73ebd5cd1a62d3b2e8e336bf
291 292 bundle2-output-bundle: "HG20", 1 parts total
292 293 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
293 294 adding branch
294 295 bundle2-input-bundle: with-transaction
295 296 bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
296 297 adding changesets
297 298 add changeset 4c9fbe56a16f
298 299 add changeset 19c888675e13
299 300 add changeset 2a7f09cac94c
300 301 adding manifests
301 302 adding file changes
302 303 adding f1.txt revisions
303 304 added 2 changesets with 2 changes to 1 files
304 305 bundle2-input-part: total payload size 1686
305 306 bundle2-input-bundle: 0 parts total
306 307 invalid branchheads cache (served): tip differs
307 308 history modification detected - truncating revision branch cache to revision 9
308 309 rebase completed
309 310 truncating cache/rbc-revs-v1 to 72
310 311
311 312 Test minimization of merge conflicts
312 313 $ hg up -q null
313 314 $ echo a > a
314 315 $ hg add a
315 316 $ hg commit -q -m 'a'
316 317 $ echo b >> a
317 318 $ hg commit -q -m 'ab'
318 319 $ hg bookmark ab
319 320 $ hg up -q '.^'
320 321 $ echo b >> a
321 322 $ echo c >> a
322 323 $ hg commit -q -m 'abc'
323 324 $ hg rebase -s 7bc217434fc1 -d ab --keep
324 325 rebasing 13:7bc217434fc1 "abc" (tip)
325 326 merging a
326 327 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
327 328 unresolved conflicts (see hg resolve, then hg rebase --continue)
328 329 [1]
329 330 $ hg diff
330 331 diff -r 328e4ab1f7cc a
331 332 --- a/a Thu Jan 01 00:00:00 1970 +0000
332 333 +++ b/a * (glob)
333 334 @@ -1,2 +1,6 @@
334 335 a
335 336 b
336 337 +<<<<<<< dest: 328e4ab1f7cc ab - test: ab
337 338 +=======
338 339 +c
339 340 +>>>>>>> source: 7bc217434fc1 - test: abc
340 341 $ hg rebase --abort
341 342 rebase aborted
342 343 $ hg up -q -C 7bc217434fc1
343 344 $ hg rebase -s . -d ab --keep -t internal:merge3
344 345 rebasing 13:7bc217434fc1 "abc" (tip)
345 346 merging a
346 347 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
347 348 unresolved conflicts (see hg resolve, then hg rebase --continue)
348 349 [1]
349 350 $ hg diff
350 351 diff -r 328e4ab1f7cc a
351 352 --- a/a Thu Jan 01 00:00:00 1970 +0000
352 353 +++ b/a * (glob)
353 354 @@ -1,2 +1,8 @@
354 355 a
355 356 +<<<<<<< dest: 328e4ab1f7cc ab - test: ab
356 357 b
357 358 +||||||| base
358 359 +=======
359 360 +b
360 361 +c
361 362 +>>>>>>> source: 7bc217434fc1 - test: abc
General Comments 0
You need to be logged in to leave comments. Login now