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