##// END OF EJS Templates
rebase: pass repo, ui and opts objects to the RR class constructor
Kostia Balytskyi -
r29399:adb0d58b default
parent child Browse files
Show More
@@ -1,1410 +1,1416
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 hex,
25 25 nullid,
26 26 nullrev,
27 27 short,
28 28 )
29 29 from mercurial import (
30 30 bookmarks,
31 31 cmdutil,
32 32 commands,
33 33 copies,
34 34 destutil,
35 35 error,
36 36 extensions,
37 37 hg,
38 38 lock,
39 39 merge,
40 40 obsolete,
41 41 patch,
42 42 phases,
43 43 registrar,
44 44 repair,
45 45 repoview,
46 46 revset,
47 47 scmutil,
48 48 util,
49 49 )
50 50
51 51 release = lock.release
52 52 templateopts = commands.templateopts
53 53
54 54 # The following constants are used throughout the rebase module. The ordering of
55 55 # their values must be maintained.
56 56
57 57 # Indicates that a revision needs to be rebased
58 58 revtodo = -1
59 59 nullmerge = -2
60 60 revignored = -3
61 61 # successor in rebase destination
62 62 revprecursor = -4
63 63 # plain prune (no successor)
64 64 revpruned = -5
65 65 revskipped = (revignored, revprecursor, revpruned)
66 66
67 67 cmdtable = {}
68 68 command = cmdutil.command(cmdtable)
69 69 # Note for extension authors: ONLY specify testedwith = 'internal' for
70 70 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
71 71 # be specifying the version(s) of Mercurial they are tested with, or
72 72 # leave the attribute unspecified.
73 73 testedwith = 'internal'
74 74
75 75 def _nothingtorebase():
76 76 return 1
77 77
78 78 def _savegraft(ctx, extra):
79 79 s = ctx.extra().get('source', None)
80 80 if s is not None:
81 81 extra['source'] = s
82 82 s = ctx.extra().get('intermediate-source', None)
83 83 if s is not None:
84 84 extra['intermediate-source'] = s
85 85
86 86 def _savebranch(ctx, extra):
87 87 extra['branch'] = ctx.branch()
88 88
89 89 def _makeextrafn(copiers):
90 90 """make an extrafn out of the given copy-functions.
91 91
92 92 A copy function takes a context and an extra dict, and mutates the
93 93 extra dict as needed based on the given context.
94 94 """
95 95 def extrafn(ctx, extra):
96 96 for c in copiers:
97 97 c(ctx, extra)
98 98 return extrafn
99 99
100 100 def _destrebase(repo, sourceset, destspace=None):
101 101 """small wrapper around destmerge to pass the right extra args
102 102
103 103 Please wrap destutil.destmerge instead."""
104 104 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
105 105 onheadcheck=False, destspace=destspace)
106 106
107 107 revsetpredicate = registrar.revsetpredicate()
108 108
109 109 @revsetpredicate('_destrebase')
110 110 def _revsetdestrebase(repo, subset, x):
111 111 # ``_rebasedefaultdest()``
112 112
113 113 # default destination for rebase.
114 114 # # XXX: Currently private because I expect the signature to change.
115 115 # # XXX: - bailing out in case of ambiguity vs returning all data.
116 116 # i18n: "_rebasedefaultdest" is a keyword
117 117 sourceset = None
118 118 if x is not None:
119 119 sourceset = revset.getset(repo, revset.fullreposet(repo), x)
120 120 return subset & revset.baseset([_destrebase(repo, sourceset)])
121 121
122 122 class rebaseruntime(object):
123 123 """This class is a container for rebase runtime state"""
124 def __init__(self):
124 def __init__(self, repo, ui, opts=None):
125 if opts is None:
126 opts = {}
127
128 self.repo = repo
129 self.ui = ui
130 self.opts = opts
125 131 self.originalwd = None
126 132 self.external = nullrev
127 133 # Mapping between the old revision id and either what is the new rebased
128 134 # revision or what needs to be done with the old revision. The state
129 135 # dict will be what contains most of the rebase progress state.
130 136 self.state = {}
131 137 self.activebookmark = None
132 138 self.target = None
133 139 self.skipped = set()
134 140 self.targetancestors = set()
135 141
136 142 @command('rebase',
137 143 [('s', 'source', '',
138 144 _('rebase the specified changeset and descendants'), _('REV')),
139 145 ('b', 'base', '',
140 146 _('rebase everything from branching point of specified changeset'),
141 147 _('REV')),
142 148 ('r', 'rev', [],
143 149 _('rebase these revisions'),
144 150 _('REV')),
145 151 ('d', 'dest', '',
146 152 _('rebase onto the specified changeset'), _('REV')),
147 153 ('', 'collapse', False, _('collapse the rebased changesets')),
148 154 ('m', 'message', '',
149 155 _('use text as collapse commit message'), _('TEXT')),
150 156 ('e', 'edit', False, _('invoke editor on commit messages')),
151 157 ('l', 'logfile', '',
152 158 _('read collapse commit message from file'), _('FILE')),
153 159 ('k', 'keep', False, _('keep original changesets')),
154 160 ('', 'keepbranches', False, _('keep original branch names')),
155 161 ('D', 'detach', False, _('(DEPRECATED)')),
156 162 ('i', 'interactive', False, _('(DEPRECATED)')),
157 163 ('t', 'tool', '', _('specify merge tool')),
158 164 ('c', 'continue', False, _('continue an interrupted rebase')),
159 165 ('a', 'abort', False, _('abort an interrupted rebase'))] +
160 166 templateopts,
161 167 _('[-s REV | -b REV] [-d REV] [OPTION]'))
162 168 def rebase(ui, repo, **opts):
163 169 """move changeset (and descendants) to a different branch
164 170
165 171 Rebase uses repeated merging to graft changesets from one part of
166 172 history (the source) onto another (the destination). This can be
167 173 useful for linearizing *local* changes relative to a master
168 174 development tree.
169 175
170 176 Published commits cannot be rebased (see :hg:`help phases`).
171 177 To copy commits, see :hg:`help graft`.
172 178
173 179 If you don't specify a destination changeset (``-d/--dest``), rebase
174 180 will use the same logic as :hg:`merge` to pick a destination. if
175 181 the current branch contains exactly one other head, the other head
176 182 is merged with by default. Otherwise, an explicit revision with
177 183 which to merge with must be provided. (destination changeset is not
178 184 modified by rebasing, but new changesets are added as its
179 185 descendants.)
180 186
181 187 Here are the ways to select changesets:
182 188
183 189 1. Explicitly select them using ``--rev``.
184 190
185 191 2. Use ``--source`` to select a root changeset and include all of its
186 192 descendants.
187 193
188 194 3. Use ``--base`` to select a changeset; rebase will find ancestors
189 195 and their descendants which are not also ancestors of the destination.
190 196
191 197 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
192 198 rebase will use ``--base .`` as above.
193 199
194 200 Rebase will destroy original changesets unless you use ``--keep``.
195 201 It will also move your bookmarks (even if you do).
196 202
197 203 Some changesets may be dropped if they do not contribute changes
198 204 (e.g. merges from the destination branch).
199 205
200 206 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
201 207 a named branch with two heads. You will need to explicitly specify source
202 208 and/or destination.
203 209
204 210 If you need to use a tool to automate merge/conflict decisions, you
205 211 can specify one with ``--tool``, see :hg:`help merge-tools`.
206 212 As a caveat: the tool will not be used to mediate when a file was
207 213 deleted, there is no hook presently available for this.
208 214
209 215 If a rebase is interrupted to manually resolve a conflict, it can be
210 216 continued with --continue/-c or aborted with --abort/-a.
211 217
212 218 .. container:: verbose
213 219
214 220 Examples:
215 221
216 222 - move "local changes" (current commit back to branching point)
217 223 to the current branch tip after a pull::
218 224
219 225 hg rebase
220 226
221 227 - move a single changeset to the stable branch::
222 228
223 229 hg rebase -r 5f493448 -d stable
224 230
225 231 - splice a commit and all its descendants onto another part of history::
226 232
227 233 hg rebase --source c0c3 --dest 4cf9
228 234
229 235 - rebase everything on a branch marked by a bookmark onto the
230 236 default branch::
231 237
232 238 hg rebase --base myfeature --dest default
233 239
234 240 - collapse a sequence of changes into a single commit::
235 241
236 242 hg rebase --collapse -r 1520:1525 -d .
237 243
238 244 - move a named branch while preserving its name::
239 245
240 246 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
241 247
242 248 Returns 0 on success, 1 if nothing to rebase or there are
243 249 unresolved conflicts.
244 250
245 251 """
246 rbsrt = rebaseruntime()
252 rbsrt = rebaseruntime(repo, ui, opts)
247 253
248 254 lock = wlock = None
249 255 try:
250 256 wlock = repo.wlock()
251 257 lock = repo.lock()
252 258
253 259 # Validate input and define rebasing points
254 260 destf = opts.get('dest', None)
255 261 srcf = opts.get('source', None)
256 262 basef = opts.get('base', None)
257 263 revf = opts.get('rev', [])
258 264 # search default destination in this space
259 265 # used in the 'hg pull --rebase' case, see issue 5214.
260 266 destspace = opts.get('_destspace')
261 267 contf = opts.get('continue')
262 268 abortf = opts.get('abort')
263 269 collapsef = opts.get('collapse', False)
264 270 collapsemsg = cmdutil.logmessage(ui, opts)
265 271 date = opts.get('date', None)
266 272 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
267 273 extrafns = [_savegraft]
268 274 if e:
269 275 extrafns = [e]
270 276 keepf = opts.get('keep', False)
271 277 keepbranchesf = opts.get('keepbranches', False)
272 278 # keepopen is not meant for use on the command line, but by
273 279 # other extensions
274 280 keepopen = opts.get('keepopen', False)
275 281
276 282 if opts.get('interactive'):
277 283 try:
278 284 if extensions.find('histedit'):
279 285 enablehistedit = ''
280 286 except KeyError:
281 287 enablehistedit = " --config extensions.histedit="
282 288 help = "hg%s help -e histedit" % enablehistedit
283 289 msg = _("interactive history editing is supported by the "
284 290 "'histedit' extension (see \"%s\")") % help
285 291 raise error.Abort(msg)
286 292
287 293 if collapsemsg and not collapsef:
288 294 raise error.Abort(
289 295 _('message can only be specified with collapse'))
290 296
291 297 if contf or abortf:
292 298 if contf and abortf:
293 299 raise error.Abort(_('cannot use both abort and continue'))
294 300 if collapsef:
295 301 raise error.Abort(
296 302 _('cannot use collapse with continue or abort'))
297 303 if srcf or basef or destf:
298 304 raise error.Abort(
299 305 _('abort and continue do not allow specifying revisions'))
300 306 if abortf and opts.get('tool', False):
301 307 ui.warn(_('tool option will be ignored\n'))
302 308
303 309 try:
304 310 (rbsrt.originalwd, rbsrt.target, rbsrt.state,
305 311 rbsrt.skipped, collapsef, keepf, keepbranchesf,
306 312 rbsrt.external, rbsrt.activebookmark) = restorestatus(repo)
307 313 collapsemsg = restorecollapsemsg(repo)
308 314 except error.RepoLookupError:
309 315 if abortf:
310 316 clearstatus(repo)
311 317 clearcollapsemsg(repo)
312 318 repo.ui.warn(_('rebase aborted (no revision is removed,'
313 319 ' only broken state is cleared)\n'))
314 320 return 0
315 321 else:
316 322 msg = _('cannot continue inconsistent rebase')
317 323 hint = _('use "hg rebase --abort" to clear broken state')
318 324 raise error.Abort(msg, hint=hint)
319 325 if abortf:
320 326 return abort(repo, rbsrt.originalwd, rbsrt.target,
321 327 rbsrt.state,
322 328 activebookmark=rbsrt.activebookmark)
323 329
324 330 obsoletenotrebased = {}
325 331 if ui.configbool('experimental', 'rebaseskipobsolete',
326 332 default=True):
327 333 rebaseobsrevs = set([r for r, st in rbsrt.state.items()
328 334 if st == revprecursor])
329 335 rebasesetrevs = set(rbsrt.state.keys())
330 336 obsoletenotrebased = _computeobsoletenotrebased(repo,
331 337 rebaseobsrevs, rbsrt.target)
332 338 rebaseobsskipped = set(obsoletenotrebased)
333 339 _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs,
334 340 rebaseobsskipped)
335 341 else:
336 342 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
337 343 destspace=destspace)
338 344 if dest is None:
339 345 return _nothingtorebase()
340 346
341 347 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
342 348 if (not (keepf or allowunstable)
343 349 and repo.revs('first(children(%ld) - %ld)',
344 350 rebaseset, rebaseset)):
345 351 raise error.Abort(
346 352 _("can't remove original changesets with"
347 353 " unrebased descendants"),
348 354 hint=_('use --keep to keep original changesets'))
349 355
350 356 obsoletenotrebased = {}
351 357 if ui.configbool('experimental', 'rebaseskipobsolete',
352 358 default=True):
353 359 rebasesetrevs = set(rebaseset)
354 360 rebaseobsrevs = _filterobsoleterevs(repo, rebasesetrevs)
355 361 obsoletenotrebased = _computeobsoletenotrebased(repo,
356 362 rebaseobsrevs,
357 363 dest)
358 364 rebaseobsskipped = set(obsoletenotrebased)
359 365 _checkobsrebase(repo, ui, rebaseobsrevs,
360 366 rebasesetrevs,
361 367 rebaseobsskipped)
362 368
363 369 result = buildstate(repo, dest, rebaseset, collapsef,
364 370 obsoletenotrebased)
365 371
366 372 if not result:
367 373 # Empty state built, nothing to rebase
368 374 ui.status(_('nothing to rebase\n'))
369 375 return _nothingtorebase()
370 376
371 377 root = min(rebaseset)
372 378 if not keepf and not repo[root].mutable():
373 379 raise error.Abort(_("can't rebase public changeset %s")
374 380 % repo[root],
375 381 hint=_('see "hg help phases" for details'))
376 382
377 383 (rbsrt.originalwd, rbsrt.target, rbsrt.state) = result
378 384 if collapsef:
379 385 rbsrt.targetancestors = repo.changelog.ancestors([rbsrt.target],
380 386 inclusive=True)
381 387 rbsrt.external = externalparent(repo, rbsrt.state,
382 388 rbsrt.targetancestors)
383 389
384 390 if dest.closesbranch() and not keepbranchesf:
385 391 ui.status(_('reopening closed branch head %s\n') % dest)
386 392
387 393 if keepbranchesf:
388 394 # insert _savebranch at the start of extrafns so if
389 395 # there's a user-provided extrafn it can clobber branch if
390 396 # desired
391 397 extrafns.insert(0, _savebranch)
392 398 if collapsef:
393 399 branches = set()
394 400 for rev in rbsrt.state:
395 401 branches.add(repo[rev].branch())
396 402 if len(branches) > 1:
397 403 raise error.Abort(_('cannot collapse multiple named '
398 404 'branches'))
399 405
400 406 # Rebase
401 407 if not rbsrt.targetancestors:
402 408 rbsrt.targetancestors = repo.changelog.ancestors([rbsrt.target],
403 409 inclusive=True)
404 410
405 411 # Keep track of the current bookmarks in order to reset them later
406 412 currentbookmarks = repo._bookmarks.copy()
407 413 rbsrt.activebookmark = rbsrt.activebookmark or repo._activebookmark
408 414 if rbsrt.activebookmark:
409 415 bookmarks.deactivate(repo)
410 416
411 417 extrafn = _makeextrafn(extrafns)
412 418
413 419 sortedstate = sorted(rbsrt.state)
414 420 total = len(sortedstate)
415 421 pos = 0
416 422 for rev in sortedstate:
417 423 ctx = repo[rev]
418 424 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
419 425 ctx.description().split('\n', 1)[0])
420 426 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
421 427 if names:
422 428 desc += ' (%s)' % ' '.join(names)
423 429 pos += 1
424 430 if rbsrt.state[rev] == revtodo:
425 431 ui.status(_('rebasing %s\n') % desc)
426 432 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
427 433 _('changesets'), total)
428 434 p1, p2, base = defineparents(repo, rev, rbsrt.target,
429 435 rbsrt.state,
430 436 rbsrt.targetancestors,
431 437 obsoletenotrebased)
432 438 storestatus(repo, rbsrt.originalwd, rbsrt.target,
433 439 rbsrt.state, collapsef, keepf, keepbranchesf,
434 440 rbsrt.external, rbsrt.activebookmark)
435 441 storecollapsemsg(repo, collapsemsg)
436 442 if len(repo[None].parents()) == 2:
437 443 repo.ui.debug('resuming interrupted rebase\n')
438 444 else:
439 445 try:
440 446 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
441 447 'rebase')
442 448 stats = rebasenode(repo, rev, p1, base, rbsrt.state,
443 449 collapsef, rbsrt.target)
444 450 if stats and stats[3] > 0:
445 451 raise error.InterventionRequired(
446 452 _('unresolved conflicts (see hg '
447 453 'resolve, then hg rebase --continue)'))
448 454 finally:
449 455 ui.setconfig('ui', 'forcemerge', '', 'rebase')
450 456 if not collapsef:
451 457 merging = p2 != nullrev
452 458 editform = cmdutil.mergeeditform(merging, 'rebase')
453 459 editor = cmdutil.getcommiteditor(editform=editform, **opts)
454 460 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
455 461 editor=editor,
456 462 keepbranches=keepbranchesf,
457 463 date=date)
458 464 else:
459 465 # Skip commit if we are collapsing
460 466 repo.dirstate.beginparentchange()
461 467 repo.setparents(repo[p1].node())
462 468 repo.dirstate.endparentchange()
463 469 newnode = None
464 470 # Update the state
465 471 if newnode is not None:
466 472 rbsrt.state[rev] = repo[newnode].rev()
467 473 ui.debug('rebased as %s\n' % short(newnode))
468 474 else:
469 475 if not collapsef:
470 476 ui.warn(_('note: rebase of %d:%s created no changes '
471 477 'to commit\n') % (rev, ctx))
472 478 rbsrt.skipped.add(rev)
473 479 rbsrt.state[rev] = p1
474 480 ui.debug('next revision set to %s\n' % p1)
475 481 elif rbsrt.state[rev] == nullmerge:
476 482 ui.debug('ignoring null merge rebase of %s\n' % rev)
477 483 elif rbsrt.state[rev] == revignored:
478 484 ui.status(_('not rebasing ignored %s\n') % desc)
479 485 elif rbsrt.state[rev] == revprecursor:
480 486 targetctx = repo[obsoletenotrebased[rev]]
481 487 desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx,
482 488 targetctx.description().split('\n', 1)[0])
483 489 msg = _('note: not rebasing %s, already in destination as %s\n')
484 490 ui.status(msg % (desc, desctarget))
485 491 elif rbsrt.state[rev] == revpruned:
486 492 msg = _('note: not rebasing %s, it has no successor\n')
487 493 ui.status(msg % desc)
488 494 else:
489 495 ui.status(_('already rebased %s as %s\n') %
490 496 (desc, repo[rbsrt.state[rev]]))
491 497
492 498 ui.progress(_('rebasing'), None)
493 499 ui.note(_('rebase merging completed\n'))
494 500
495 501 if collapsef and not keepopen:
496 502 p1, p2, _base = defineparents(repo, min(rbsrt.state),
497 503 rbsrt.target, rbsrt.state,
498 504 rbsrt.targetancestors,
499 505 obsoletenotrebased)
500 506 editopt = opts.get('edit')
501 507 editform = 'rebase.collapse'
502 508 if collapsemsg:
503 509 commitmsg = collapsemsg
504 510 else:
505 511 commitmsg = 'Collapsed revision'
506 512 for rebased in rbsrt.state:
507 513 if rebased not in rbsrt.skipped and\
508 514 rbsrt.state[rebased] > nullmerge:
509 515 commitmsg += '\n* %s' % repo[rebased].description()
510 516 editopt = True
511 517 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
512 518 newnode = concludenode(repo, rev, p1, rbsrt.external,
513 519 commitmsg=commitmsg,
514 520 extrafn=extrafn, editor=editor,
515 521 keepbranches=keepbranchesf,
516 522 date=date)
517 523 if newnode is None:
518 524 newrev = rbsrt.target
519 525 else:
520 526 newrev = repo[newnode].rev()
521 527 for oldrev in rbsrt.state.iterkeys():
522 528 if rbsrt.state[oldrev] > nullmerge:
523 529 rbsrt.state[oldrev] = newrev
524 530
525 531 if 'qtip' in repo.tags():
526 532 updatemq(repo, rbsrt.state, rbsrt.skipped, **opts)
527 533
528 534 if currentbookmarks:
529 535 # Nodeids are needed to reset bookmarks
530 536 nstate = {}
531 537 for k, v in rbsrt.state.iteritems():
532 538 if v > nullmerge:
533 539 nstate[repo[k].node()] = repo[v].node()
534 540 elif v == revprecursor:
535 541 succ = obsoletenotrebased[k]
536 542 nstate[repo[k].node()] = repo[succ].node()
537 543 # XXX this is the same as dest.node() for the non-continue path --
538 544 # this should probably be cleaned up
539 545 targetnode = repo[rbsrt.target].node()
540 546
541 547 # restore original working directory
542 548 # (we do this before stripping)
543 549 newwd = rbsrt.state.get(rbsrt.originalwd, rbsrt.originalwd)
544 550 if newwd == revprecursor:
545 551 newwd = obsoletenotrebased[rbsrt.originalwd]
546 552 elif newwd < 0:
547 553 # original directory is a parent of rebase set root or ignored
548 554 newwd = rbsrt.originalwd
549 555 if newwd not in [c.rev() for c in repo[None].parents()]:
550 556 ui.note(_("update back to initial working directory parent\n"))
551 557 hg.updaterepo(repo, newwd, False)
552 558
553 559 if not keepf:
554 560 collapsedas = None
555 561 if collapsef:
556 562 collapsedas = newnode
557 563 clearrebased(ui, repo, rbsrt.state, rbsrt.skipped, collapsedas)
558 564
559 565 with repo.transaction('bookmark') as tr:
560 566 if currentbookmarks:
561 567 updatebookmarks(repo, targetnode, nstate, currentbookmarks, tr)
562 568 if rbsrt.activebookmark not in repo._bookmarks:
563 569 # active bookmark was divergent one and has been deleted
564 570 rbsrt.activebookmark = None
565 571 clearstatus(repo)
566 572 clearcollapsemsg(repo)
567 573
568 574 ui.note(_("rebase completed\n"))
569 575 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
570 576 if rbsrt.skipped:
571 577 skippedlen = len(rbsrt.skipped)
572 578 ui.note(_("%d revisions have been skipped\n") % skippedlen)
573 579
574 580 if (rbsrt.activebookmark and
575 581 repo['.'].node() == repo._bookmarks[rbsrt.activebookmark]):
576 582 bookmarks.activate(repo, rbsrt.activebookmark)
577 583
578 584 finally:
579 585 release(lock, wlock)
580 586
581 587 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=[],
582 588 destspace=None):
583 589 """use revisions argument to define destination and rebase set
584 590 """
585 591 # destspace is here to work around issues with `hg pull --rebase` see
586 592 # issue5214 for details
587 593 if srcf and basef:
588 594 raise error.Abort(_('cannot specify both a source and a base'))
589 595 if revf and basef:
590 596 raise error.Abort(_('cannot specify both a revision and a base'))
591 597 if revf and srcf:
592 598 raise error.Abort(_('cannot specify both a revision and a source'))
593 599
594 600 cmdutil.checkunfinished(repo)
595 601 cmdutil.bailifchanged(repo)
596 602
597 603 if destf:
598 604 dest = scmutil.revsingle(repo, destf)
599 605
600 606 if revf:
601 607 rebaseset = scmutil.revrange(repo, revf)
602 608 if not rebaseset:
603 609 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
604 610 return None, None
605 611 elif srcf:
606 612 src = scmutil.revrange(repo, [srcf])
607 613 if not src:
608 614 ui.status(_('empty "source" revision set - nothing to rebase\n'))
609 615 return None, None
610 616 rebaseset = repo.revs('(%ld)::', src)
611 617 assert rebaseset
612 618 else:
613 619 base = scmutil.revrange(repo, [basef or '.'])
614 620 if not base:
615 621 ui.status(_('empty "base" revision set - '
616 622 "can't compute rebase set\n"))
617 623 return None, None
618 624 if not destf:
619 625 dest = repo[_destrebase(repo, base, destspace=destspace)]
620 626 destf = str(dest)
621 627
622 628 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
623 629 if commonanc is not None:
624 630 rebaseset = repo.revs('(%d::(%ld) - %d)::',
625 631 commonanc, base, commonanc)
626 632 else:
627 633 rebaseset = []
628 634
629 635 if not rebaseset:
630 636 # transform to list because smartsets are not comparable to
631 637 # lists. This should be improved to honor laziness of
632 638 # smartset.
633 639 if list(base) == [dest.rev()]:
634 640 if basef:
635 641 ui.status(_('nothing to rebase - %s is both "base"'
636 642 ' and destination\n') % dest)
637 643 else:
638 644 ui.status(_('nothing to rebase - working directory '
639 645 'parent is also destination\n'))
640 646 elif not repo.revs('%ld - ::%d', base, dest):
641 647 if basef:
642 648 ui.status(_('nothing to rebase - "base" %s is '
643 649 'already an ancestor of destination '
644 650 '%s\n') %
645 651 ('+'.join(str(repo[r]) for r in base),
646 652 dest))
647 653 else:
648 654 ui.status(_('nothing to rebase - working '
649 655 'directory parent is already an '
650 656 'ancestor of destination %s\n') % dest)
651 657 else: # can it happen?
652 658 ui.status(_('nothing to rebase from %s to %s\n') %
653 659 ('+'.join(str(repo[r]) for r in base), dest))
654 660 return None, None
655 661
656 662 if not destf:
657 663 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
658 664 destf = str(dest)
659 665
660 666 return dest, rebaseset
661 667
662 668 def externalparent(repo, state, targetancestors):
663 669 """Return the revision that should be used as the second parent
664 670 when the revisions in state is collapsed on top of targetancestors.
665 671 Abort if there is more than one parent.
666 672 """
667 673 parents = set()
668 674 source = min(state)
669 675 for rev in state:
670 676 if rev == source:
671 677 continue
672 678 for p in repo[rev].parents():
673 679 if (p.rev() not in state
674 680 and p.rev() not in targetancestors):
675 681 parents.add(p.rev())
676 682 if not parents:
677 683 return nullrev
678 684 if len(parents) == 1:
679 685 return parents.pop()
680 686 raise error.Abort(_('unable to collapse on top of %s, there is more '
681 687 'than one external parent: %s') %
682 688 (max(targetancestors),
683 689 ', '.join(str(p) for p in sorted(parents))))
684 690
685 691 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
686 692 keepbranches=False, date=None):
687 693 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
688 694 but also store useful information in extra.
689 695 Return node of committed revision.'''
690 696 dsguard = cmdutil.dirstateguard(repo, 'rebase')
691 697 try:
692 698 repo.setparents(repo[p1].node(), repo[p2].node())
693 699 ctx = repo[rev]
694 700 if commitmsg is None:
695 701 commitmsg = ctx.description()
696 702 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
697 703 extra = {'rebase_source': ctx.hex()}
698 704 if extrafn:
699 705 extrafn(ctx, extra)
700 706
701 707 backup = repo.ui.backupconfig('phases', 'new-commit')
702 708 try:
703 709 targetphase = max(ctx.phase(), phases.draft)
704 710 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
705 711 if keepbranch:
706 712 repo.ui.setconfig('ui', 'allowemptycommit', True)
707 713 # Commit might fail if unresolved files exist
708 714 if date is None:
709 715 date = ctx.date()
710 716 newnode = repo.commit(text=commitmsg, user=ctx.user(),
711 717 date=date, extra=extra, editor=editor)
712 718 finally:
713 719 repo.ui.restoreconfig(backup)
714 720
715 721 repo.dirstate.setbranch(repo[newnode].branch())
716 722 dsguard.close()
717 723 return newnode
718 724 finally:
719 725 release(dsguard)
720 726
721 727 def rebasenode(repo, rev, p1, base, state, collapse, target):
722 728 'Rebase a single revision rev on top of p1 using base as merge ancestor'
723 729 # Merge phase
724 730 # Update to target and merge it with local
725 731 if repo['.'].rev() != p1:
726 732 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
727 733 merge.update(repo, p1, False, True)
728 734 else:
729 735 repo.ui.debug(" already in target\n")
730 736 repo.dirstate.write(repo.currenttransaction())
731 737 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
732 738 if base is not None:
733 739 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
734 740 # When collapsing in-place, the parent is the common ancestor, we
735 741 # have to allow merging with it.
736 742 stats = merge.update(repo, rev, True, True, base, collapse,
737 743 labels=['dest', 'source'])
738 744 if collapse:
739 745 copies.duplicatecopies(repo, rev, target)
740 746 else:
741 747 # If we're not using --collapse, we need to
742 748 # duplicate copies between the revision we're
743 749 # rebasing and its first parent, but *not*
744 750 # duplicate any copies that have already been
745 751 # performed in the destination.
746 752 p1rev = repo[rev].p1().rev()
747 753 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
748 754 return stats
749 755
750 756 def nearestrebased(repo, rev, state):
751 757 """return the nearest ancestors of rev in the rebase result"""
752 758 rebased = [r for r in state if state[r] > nullmerge]
753 759 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
754 760 if candidates:
755 761 return state[candidates.first()]
756 762 else:
757 763 return None
758 764
759 765 def _checkobsrebase(repo, ui,
760 766 rebaseobsrevs,
761 767 rebasesetrevs,
762 768 rebaseobsskipped):
763 769 """
764 770 Abort if rebase will create divergence or rebase is noop because of markers
765 771
766 772 `rebaseobsrevs`: set of obsolete revision in source
767 773 `rebasesetrevs`: set of revisions to be rebased from source
768 774 `rebaseobsskipped`: set of revisions from source skipped because they have
769 775 successors in destination
770 776 """
771 777 # Obsolete node with successors not in dest leads to divergence
772 778 divergenceok = ui.configbool('experimental',
773 779 'allowdivergence')
774 780 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
775 781
776 782 if divergencebasecandidates and not divergenceok:
777 783 divhashes = (str(repo[r])
778 784 for r in divergencebasecandidates)
779 785 msg = _("this rebase will cause "
780 786 "divergences from: %s")
781 787 h = _("to force the rebase please set "
782 788 "experimental.allowdivergence=True")
783 789 raise error.Abort(msg % (",".join(divhashes),), hint=h)
784 790
785 791 def defineparents(repo, rev, target, state, targetancestors,
786 792 obsoletenotrebased):
787 793 'Return the new parent relationship of the revision that will be rebased'
788 794 parents = repo[rev].parents()
789 795 p1 = p2 = nullrev
790 796 rp1 = None
791 797
792 798 p1n = parents[0].rev()
793 799 if p1n in targetancestors:
794 800 p1 = target
795 801 elif p1n in state:
796 802 if state[p1n] == nullmerge:
797 803 p1 = target
798 804 elif state[p1n] in revskipped:
799 805 p1 = nearestrebased(repo, p1n, state)
800 806 if p1 is None:
801 807 p1 = target
802 808 else:
803 809 p1 = state[p1n]
804 810 else: # p1n external
805 811 p1 = target
806 812 p2 = p1n
807 813
808 814 if len(parents) == 2 and parents[1].rev() not in targetancestors:
809 815 p2n = parents[1].rev()
810 816 # interesting second parent
811 817 if p2n in state:
812 818 if p1 == target: # p1n in targetancestors or external
813 819 p1 = state[p2n]
814 820 if p1 == revprecursor:
815 821 rp1 = obsoletenotrebased[p2n]
816 822 elif state[p2n] in revskipped:
817 823 p2 = nearestrebased(repo, p2n, state)
818 824 if p2 is None:
819 825 # no ancestors rebased yet, detach
820 826 p2 = target
821 827 else:
822 828 p2 = state[p2n]
823 829 else: # p2n external
824 830 if p2 != nullrev: # p1n external too => rev is a merged revision
825 831 raise error.Abort(_('cannot use revision %d as base, result '
826 832 'would have 3 parents') % rev)
827 833 p2 = p2n
828 834 repo.ui.debug(" future parents are %d and %d\n" %
829 835 (repo[rp1 or p1].rev(), repo[p2].rev()))
830 836
831 837 if not any(p.rev() in state for p in parents):
832 838 # Case (1) root changeset of a non-detaching rebase set.
833 839 # Let the merge mechanism find the base itself.
834 840 base = None
835 841 elif not repo[rev].p2():
836 842 # Case (2) detaching the node with a single parent, use this parent
837 843 base = repo[rev].p1().rev()
838 844 else:
839 845 # Assuming there is a p1, this is the case where there also is a p2.
840 846 # We are thus rebasing a merge and need to pick the right merge base.
841 847 #
842 848 # Imagine we have:
843 849 # - M: current rebase revision in this step
844 850 # - A: one parent of M
845 851 # - B: other parent of M
846 852 # - D: destination of this merge step (p1 var)
847 853 #
848 854 # Consider the case where D is a descendant of A or B and the other is
849 855 # 'outside'. In this case, the right merge base is the D ancestor.
850 856 #
851 857 # An informal proof, assuming A is 'outside' and B is the D ancestor:
852 858 #
853 859 # If we pick B as the base, the merge involves:
854 860 # - changes from B to M (actual changeset payload)
855 861 # - changes from B to D (induced by rebase) as D is a rebased
856 862 # version of B)
857 863 # Which exactly represent the rebase operation.
858 864 #
859 865 # If we pick A as the base, the merge involves:
860 866 # - changes from A to M (actual changeset payload)
861 867 # - changes from A to D (with include changes between unrelated A and B
862 868 # plus changes induced by rebase)
863 869 # Which does not represent anything sensible and creates a lot of
864 870 # conflicts. A is thus not the right choice - B is.
865 871 #
866 872 # Note: The base found in this 'proof' is only correct in the specified
867 873 # case. This base does not make sense if is not D a descendant of A or B
868 874 # or if the other is not parent 'outside' (especially not if the other
869 875 # parent has been rebased). The current implementation does not
870 876 # make it feasible to consider different cases separately. In these
871 877 # other cases we currently just leave it to the user to correctly
872 878 # resolve an impossible merge using a wrong ancestor.
873 879 #
874 880 # xx, p1 could be -4, and both parents could probably be -4...
875 881 for p in repo[rev].parents():
876 882 if state.get(p.rev()) == p1:
877 883 base = p.rev()
878 884 break
879 885 else: # fallback when base not found
880 886 base = None
881 887
882 888 # Raise because this function is called wrong (see issue 4106)
883 889 raise AssertionError('no base found to rebase on '
884 890 '(defineparents called wrong)')
885 891 return rp1 or p1, p2, base
886 892
887 893 def isagitpatch(repo, patchname):
888 894 'Return true if the given patch is in git format'
889 895 mqpatch = os.path.join(repo.mq.path, patchname)
890 896 for line in patch.linereader(file(mqpatch, 'rb')):
891 897 if line.startswith('diff --git'):
892 898 return True
893 899 return False
894 900
895 901 def updatemq(repo, state, skipped, **opts):
896 902 'Update rebased mq patches - finalize and then import them'
897 903 mqrebase = {}
898 904 mq = repo.mq
899 905 original_series = mq.fullseries[:]
900 906 skippedpatches = set()
901 907
902 908 for p in mq.applied:
903 909 rev = repo[p.node].rev()
904 910 if rev in state:
905 911 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
906 912 (rev, p.name))
907 913 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
908 914 else:
909 915 # Applied but not rebased, not sure this should happen
910 916 skippedpatches.add(p.name)
911 917
912 918 if mqrebase:
913 919 mq.finish(repo, mqrebase.keys())
914 920
915 921 # We must start import from the newest revision
916 922 for rev in sorted(mqrebase, reverse=True):
917 923 if rev not in skipped:
918 924 name, isgit = mqrebase[rev]
919 925 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
920 926 (name, state[rev], repo[state[rev]]))
921 927 mq.qimport(repo, (), patchname=name, git=isgit,
922 928 rev=[str(state[rev])])
923 929 else:
924 930 # Rebased and skipped
925 931 skippedpatches.add(mqrebase[rev][0])
926 932
927 933 # Patches were either applied and rebased and imported in
928 934 # order, applied and removed or unapplied. Discard the removed
929 935 # ones while preserving the original series order and guards.
930 936 newseries = [s for s in original_series
931 937 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
932 938 mq.fullseries[:] = newseries
933 939 mq.seriesdirty = True
934 940 mq.savedirty()
935 941
936 942 def updatebookmarks(repo, targetnode, nstate, originalbookmarks, tr):
937 943 'Move bookmarks to their correct changesets, and delete divergent ones'
938 944 marks = repo._bookmarks
939 945 for k, v in originalbookmarks.iteritems():
940 946 if v in nstate:
941 947 # update the bookmarks for revs that have moved
942 948 marks[k] = nstate[v]
943 949 bookmarks.deletedivergent(repo, [targetnode], k)
944 950 marks.recordchange(tr)
945 951
946 952 def storecollapsemsg(repo, collapsemsg):
947 953 'Store the collapse message to allow recovery'
948 954 collapsemsg = collapsemsg or ''
949 955 f = repo.vfs("last-message.txt", "w")
950 956 f.write("%s\n" % collapsemsg)
951 957 f.close()
952 958
953 959 def clearcollapsemsg(repo):
954 960 'Remove collapse message file'
955 961 util.unlinkpath(repo.join("last-message.txt"), ignoremissing=True)
956 962
957 963 def restorecollapsemsg(repo):
958 964 'Restore previously stored collapse message'
959 965 try:
960 966 f = repo.vfs("last-message.txt")
961 967 collapsemsg = f.readline().strip()
962 968 f.close()
963 969 except IOError as err:
964 970 if err.errno != errno.ENOENT:
965 971 raise
966 972 raise error.Abort(_('no rebase in progress'))
967 973 return collapsemsg
968 974
969 975 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
970 976 external, activebookmark):
971 977 'Store the current status to allow recovery'
972 978 f = repo.vfs("rebasestate", "w")
973 979 f.write(repo[originalwd].hex() + '\n')
974 980 f.write(repo[target].hex() + '\n')
975 981 f.write(repo[external].hex() + '\n')
976 982 f.write('%d\n' % int(collapse))
977 983 f.write('%d\n' % int(keep))
978 984 f.write('%d\n' % int(keepbranches))
979 985 f.write('%s\n' % (activebookmark or ''))
980 986 for d, v in state.iteritems():
981 987 oldrev = repo[d].hex()
982 988 if v >= 0:
983 989 newrev = repo[v].hex()
984 990 elif v == revtodo:
985 991 # To maintain format compatibility, we have to use nullid.
986 992 # Please do remove this special case when upgrading the format.
987 993 newrev = hex(nullid)
988 994 else:
989 995 newrev = v
990 996 f.write("%s:%s\n" % (oldrev, newrev))
991 997 f.close()
992 998 repo.ui.debug('rebase status stored\n')
993 999
994 1000 def clearstatus(repo):
995 1001 'Remove the status files'
996 1002 _clearrebasesetvisibiliy(repo)
997 1003 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
998 1004
999 1005 def restorestatus(repo):
1000 1006 'Restore a previously stored status'
1001 1007 keepbranches = None
1002 1008 target = None
1003 1009 collapse = False
1004 1010 external = nullrev
1005 1011 activebookmark = None
1006 1012 state = {}
1007 1013
1008 1014 try:
1009 1015 f = repo.vfs("rebasestate")
1010 1016 for i, l in enumerate(f.read().splitlines()):
1011 1017 if i == 0:
1012 1018 originalwd = repo[l].rev()
1013 1019 elif i == 1:
1014 1020 target = repo[l].rev()
1015 1021 elif i == 2:
1016 1022 external = repo[l].rev()
1017 1023 elif i == 3:
1018 1024 collapse = bool(int(l))
1019 1025 elif i == 4:
1020 1026 keep = bool(int(l))
1021 1027 elif i == 5:
1022 1028 keepbranches = bool(int(l))
1023 1029 elif i == 6 and not (len(l) == 81 and ':' in l):
1024 1030 # line 6 is a recent addition, so for backwards compatibility
1025 1031 # check that the line doesn't look like the oldrev:newrev lines
1026 1032 activebookmark = l
1027 1033 else:
1028 1034 oldrev, newrev = l.split(':')
1029 1035 if newrev in (str(nullmerge), str(revignored),
1030 1036 str(revprecursor), str(revpruned)):
1031 1037 state[repo[oldrev].rev()] = int(newrev)
1032 1038 elif newrev == nullid:
1033 1039 state[repo[oldrev].rev()] = revtodo
1034 1040 # Legacy compat special case
1035 1041 else:
1036 1042 state[repo[oldrev].rev()] = repo[newrev].rev()
1037 1043
1038 1044 except IOError as err:
1039 1045 if err.errno != errno.ENOENT:
1040 1046 raise
1041 1047 cmdutil.wrongtooltocontinue(repo, _('rebase'))
1042 1048
1043 1049 if keepbranches is None:
1044 1050 raise error.Abort(_('.hg/rebasestate is incomplete'))
1045 1051
1046 1052 skipped = set()
1047 1053 # recompute the set of skipped revs
1048 1054 if not collapse:
1049 1055 seen = set([target])
1050 1056 for old, new in sorted(state.items()):
1051 1057 if new != revtodo and new in seen:
1052 1058 skipped.add(old)
1053 1059 seen.add(new)
1054 1060 repo.ui.debug('computed skipped revs: %s\n' %
1055 1061 (' '.join(str(r) for r in sorted(skipped)) or None))
1056 1062 repo.ui.debug('rebase status resumed\n')
1057 1063 _setrebasesetvisibility(repo, state.keys())
1058 1064 return (originalwd, target, state, skipped,
1059 1065 collapse, keep, keepbranches, external, activebookmark)
1060 1066
1061 1067 def needupdate(repo, state):
1062 1068 '''check whether we should `update --clean` away from a merge, or if
1063 1069 somehow the working dir got forcibly updated, e.g. by older hg'''
1064 1070 parents = [p.rev() for p in repo[None].parents()]
1065 1071
1066 1072 # Are we in a merge state at all?
1067 1073 if len(parents) < 2:
1068 1074 return False
1069 1075
1070 1076 # We should be standing on the first as-of-yet unrebased commit.
1071 1077 firstunrebased = min([old for old, new in state.iteritems()
1072 1078 if new == nullrev])
1073 1079 if firstunrebased in parents:
1074 1080 return True
1075 1081
1076 1082 return False
1077 1083
1078 1084 def abort(repo, originalwd, target, state, activebookmark=None):
1079 1085 '''Restore the repository to its original state. Additional args:
1080 1086
1081 1087 activebookmark: the name of the bookmark that should be active after the
1082 1088 restore'''
1083 1089
1084 1090 try:
1085 1091 # If the first commits in the rebased set get skipped during the rebase,
1086 1092 # their values within the state mapping will be the target rev id. The
1087 1093 # dstates list must must not contain the target rev (issue4896)
1088 1094 dstates = [s for s in state.values() if s >= 0 and s != target]
1089 1095 immutable = [d for d in dstates if not repo[d].mutable()]
1090 1096 cleanup = True
1091 1097 if immutable:
1092 1098 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1093 1099 % ', '.join(str(repo[r]) for r in immutable),
1094 1100 hint=_('see "hg help phases" for details'))
1095 1101 cleanup = False
1096 1102
1097 1103 descendants = set()
1098 1104 if dstates:
1099 1105 descendants = set(repo.changelog.descendants(dstates))
1100 1106 if descendants - set(dstates):
1101 1107 repo.ui.warn(_("warning: new changesets detected on target branch, "
1102 1108 "can't strip\n"))
1103 1109 cleanup = False
1104 1110
1105 1111 if cleanup:
1106 1112 shouldupdate = False
1107 1113 rebased = filter(lambda x: x >= 0 and x != target, state.values())
1108 1114 if rebased:
1109 1115 strippoints = [
1110 1116 c.node() for c in repo.set('roots(%ld)', rebased)]
1111 1117 shouldupdate = len([
1112 1118 c.node() for c in repo.set('. & (%ld)', rebased)]) > 0
1113 1119
1114 1120 # Update away from the rebase if necessary
1115 1121 if shouldupdate or needupdate(repo, state):
1116 1122 merge.update(repo, originalwd, False, True)
1117 1123
1118 1124 # Strip from the first rebased revision
1119 1125 if rebased:
1120 1126 # no backup of rebased cset versions needed
1121 1127 repair.strip(repo.ui, repo, strippoints)
1122 1128
1123 1129 if activebookmark and activebookmark in repo._bookmarks:
1124 1130 bookmarks.activate(repo, activebookmark)
1125 1131
1126 1132 finally:
1127 1133 clearstatus(repo)
1128 1134 clearcollapsemsg(repo)
1129 1135 repo.ui.warn(_('rebase aborted\n'))
1130 1136 return 0
1131 1137
1132 1138 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1133 1139 '''Define which revisions are going to be rebased and where
1134 1140
1135 1141 repo: repo
1136 1142 dest: context
1137 1143 rebaseset: set of rev
1138 1144 '''
1139 1145 _setrebasesetvisibility(repo, rebaseset)
1140 1146
1141 1147 # This check isn't strictly necessary, since mq detects commits over an
1142 1148 # applied patch. But it prevents messing up the working directory when
1143 1149 # a partially completed rebase is blocked by mq.
1144 1150 if 'qtip' in repo.tags() and (dest.node() in
1145 1151 [s.node for s in repo.mq.applied]):
1146 1152 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1147 1153
1148 1154 roots = list(repo.set('roots(%ld)', rebaseset))
1149 1155 if not roots:
1150 1156 raise error.Abort(_('no matching revisions'))
1151 1157 roots.sort()
1152 1158 state = {}
1153 1159 detachset = set()
1154 1160 for root in roots:
1155 1161 commonbase = root.ancestor(dest)
1156 1162 if commonbase == root:
1157 1163 raise error.Abort(_('source is ancestor of destination'))
1158 1164 if commonbase == dest:
1159 1165 samebranch = root.branch() == dest.branch()
1160 1166 if not collapse and samebranch and root in dest.children():
1161 1167 repo.ui.debug('source is a child of destination\n')
1162 1168 return None
1163 1169
1164 1170 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
1165 1171 state.update(dict.fromkeys(rebaseset, revtodo))
1166 1172 # Rebase tries to turn <dest> into a parent of <root> while
1167 1173 # preserving the number of parents of rebased changesets:
1168 1174 #
1169 1175 # - A changeset with a single parent will always be rebased as a
1170 1176 # changeset with a single parent.
1171 1177 #
1172 1178 # - A merge will be rebased as merge unless its parents are both
1173 1179 # ancestors of <dest> or are themselves in the rebased set and
1174 1180 # pruned while rebased.
1175 1181 #
1176 1182 # If one parent of <root> is an ancestor of <dest>, the rebased
1177 1183 # version of this parent will be <dest>. This is always true with
1178 1184 # --base option.
1179 1185 #
1180 1186 # Otherwise, we need to *replace* the original parents with
1181 1187 # <dest>. This "detaches" the rebased set from its former location
1182 1188 # and rebases it onto <dest>. Changes introduced by ancestors of
1183 1189 # <root> not common with <dest> (the detachset, marked as
1184 1190 # nullmerge) are "removed" from the rebased changesets.
1185 1191 #
1186 1192 # - If <root> has a single parent, set it to <dest>.
1187 1193 #
1188 1194 # - If <root> is a merge, we cannot decide which parent to
1189 1195 # replace, the rebase operation is not clearly defined.
1190 1196 #
1191 1197 # The table below sums up this behavior:
1192 1198 #
1193 1199 # +------------------+----------------------+-------------------------+
1194 1200 # | | one parent | merge |
1195 1201 # +------------------+----------------------+-------------------------+
1196 1202 # | parent in | new parent is <dest> | parents in ::<dest> are |
1197 1203 # | ::<dest> | | remapped to <dest> |
1198 1204 # +------------------+----------------------+-------------------------+
1199 1205 # | unrelated source | new parent is <dest> | ambiguous, abort |
1200 1206 # +------------------+----------------------+-------------------------+
1201 1207 #
1202 1208 # The actual abort is handled by `defineparents`
1203 1209 if len(root.parents()) <= 1:
1204 1210 # ancestors of <root> not ancestors of <dest>
1205 1211 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1206 1212 [root.rev()]))
1207 1213 for r in detachset:
1208 1214 if r not in state:
1209 1215 state[r] = nullmerge
1210 1216 if len(roots) > 1:
1211 1217 # If we have multiple roots, we may have "hole" in the rebase set.
1212 1218 # Rebase roots that descend from those "hole" should not be detached as
1213 1219 # other root are. We use the special `revignored` to inform rebase that
1214 1220 # the revision should be ignored but that `defineparents` should search
1215 1221 # a rebase destination that make sense regarding rebased topology.
1216 1222 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1217 1223 for ignored in set(rebasedomain) - set(rebaseset):
1218 1224 state[ignored] = revignored
1219 1225 for r in obsoletenotrebased:
1220 1226 if obsoletenotrebased[r] is None:
1221 1227 state[r] = revpruned
1222 1228 else:
1223 1229 state[r] = revprecursor
1224 1230 return repo['.'].rev(), dest.rev(), state
1225 1231
1226 1232 def clearrebased(ui, repo, state, skipped, collapsedas=None):
1227 1233 """dispose of rebased revision at the end of the rebase
1228 1234
1229 1235 If `collapsedas` is not None, the rebase was a collapse whose result if the
1230 1236 `collapsedas` node."""
1231 1237 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1232 1238 markers = []
1233 1239 for rev, newrev in sorted(state.items()):
1234 1240 if newrev >= 0:
1235 1241 if rev in skipped:
1236 1242 succs = ()
1237 1243 elif collapsedas is not None:
1238 1244 succs = (repo[collapsedas],)
1239 1245 else:
1240 1246 succs = (repo[newrev],)
1241 1247 markers.append((repo[rev], succs))
1242 1248 if markers:
1243 1249 obsolete.createmarkers(repo, markers)
1244 1250 else:
1245 1251 rebased = [rev for rev in state if state[rev] > nullmerge]
1246 1252 if rebased:
1247 1253 stripped = []
1248 1254 for root in repo.set('roots(%ld)', rebased):
1249 1255 if set(repo.changelog.descendants([root.rev()])) - set(state):
1250 1256 ui.warn(_("warning: new changesets detected "
1251 1257 "on source branch, not stripping\n"))
1252 1258 else:
1253 1259 stripped.append(root.node())
1254 1260 if stripped:
1255 1261 # backup the old csets by default
1256 1262 repair.strip(ui, repo, stripped, "all")
1257 1263
1258 1264
1259 1265 def pullrebase(orig, ui, repo, *args, **opts):
1260 1266 'Call rebase after pull if the latter has been invoked with --rebase'
1261 1267 ret = None
1262 1268 if opts.get('rebase'):
1263 1269 wlock = lock = None
1264 1270 try:
1265 1271 wlock = repo.wlock()
1266 1272 lock = repo.lock()
1267 1273 if opts.get('update'):
1268 1274 del opts['update']
1269 1275 ui.debug('--update and --rebase are not compatible, ignoring '
1270 1276 'the update flag\n')
1271 1277
1272 1278 revsprepull = len(repo)
1273 1279 origpostincoming = commands.postincoming
1274 1280 def _dummy(*args, **kwargs):
1275 1281 pass
1276 1282 commands.postincoming = _dummy
1277 1283 try:
1278 1284 ret = orig(ui, repo, *args, **opts)
1279 1285 finally:
1280 1286 commands.postincoming = origpostincoming
1281 1287 revspostpull = len(repo)
1282 1288 if revspostpull > revsprepull:
1283 1289 # --rev option from pull conflict with rebase own --rev
1284 1290 # dropping it
1285 1291 if 'rev' in opts:
1286 1292 del opts['rev']
1287 1293 # positional argument from pull conflicts with rebase's own
1288 1294 # --source.
1289 1295 if 'source' in opts:
1290 1296 del opts['source']
1291 1297 # revsprepull is the len of the repo, not revnum of tip.
1292 1298 destspace = list(repo.changelog.revs(start=revsprepull))
1293 1299 opts['_destspace'] = destspace
1294 1300 try:
1295 1301 rebase(ui, repo, **opts)
1296 1302 except error.NoMergeDestAbort:
1297 1303 # we can maybe update instead
1298 1304 rev, _a, _b = destutil.destupdate(repo)
1299 1305 if rev == repo['.'].rev():
1300 1306 ui.status(_('nothing to rebase\n'))
1301 1307 else:
1302 1308 ui.status(_('nothing to rebase - updating instead\n'))
1303 1309 # not passing argument to get the bare update behavior
1304 1310 # with warning and trumpets
1305 1311 commands.update(ui, repo)
1306 1312 finally:
1307 1313 release(lock, wlock)
1308 1314 else:
1309 1315 if opts.get('tool'):
1310 1316 raise error.Abort(_('--tool can only be used with --rebase'))
1311 1317 ret = orig(ui, repo, *args, **opts)
1312 1318
1313 1319 return ret
1314 1320
1315 1321 def _setrebasesetvisibility(repo, revs):
1316 1322 """store the currently rebased set on the repo object
1317 1323
1318 1324 This is used by another function to prevent rebased revision to because
1319 1325 hidden (see issue4505)"""
1320 1326 repo = repo.unfiltered()
1321 1327 revs = set(revs)
1322 1328 repo._rebaseset = revs
1323 1329 # invalidate cache if visibility changes
1324 1330 hiddens = repo.filteredrevcache.get('visible', set())
1325 1331 if revs & hiddens:
1326 1332 repo.invalidatevolatilesets()
1327 1333
1328 1334 def _clearrebasesetvisibiliy(repo):
1329 1335 """remove rebaseset data from the repo"""
1330 1336 repo = repo.unfiltered()
1331 1337 if '_rebaseset' in vars(repo):
1332 1338 del repo._rebaseset
1333 1339
1334 1340 def _rebasedvisible(orig, repo):
1335 1341 """ensure rebased revs stay visible (see issue4505)"""
1336 1342 blockers = orig(repo)
1337 1343 blockers.update(getattr(repo, '_rebaseset', ()))
1338 1344 return blockers
1339 1345
1340 1346 def _filterobsoleterevs(repo, revs):
1341 1347 """returns a set of the obsolete revisions in revs"""
1342 1348 return set(r for r in revs if repo[r].obsolete())
1343 1349
1344 1350 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1345 1351 """return a mapping obsolete => successor for all obsolete nodes to be
1346 1352 rebased that have a successors in the destination
1347 1353
1348 1354 obsolete => None entries in the mapping indicate nodes with no succesor"""
1349 1355 obsoletenotrebased = {}
1350 1356
1351 1357 # Build a mapping successor => obsolete nodes for the obsolete
1352 1358 # nodes to be rebased
1353 1359 allsuccessors = {}
1354 1360 cl = repo.changelog
1355 1361 for r in rebaseobsrevs:
1356 1362 node = cl.node(r)
1357 1363 for s in obsolete.allsuccessors(repo.obsstore, [node]):
1358 1364 try:
1359 1365 allsuccessors[cl.rev(s)] = cl.rev(node)
1360 1366 except LookupError:
1361 1367 pass
1362 1368
1363 1369 if allsuccessors:
1364 1370 # Look for successors of obsolete nodes to be rebased among
1365 1371 # the ancestors of dest
1366 1372 ancs = cl.ancestors([repo[dest].rev()],
1367 1373 stoprev=min(allsuccessors),
1368 1374 inclusive=True)
1369 1375 for s in allsuccessors:
1370 1376 if s in ancs:
1371 1377 obsoletenotrebased[allsuccessors[s]] = s
1372 1378 elif (s == allsuccessors[s] and
1373 1379 allsuccessors.values().count(s) == 1):
1374 1380 # plain prune
1375 1381 obsoletenotrebased[s] = None
1376 1382
1377 1383 return obsoletenotrebased
1378 1384
1379 1385 def summaryhook(ui, repo):
1380 1386 if not os.path.exists(repo.join('rebasestate')):
1381 1387 return
1382 1388 try:
1383 1389 state = restorestatus(repo)[2]
1384 1390 except error.RepoLookupError:
1385 1391 # i18n: column positioning for "hg summary"
1386 1392 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1387 1393 ui.write(msg)
1388 1394 return
1389 1395 numrebased = len([i for i in state.itervalues() if i >= 0])
1390 1396 # i18n: column positioning for "hg summary"
1391 1397 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1392 1398 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1393 1399 ui.label(_('%d remaining'), 'rebase.remaining') %
1394 1400 (len(state) - numrebased)))
1395 1401
1396 1402 def uisetup(ui):
1397 1403 #Replace pull with a decorator to provide --rebase option
1398 1404 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1399 1405 entry[1].append(('', 'rebase', None,
1400 1406 _("rebase working directory to branch head")))
1401 1407 entry[1].append(('t', 'tool', '',
1402 1408 _("specify merge tool for rebase")))
1403 1409 cmdutil.summaryhooks.add('rebase', summaryhook)
1404 1410 cmdutil.unfinishedstates.append(
1405 1411 ['rebasestate', False, False, _('rebase in progress'),
1406 1412 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1407 1413 cmdutil.afterresolvedstates.append(
1408 1414 ['rebasestate', _('hg rebase --continue')])
1409 1415 # ensure rebased rev are not hidden
1410 1416 extensions.wrapfunction(repoview, '_getdynamicblockers', _rebasedvisible)
General Comments 0
You need to be logged in to leave comments. Login now