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