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