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