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