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