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