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