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