##// END OF EJS Templates
rebase: fix some weird mixed-case naming
Matt Mackall -
r22906:75d0edb6 default
parent child Browse files
Show More
@@ -1,1024 +1,1024 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 http://mercurial.selenic.com/wiki/RebaseExtension
15 15 '''
16 16
17 17 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
18 18 from mercurial import extensions, patch, scmutil, phases, obsolete, error
19 19 from mercurial import copies
20 20 from mercurial.commands import templateopts
21 21 from mercurial.node import nullrev
22 22 from mercurial.lock import release
23 23 from mercurial.i18n import _
24 24 import os, errno
25 25
26 26 nullmerge = -2
27 27 revignored = -3
28 28
29 29 cmdtable = {}
30 30 command = cmdutil.command(cmdtable)
31 31 testedwith = 'internal'
32 32
33 33 def _savegraft(ctx, extra):
34 34 s = ctx.extra().get('source', None)
35 35 if s is not None:
36 36 extra['source'] = s
37 37
38 38 def _savebranch(ctx, extra):
39 39 extra['branch'] = ctx.branch()
40 40
41 41 def _makeextrafn(copiers):
42 42 """make an extrafn out of the given copy-functions.
43 43
44 44 A copy function takes a context and an extra dict, and mutates the
45 45 extra dict as needed based on the given context.
46 46 """
47 47 def extrafn(ctx, extra):
48 48 for c in copiers:
49 49 c(ctx, extra)
50 50 return extrafn
51 51
52 52 @command('rebase',
53 53 [('s', 'source', '',
54 54 _('rebase the specified changeset and descendants'), _('REV')),
55 55 ('b', 'base', '',
56 56 _('rebase everything from branching point of specified changeset'),
57 57 _('REV')),
58 58 ('r', 'rev', [],
59 59 _('rebase these revisions'),
60 60 _('REV')),
61 61 ('d', 'dest', '',
62 62 _('rebase onto the specified changeset'), _('REV')),
63 63 ('', 'collapse', False, _('collapse the rebased changesets')),
64 64 ('m', 'message', '',
65 65 _('use text as collapse commit message'), _('TEXT')),
66 66 ('e', 'edit', False, _('invoke editor on commit messages')),
67 67 ('l', 'logfile', '',
68 68 _('read collapse commit message from file'), _('FILE')),
69 69 ('', 'keep', False, _('keep original changesets')),
70 70 ('', 'keepbranches', False, _('keep original branch names')),
71 71 ('D', 'detach', False, _('(DEPRECATED)')),
72 72 ('i', 'interactive', False, _('(DEPRECATED)')),
73 73 ('t', 'tool', '', _('specify merge tool')),
74 74 ('c', 'continue', False, _('continue an interrupted rebase')),
75 75 ('a', 'abort', False, _('abort an interrupted rebase'))] +
76 76 templateopts,
77 77 _('[-s REV | -b REV] [-d REV] [OPTION]'))
78 78 def rebase(ui, repo, **opts):
79 79 """move changeset (and descendants) to a different branch
80 80
81 81 Rebase uses repeated merging to graft changesets from one part of
82 82 history (the source) onto another (the destination). This can be
83 83 useful for linearizing *local* changes relative to a master
84 84 development tree.
85 85
86 86 You should not rebase changesets that have already been shared
87 87 with others. Doing so will force everybody else to perform the
88 88 same rebase or they will end up with duplicated changesets after
89 89 pulling in your rebased changesets.
90 90
91 91 In its default configuration, Mercurial will prevent you from
92 92 rebasing published changes. See :hg:`help phases` for details.
93 93
94 94 If you don't specify a destination changeset (``-d/--dest``),
95 95 rebase uses the current branch tip as the destination. (The
96 96 destination changeset is not modified by rebasing, but new
97 97 changesets are added as its descendants.)
98 98
99 99 You can specify which changesets to rebase in two ways: as a
100 100 "source" changeset or as a "base" changeset. Both are shorthand
101 101 for a topologically related set of changesets (the "source
102 102 branch"). If you specify source (``-s/--source``), rebase will
103 103 rebase that changeset and all of its descendants onto dest. If you
104 104 specify base (``-b/--base``), rebase will select ancestors of base
105 105 back to but not including the common ancestor with dest. Thus,
106 106 ``-b`` is less precise but more convenient than ``-s``: you can
107 107 specify any changeset in the source branch, and rebase will select
108 108 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
109 109 uses the parent of the working directory as the base.
110 110
111 111 For advanced usage, a third way is available through the ``--rev``
112 112 option. It allows you to specify an arbitrary set of changesets to
113 113 rebase. Descendants of revs you specify with this option are not
114 114 automatically included in the rebase.
115 115
116 116 By default, rebase recreates the changesets in the source branch
117 117 as descendants of dest and then destroys the originals. Use
118 118 ``--keep`` to preserve the original source changesets. Some
119 119 changesets in the source branch (e.g. merges from the destination
120 120 branch) may be dropped if they no longer contribute any change.
121 121
122 122 One result of the rules for selecting the destination changeset
123 123 and source branch is that, unlike ``merge``, rebase will do
124 124 nothing if you are at the branch tip of a named branch
125 125 with two heads. You need to explicitly specify source and/or
126 126 destination (or ``update`` to the other head, if it's the head of
127 127 the intended source branch).
128 128
129 129 If a rebase is interrupted to manually resolve a merge, it can be
130 130 continued with --continue/-c or aborted with --abort/-a.
131 131
132 132 .. container:: verbose
133 133
134 134 Examples:
135 135
136 136 - move "local changes" (current commit back to branching point)
137 137 to the current branch tip after a pull::
138 138
139 139 hg rebase
140 140
141 141 - move a single changeset to the stable branch::
142 142
143 143 hg rebase -r 5f493448 -d stable
144 144
145 145 - splice a commit and all its descendants onto another part of history::
146 146
147 147 hg rebase --source c0c3 --dest 4cf9
148 148
149 149 - rebase everything on a branch marked by a bookmark onto the
150 150 default branch::
151 151
152 152 hg rebase --base myfeature --dest default
153 153
154 154 - collapse a sequence of changes into a single commit::
155 155
156 156 hg rebase --collapse -r 1520:1525 -d .
157 157
158 158 - move a named branch while preserving its name::
159 159
160 160 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
161 161
162 162 Returns 0 on success, 1 if nothing to rebase or there are
163 163 unresolved conflicts.
164 164
165 165 """
166 166 originalwd = target = None
167 167 activebookmark = None
168 168 external = nullrev
169 169 state = {}
170 170 skipped = set()
171 171 targetancestors = set()
172 172
173 173
174 174 lock = wlock = None
175 175 try:
176 176 wlock = repo.wlock()
177 177 lock = repo.lock()
178 178
179 179 # Validate input and define rebasing points
180 180 destf = opts.get('dest', None)
181 181 srcf = opts.get('source', None)
182 182 basef = opts.get('base', None)
183 183 revf = opts.get('rev', [])
184 184 contf = opts.get('continue')
185 185 abortf = opts.get('abort')
186 186 collapsef = opts.get('collapse', False)
187 187 collapsemsg = cmdutil.logmessage(ui, opts)
188 188 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
189 189 extrafns = [_savegraft]
190 190 if e:
191 191 extrafns = [e]
192 192 keepf = opts.get('keep', False)
193 193 keepbranchesf = opts.get('keepbranches', False)
194 194 # keepopen is not meant for use on the command line, but by
195 195 # other extensions
196 196 keepopen = opts.get('keepopen', False)
197 197
198 198 if opts.get('interactive'):
199 199 msg = _("interactive history editing is supported by the "
200 200 "'histedit' extension (see 'hg help histedit')")
201 201 raise util.Abort(msg)
202 202
203 203 if collapsemsg and not collapsef:
204 204 raise util.Abort(
205 205 _('message can only be specified with collapse'))
206 206
207 207 if contf or abortf:
208 208 if contf and abortf:
209 209 raise util.Abort(_('cannot use both abort and continue'))
210 210 if collapsef:
211 211 raise util.Abort(
212 212 _('cannot use collapse with continue or abort'))
213 213 if srcf or basef or destf:
214 214 raise util.Abort(
215 215 _('abort and continue do not allow specifying revisions'))
216 216 if opts.get('tool', False):
217 217 ui.warn(_('tool option will be ignored\n'))
218 218
219 219 try:
220 220 (originalwd, target, state, skipped, collapsef, keepf,
221 221 keepbranchesf, external, activebookmark) = restorestatus(repo)
222 222 except error.RepoLookupError:
223 223 if abortf:
224 224 clearstatus(repo)
225 225 repo.ui.warn(_('rebase aborted (no revision is removed,'
226 226 ' only broken state is cleared)\n'))
227 227 return 0
228 228 else:
229 229 msg = _('cannot continue inconsistent rebase')
230 230 hint = _('use "hg rebase --abort" to clear broken state')
231 231 raise util.Abort(msg, hint=hint)
232 232 if abortf:
233 233 return abort(repo, originalwd, target, state)
234 234 else:
235 235 if srcf and basef:
236 236 raise util.Abort(_('cannot specify both a '
237 237 'source and a base'))
238 238 if revf and basef:
239 239 raise util.Abort(_('cannot specify both a '
240 240 'revision and a base'))
241 241 if revf and srcf:
242 242 raise util.Abort(_('cannot specify both a '
243 243 'revision and a source'))
244 244
245 245 cmdutil.checkunfinished(repo)
246 246 cmdutil.bailifchanged(repo)
247 247
248 248 if not destf:
249 249 # Destination defaults to the latest revision in the
250 250 # current branch
251 251 branch = repo[None].branch()
252 252 dest = repo[branch]
253 253 else:
254 254 dest = scmutil.revsingle(repo, destf)
255 255
256 256 if revf:
257 257 rebaseset = scmutil.revrange(repo, revf)
258 258 if not rebaseset:
259 259 ui.status(_('empty "rev" revision set - '
260 260 'nothing to rebase\n'))
261 261 return 1
262 262 elif srcf:
263 263 src = scmutil.revrange(repo, [srcf])
264 264 if not src:
265 265 ui.status(_('empty "source" revision set - '
266 266 'nothing to rebase\n'))
267 267 return 1
268 268 rebaseset = repo.revs('(%ld)::', src)
269 269 assert rebaseset
270 270 else:
271 271 base = scmutil.revrange(repo, [basef or '.'])
272 272 if not base:
273 273 ui.status(_('empty "base" revision set - '
274 274 "can't compute rebase set\n"))
275 275 return 1
276 276 rebaseset = repo.revs(
277 277 '(children(ancestor(%ld, %d)) and ::(%ld))::',
278 278 base, dest, base)
279 279 if not rebaseset:
280 280 # transform to list because smartsets are not comparable to
281 281 # lists. This should be improved to honor lazyness of
282 282 # smartset.
283 283 if list(base) == [dest.rev()]:
284 284 if basef:
285 285 ui.status(_('nothing to rebase - %s is both "base"'
286 286 ' and destination\n') % dest)
287 287 else:
288 288 ui.status(_('nothing to rebase - working directory '
289 289 'parent is also destination\n'))
290 290 elif not repo.revs('%ld - ::%d', base, dest):
291 291 if basef:
292 292 ui.status(_('nothing to rebase - "base" %s is '
293 293 'already an ancestor of destination '
294 294 '%s\n') %
295 295 ('+'.join(str(repo[r]) for r in base),
296 296 dest))
297 297 else:
298 298 ui.status(_('nothing to rebase - working '
299 299 'directory parent is already an '
300 300 'ancestor of destination %s\n') % dest)
301 301 else: # can it happen?
302 302 ui.status(_('nothing to rebase from %s to %s\n') %
303 303 ('+'.join(str(repo[r]) for r in base), dest))
304 304 return 1
305 305
306 306 if (not (keepf or obsolete._enabled)
307 307 and repo.revs('first(children(%ld) - %ld)',
308 308 rebaseset, rebaseset)):
309 309 raise util.Abort(
310 310 _("can't remove original changesets with"
311 311 " unrebased descendants"),
312 312 hint=_('use --keep to keep original changesets'))
313 313
314 314 result = buildstate(repo, dest, rebaseset, collapsef)
315 315 if not result:
316 316 # Empty state built, nothing to rebase
317 317 ui.status(_('nothing to rebase\n'))
318 318 return 1
319 319
320 320 root = min(rebaseset)
321 321 if not keepf and not repo[root].mutable():
322 322 raise util.Abort(_("can't rebase immutable changeset %s")
323 323 % repo[root],
324 324 hint=_('see hg help phases for details'))
325 325
326 326 originalwd, target, state = result
327 327 if collapsef:
328 328 targetancestors = repo.changelog.ancestors([target],
329 329 inclusive=True)
330 330 external = externalparent(repo, state, targetancestors)
331 331
332 332 if dest.closesbranch() and not keepbranchesf:
333 333 ui.status(_('reopening closed branch head %s\n') % dest)
334 334
335 335 if keepbranchesf:
336 336 # insert _savebranch at the start of extrafns so if
337 337 # there's a user-provided extrafn it can clobber branch if
338 338 # desired
339 339 extrafns.insert(0, _savebranch)
340 340 if collapsef:
341 341 branches = set()
342 342 for rev in state:
343 343 branches.add(repo[rev].branch())
344 344 if len(branches) > 1:
345 345 raise util.Abort(_('cannot collapse multiple named '
346 346 'branches'))
347 347
348 348 # Rebase
349 349 if not targetancestors:
350 350 targetancestors = repo.changelog.ancestors([target], inclusive=True)
351 351
352 352 # Keep track of the current bookmarks in order to reset them later
353 353 currentbookmarks = repo._bookmarks.copy()
354 354 activebookmark = activebookmark or repo._bookmarkcurrent
355 355 if activebookmark:
356 356 bookmarks.unsetcurrent(repo)
357 357
358 358 extrafn = _makeextrafn(extrafns)
359 359
360 360 sortedstate = sorted(state)
361 361 total = len(sortedstate)
362 362 pos = 0
363 363 for rev in sortedstate:
364 364 pos += 1
365 365 if state[rev] == -1:
366 366 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
367 367 _('changesets'), total)
368 368 p1, p2 = defineparents(repo, rev, target, state,
369 369 targetancestors)
370 370 storestatus(repo, originalwd, target, state, collapsef, keepf,
371 371 keepbranchesf, external, activebookmark)
372 372 if len(repo.parents()) == 2:
373 373 repo.ui.debug('resuming interrupted rebase\n')
374 374 else:
375 375 try:
376 376 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
377 377 'rebase')
378 378 stats = rebasenode(repo, rev, p1, state, collapsef,
379 379 target)
380 380 if stats and stats[3] > 0:
381 381 raise error.InterventionRequired(
382 382 _('unresolved conflicts (see hg '
383 383 'resolve, then hg rebase --continue)'))
384 384 finally:
385 385 ui.setconfig('ui', 'forcemerge', '', 'rebase')
386 386 if not collapsef:
387 387 merging = repo[p2].rev() != nullrev
388 388 editform = cmdutil.mergeeditform(merging, 'rebase')
389 389 editor = cmdutil.getcommiteditor(editform=editform, **opts)
390 390 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
391 391 editor=editor)
392 392 else:
393 393 # Skip commit if we are collapsing
394 394 repo.dirstate.beginparentchange()
395 395 repo.setparents(repo[p1].node())
396 396 repo.dirstate.endparentchange()
397 397 newrev = None
398 398 # Update the state
399 399 if newrev is not None:
400 400 state[rev] = repo[newrev].rev()
401 401 else:
402 402 if not collapsef:
403 403 ui.note(_('no changes, revision %d skipped\n') % rev)
404 404 ui.debug('next revision set to %s\n' % p1)
405 405 skipped.add(rev)
406 406 state[rev] = p1
407 407
408 408 ui.progress(_('rebasing'), None)
409 409 ui.note(_('rebase merging completed\n'))
410 410
411 411 if collapsef and not keepopen:
412 412 p1, p2 = defineparents(repo, min(state), target,
413 413 state, targetancestors)
414 414 editopt = opts.get('edit')
415 415 editform = 'rebase.collapse'
416 416 if collapsemsg:
417 417 commitmsg = collapsemsg
418 418 else:
419 419 commitmsg = 'Collapsed revision'
420 420 for rebased in state:
421 421 if rebased not in skipped and state[rebased] > nullmerge:
422 422 commitmsg += '\n* %s' % repo[rebased].description()
423 423 editopt = True
424 424 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
425 425 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
426 426 extrafn=extrafn, editor=editor)
427 427 for oldrev in state.iterkeys():
428 428 if state[oldrev] > nullmerge:
429 429 state[oldrev] = newrev
430 430
431 431 if 'qtip' in repo.tags():
432 432 updatemq(repo, state, skipped, **opts)
433 433
434 434 if currentbookmarks:
435 435 # Nodeids are needed to reset bookmarks
436 436 nstate = {}
437 437 for k, v in state.iteritems():
438 438 if v > nullmerge:
439 439 nstate[repo[k].node()] = repo[v].node()
440 440 # XXX this is the same as dest.node() for the non-continue path --
441 441 # this should probably be cleaned up
442 442 targetnode = repo[target].node()
443 443
444 444 # restore original working directory
445 445 # (we do this before stripping)
446 446 newwd = state.get(originalwd, originalwd)
447 447 if newwd not in [c.rev() for c in repo[None].parents()]:
448 448 ui.note(_("update back to initial working directory parent\n"))
449 449 hg.updaterepo(repo, newwd, False)
450 450
451 451 if not keepf:
452 452 collapsedas = None
453 453 if collapsef:
454 454 collapsedas = newrev
455 455 clearrebased(ui, repo, state, skipped, collapsedas)
456 456
457 457 if currentbookmarks:
458 458 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
459 459 if activebookmark not in repo._bookmarks:
460 460 # active bookmark was divergent one and has been deleted
461 461 activebookmark = None
462 462
463 463 clearstatus(repo)
464 464 ui.note(_("rebase completed\n"))
465 465 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
466 466 if skipped:
467 467 ui.note(_("%d revisions have been skipped\n") % len(skipped))
468 468
469 469 if (activebookmark and
470 470 repo['.'].node() == repo._bookmarks[activebookmark]):
471 471 bookmarks.setcurrent(repo, activebookmark)
472 472
473 473 finally:
474 474 release(lock, wlock)
475 475
476 476 def externalparent(repo, state, targetancestors):
477 477 """Return the revision that should be used as the second parent
478 478 when the revisions in state is collapsed on top of targetancestors.
479 479 Abort if there is more than one parent.
480 480 """
481 481 parents = set()
482 482 source = min(state)
483 483 for rev in state:
484 484 if rev == source:
485 485 continue
486 486 for p in repo[rev].parents():
487 487 if (p.rev() not in state
488 488 and p.rev() not in targetancestors):
489 489 parents.add(p.rev())
490 490 if not parents:
491 491 return nullrev
492 492 if len(parents) == 1:
493 493 return parents.pop()
494 494 raise util.Abort(_('unable to collapse on top of %s, there is more '
495 495 'than one external parent: %s') %
496 496 (max(targetancestors),
497 497 ', '.join(str(p) for p in sorted(parents))))
498 498
499 499 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
500 500 'Commit the changes and store useful information in extra'
501 501 try:
502 502 repo.dirstate.beginparentchange()
503 503 repo.setparents(repo[p1].node(), repo[p2].node())
504 504 repo.dirstate.endparentchange()
505 505 ctx = repo[rev]
506 506 if commitmsg is None:
507 507 commitmsg = ctx.description()
508 508 extra = {'rebase_source': ctx.hex()}
509 509 if extrafn:
510 510 extrafn(ctx, extra)
511 511
512 512 backup = repo.ui.backupconfig('phases', 'new-commit')
513 513 try:
514 514 targetphase = max(ctx.phase(), phases.draft)
515 515 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
516 516 # Commit might fail if unresolved files exist
517 517 newrev = repo.commit(text=commitmsg, user=ctx.user(),
518 518 date=ctx.date(), extra=extra, editor=editor)
519 519 finally:
520 520 repo.ui.restoreconfig(backup)
521 521
522 522 repo.dirstate.setbranch(repo[newrev].branch())
523 523 return newrev
524 524 except util.Abort:
525 525 # Invalidate the previous setparents
526 526 repo.dirstate.invalidate()
527 527 raise
528 528
529 529 def rebasenode(repo, rev, p1, state, collapse, target):
530 530 'Rebase a single revision'
531 531 # Merge phase
532 532 # Update to target and merge it with local
533 533 if repo['.'].rev() != repo[p1].rev():
534 534 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
535 535 merge.update(repo, p1, False, True, False)
536 536 else:
537 537 repo.ui.debug(" already in target\n")
538 538 repo.dirstate.write()
539 539 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
540 540 if repo[rev].rev() == repo[min(state)].rev():
541 541 # Case (1) initial changeset of a non-detaching rebase.
542 542 # Let the merge mechanism find the base itself.
543 543 base = None
544 544 elif not repo[rev].p2():
545 545 # Case (2) detaching the node with a single parent, use this parent
546 546 base = repo[rev].p1().node()
547 547 else:
548 548 # In case of merge, we need to pick the right parent as merge base.
549 549 #
550 550 # Imagine we have:
551 551 # - M: currently rebase revision in this step
552 552 # - A: one parent of M
553 553 # - B: second parent of M
554 554 # - D: destination of this merge step (p1 var)
555 555 #
556 556 # If we are rebasing on D, D is the successors of A or B. The right
557 557 # merge base is the one D succeed to. We pretend it is B for the rest
558 558 # of this comment
559 559 #
560 560 # If we pick B as the base, the merge involves:
561 561 # - changes from B to M (actual changeset payload)
562 562 # - changes from B to D (induced by rebase) as D is a rebased
563 563 # version of B)
564 564 # Which exactly represent the rebase operation.
565 565 #
566 566 # If we pick the A as the base, the merge involves
567 567 # - changes from A to M (actual changeset payload)
568 568 # - changes from A to D (with include changes between unrelated A and B
569 569 # plus changes induced by rebase)
570 570 # Which does not represent anything sensible and creates a lot of
571 571 # conflicts.
572 572 for p in repo[rev].parents():
573 573 if state.get(p.rev()) == repo[p1].rev():
574 574 base = p.node()
575 575 break
576 576 else: # fallback when base not found
577 577 base = None
578 578
579 579 # Raise because this function is called wrong (see issue 4106)
580 580 raise AssertionError('no base found to rebase on '
581 581 '(rebasenode called wrong)')
582 582 if base is not None:
583 583 repo.ui.debug(" detach base %d:%s\n" % (repo[base].rev(), repo[base]))
584 584 # When collapsing in-place, the parent is the common ancestor, we
585 585 # have to allow merging with it.
586 586 stats = merge.update(repo, rev, True, True, False, base, collapse,
587 587 labels=['dest', 'source'])
588 588 if collapse:
589 589 copies.duplicatecopies(repo, rev, target)
590 590 else:
591 591 # If we're not using --collapse, we need to
592 592 # duplicate copies between the revision we're
593 593 # rebasing and its first parent, but *not*
594 594 # duplicate any copies that have already been
595 595 # performed in the destination.
596 596 p1rev = repo[rev].p1().rev()
597 597 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
598 598 return stats
599 599
600 600 def nearestrebased(repo, rev, state):
601 601 """return the nearest ancestors of rev in the rebase result"""
602 602 rebased = [r for r in state if state[r] > nullmerge]
603 603 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
604 604 if candidates:
605 605 return state[candidates.first()]
606 606 else:
607 607 return None
608 608
609 609 def defineparents(repo, rev, target, state, targetancestors):
610 610 'Return the new parent relationship of the revision that will be rebased'
611 611 parents = repo[rev].parents()
612 612 p1 = p2 = nullrev
613 613
614 P1n = parents[0].rev()
615 if P1n in targetancestors:
614 p1n = parents[0].rev()
615 if p1n in targetancestors:
616 616 p1 = target
617 elif P1n in state:
618 if state[P1n] == nullmerge:
617 elif p1n in state:
618 if state[p1n] == nullmerge:
619 619 p1 = target
620 elif state[P1n] == revignored:
621 p1 = nearestrebased(repo, P1n, state)
620 elif state[p1n] == revignored:
621 p1 = nearestrebased(repo, p1n, state)
622 622 if p1 is None:
623 623 p1 = target
624 624 else:
625 p1 = state[P1n]
626 else: # P1n external
625 p1 = state[p1n]
626 else: # p1n external
627 627 p1 = target
628 p2 = P1n
628 p2 = p1n
629 629
630 630 if len(parents) == 2 and parents[1].rev() not in targetancestors:
631 P2n = parents[1].rev()
631 p2n = parents[1].rev()
632 632 # interesting second parent
633 if P2n in state:
634 if p1 == target: # P1n in targetancestors or external
635 p1 = state[P2n]
636 elif state[P2n] == revignored:
637 p2 = nearestrebased(repo, P2n, state)
633 if p2n in state:
634 if p1 == target: # p1n in targetancestors or external
635 p1 = state[p2n]
636 elif state[p2n] == revignored:
637 p2 = nearestrebased(repo, p2n, state)
638 638 if p2 is None:
639 639 # no ancestors rebased yet, detach
640 640 p2 = target
641 641 else:
642 p2 = state[P2n]
643 else: # P2n external
644 if p2 != nullrev: # P1n external too => rev is a merged revision
642 p2 = state[p2n]
643 else: # p2n external
644 if p2 != nullrev: # p1n external too => rev is a merged revision
645 645 raise util.Abort(_('cannot use revision %d as base, result '
646 646 'would have 3 parents') % rev)
647 p2 = P2n
647 p2 = p2n
648 648 repo.ui.debug(" future parents are %d and %d\n" %
649 649 (repo[p1].rev(), repo[p2].rev()))
650 650 return p1, p2
651 651
652 652 def isagitpatch(repo, patchname):
653 653 'Return true if the given patch is in git format'
654 654 mqpatch = os.path.join(repo.mq.path, patchname)
655 655 for line in patch.linereader(file(mqpatch, 'rb')):
656 656 if line.startswith('diff --git'):
657 657 return True
658 658 return False
659 659
660 660 def updatemq(repo, state, skipped, **opts):
661 661 'Update rebased mq patches - finalize and then import them'
662 662 mqrebase = {}
663 663 mq = repo.mq
664 664 original_series = mq.fullseries[:]
665 665 skippedpatches = set()
666 666
667 667 for p in mq.applied:
668 668 rev = repo[p.node].rev()
669 669 if rev in state:
670 670 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
671 671 (rev, p.name))
672 672 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
673 673 else:
674 674 # Applied but not rebased, not sure this should happen
675 675 skippedpatches.add(p.name)
676 676
677 677 if mqrebase:
678 678 mq.finish(repo, mqrebase.keys())
679 679
680 680 # We must start import from the newest revision
681 681 for rev in sorted(mqrebase, reverse=True):
682 682 if rev not in skipped:
683 683 name, isgit = mqrebase[rev]
684 684 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
685 685 mq.qimport(repo, (), patchname=name, git=isgit,
686 686 rev=[str(state[rev])])
687 687 else:
688 688 # Rebased and skipped
689 689 skippedpatches.add(mqrebase[rev][0])
690 690
691 691 # Patches were either applied and rebased and imported in
692 692 # order, applied and removed or unapplied. Discard the removed
693 693 # ones while preserving the original series order and guards.
694 694 newseries = [s for s in original_series
695 695 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
696 696 mq.fullseries[:] = newseries
697 697 mq.seriesdirty = True
698 698 mq.savedirty()
699 699
700 700 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
701 701 'Move bookmarks to their correct changesets, and delete divergent ones'
702 702 marks = repo._bookmarks
703 703 for k, v in originalbookmarks.iteritems():
704 704 if v in nstate:
705 705 # update the bookmarks for revs that have moved
706 706 marks[k] = nstate[v]
707 707 bookmarks.deletedivergent(repo, [targetnode], k)
708 708
709 709 marks.write()
710 710
711 711 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
712 712 external, activebookmark):
713 713 'Store the current status to allow recovery'
714 714 f = repo.opener("rebasestate", "w")
715 715 f.write(repo[originalwd].hex() + '\n')
716 716 f.write(repo[target].hex() + '\n')
717 717 f.write(repo[external].hex() + '\n')
718 718 f.write('%d\n' % int(collapse))
719 719 f.write('%d\n' % int(keep))
720 720 f.write('%d\n' % int(keepbranches))
721 721 f.write('%s\n' % (activebookmark or ''))
722 722 for d, v in state.iteritems():
723 723 oldrev = repo[d].hex()
724 724 if v > nullmerge:
725 725 newrev = repo[v].hex()
726 726 else:
727 727 newrev = v
728 728 f.write("%s:%s\n" % (oldrev, newrev))
729 729 f.close()
730 730 repo.ui.debug('rebase status stored\n')
731 731
732 732 def clearstatus(repo):
733 733 'Remove the status files'
734 734 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
735 735
736 736 def restorestatus(repo):
737 737 'Restore a previously stored status'
738 738 try:
739 739 keepbranches = None
740 740 target = None
741 741 collapse = False
742 742 external = nullrev
743 743 activebookmark = None
744 744 state = {}
745 745 f = repo.opener("rebasestate")
746 746 for i, l in enumerate(f.read().splitlines()):
747 747 if i == 0:
748 748 originalwd = repo[l].rev()
749 749 elif i == 1:
750 750 target = repo[l].rev()
751 751 elif i == 2:
752 752 external = repo[l].rev()
753 753 elif i == 3:
754 754 collapse = bool(int(l))
755 755 elif i == 4:
756 756 keep = bool(int(l))
757 757 elif i == 5:
758 758 keepbranches = bool(int(l))
759 759 elif i == 6 and not (len(l) == 81 and ':' in l):
760 760 # line 6 is a recent addition, so for backwards compatibility
761 761 # check that the line doesn't look like the oldrev:newrev lines
762 762 activebookmark = l
763 763 else:
764 764 oldrev, newrev = l.split(':')
765 765 if newrev in (str(nullmerge), str(revignored)):
766 766 state[repo[oldrev].rev()] = int(newrev)
767 767 else:
768 768 state[repo[oldrev].rev()] = repo[newrev].rev()
769 769
770 770 if keepbranches is None:
771 771 raise util.Abort(_('.hg/rebasestate is incomplete'))
772 772
773 773 skipped = set()
774 774 # recompute the set of skipped revs
775 775 if not collapse:
776 776 seen = set([target])
777 777 for old, new in sorted(state.items()):
778 778 if new != nullrev and new in seen:
779 779 skipped.add(old)
780 780 seen.add(new)
781 781 repo.ui.debug('computed skipped revs: %s\n' %
782 782 (' '.join(str(r) for r in sorted(skipped)) or None))
783 783 repo.ui.debug('rebase status resumed\n')
784 784 return (originalwd, target, state, skipped,
785 785 collapse, keep, keepbranches, external, activebookmark)
786 786 except IOError, err:
787 787 if err.errno != errno.ENOENT:
788 788 raise
789 789 raise util.Abort(_('no rebase in progress'))
790 790
791 791 def inrebase(repo, originalwd, state):
792 792 '''check whether the working dir is in an interrupted rebase'''
793 793 parents = [p.rev() for p in repo.parents()]
794 794 if originalwd in parents:
795 795 return True
796 796
797 797 for newrev in state.itervalues():
798 798 if newrev in parents:
799 799 return True
800 800
801 801 return False
802 802
803 803 def abort(repo, originalwd, target, state):
804 804 'Restore the repository to its original state'
805 805 dstates = [s for s in state.values() if s > nullrev]
806 806 immutable = [d for d in dstates if not repo[d].mutable()]
807 807 cleanup = True
808 808 if immutable:
809 809 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
810 810 % ', '.join(str(repo[r]) for r in immutable),
811 811 hint=_('see hg help phases for details'))
812 812 cleanup = False
813 813
814 814 descendants = set()
815 815 if dstates:
816 816 descendants = set(repo.changelog.descendants(dstates))
817 817 if descendants - set(dstates):
818 818 repo.ui.warn(_("warning: new changesets detected on target branch, "
819 819 "can't strip\n"))
820 820 cleanup = False
821 821
822 822 if cleanup:
823 823 # Update away from the rebase if necessary
824 824 if inrebase(repo, originalwd, state):
825 825 merge.update(repo, repo[originalwd].rev(), False, True, False)
826 826
827 827 # Strip from the first rebased revision
828 828 rebased = filter(lambda x: x > -1 and x != target, state.values())
829 829 if rebased:
830 830 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
831 831 # no backup of rebased cset versions needed
832 832 repair.strip(repo.ui, repo, strippoints)
833 833
834 834 clearstatus(repo)
835 835 repo.ui.warn(_('rebase aborted\n'))
836 836 return 0
837 837
838 838 def buildstate(repo, dest, rebaseset, collapse):
839 839 '''Define which revisions are going to be rebased and where
840 840
841 841 repo: repo
842 842 dest: context
843 843 rebaseset: set of rev
844 844 '''
845 845
846 846 # This check isn't strictly necessary, since mq detects commits over an
847 847 # applied patch. But it prevents messing up the working directory when
848 848 # a partially completed rebase is blocked by mq.
849 849 if 'qtip' in repo.tags() and (dest.node() in
850 850 [s.node for s in repo.mq.applied]):
851 851 raise util.Abort(_('cannot rebase onto an applied mq patch'))
852 852
853 853 roots = list(repo.set('roots(%ld)', rebaseset))
854 854 if not roots:
855 855 raise util.Abort(_('no matching revisions'))
856 856 roots.sort()
857 857 state = {}
858 858 detachset = set()
859 859 for root in roots:
860 860 commonbase = root.ancestor(dest)
861 861 if commonbase == root:
862 862 raise util.Abort(_('source is ancestor of destination'))
863 863 if commonbase == dest:
864 864 samebranch = root.branch() == dest.branch()
865 865 if not collapse and samebranch and root in dest.children():
866 866 repo.ui.debug('source is a child of destination\n')
867 867 return None
868 868
869 869 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
870 870 state.update(dict.fromkeys(rebaseset, nullrev))
871 871 # Rebase tries to turn <dest> into a parent of <root> while
872 872 # preserving the number of parents of rebased changesets:
873 873 #
874 874 # - A changeset with a single parent will always be rebased as a
875 875 # changeset with a single parent.
876 876 #
877 877 # - A merge will be rebased as merge unless its parents are both
878 878 # ancestors of <dest> or are themselves in the rebased set and
879 879 # pruned while rebased.
880 880 #
881 881 # If one parent of <root> is an ancestor of <dest>, the rebased
882 882 # version of this parent will be <dest>. This is always true with
883 883 # --base option.
884 884 #
885 885 # Otherwise, we need to *replace* the original parents with
886 886 # <dest>. This "detaches" the rebased set from its former location
887 887 # and rebases it onto <dest>. Changes introduced by ancestors of
888 888 # <root> not common with <dest> (the detachset, marked as
889 889 # nullmerge) are "removed" from the rebased changesets.
890 890 #
891 891 # - If <root> has a single parent, set it to <dest>.
892 892 #
893 893 # - If <root> is a merge, we cannot decide which parent to
894 894 # replace, the rebase operation is not clearly defined.
895 895 #
896 896 # The table below sums up this behavior:
897 897 #
898 898 # +------------------+----------------------+-------------------------+
899 899 # | | one parent | merge |
900 900 # +------------------+----------------------+-------------------------+
901 901 # | parent in | new parent is <dest> | parents in ::<dest> are |
902 902 # | ::<dest> | | remapped to <dest> |
903 903 # +------------------+----------------------+-------------------------+
904 904 # | unrelated source | new parent is <dest> | ambiguous, abort |
905 905 # +------------------+----------------------+-------------------------+
906 906 #
907 907 # The actual abort is handled by `defineparents`
908 908 if len(root.parents()) <= 1:
909 909 # ancestors of <root> not ancestors of <dest>
910 910 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
911 911 [root.rev()]))
912 912 for r in detachset:
913 913 if r not in state:
914 914 state[r] = nullmerge
915 915 if len(roots) > 1:
916 916 # If we have multiple roots, we may have "hole" in the rebase set.
917 917 # Rebase roots that descend from those "hole" should not be detached as
918 918 # other root are. We use the special `revignored` to inform rebase that
919 919 # the revision should be ignored but that `defineparents` should search
920 920 # a rebase destination that make sense regarding rebased topology.
921 921 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
922 922 for ignored in set(rebasedomain) - set(rebaseset):
923 923 state[ignored] = revignored
924 924 return repo['.'].rev(), dest.rev(), state
925 925
926 926 def clearrebased(ui, repo, state, skipped, collapsedas=None):
927 927 """dispose of rebased revision at the end of the rebase
928 928
929 929 If `collapsedas` is not None, the rebase was a collapse whose result if the
930 930 `collapsedas` node."""
931 931 if obsolete._enabled:
932 932 markers = []
933 933 for rev, newrev in sorted(state.items()):
934 934 if newrev >= 0:
935 935 if rev in skipped:
936 936 succs = ()
937 937 elif collapsedas is not None:
938 938 succs = (repo[collapsedas],)
939 939 else:
940 940 succs = (repo[newrev],)
941 941 markers.append((repo[rev], succs))
942 942 if markers:
943 943 obsolete.createmarkers(repo, markers)
944 944 else:
945 945 rebased = [rev for rev in state if state[rev] > nullmerge]
946 946 if rebased:
947 947 stripped = []
948 948 for root in repo.set('roots(%ld)', rebased):
949 949 if set(repo.changelog.descendants([root.rev()])) - set(state):
950 950 ui.warn(_("warning: new changesets detected "
951 951 "on source branch, not stripping\n"))
952 952 else:
953 953 stripped.append(root.node())
954 954 if stripped:
955 955 # backup the old csets by default
956 956 repair.strip(ui, repo, stripped, "all")
957 957
958 958
959 959 def pullrebase(orig, ui, repo, *args, **opts):
960 960 'Call rebase after pull if the latter has been invoked with --rebase'
961 961 if opts.get('rebase'):
962 962 if opts.get('update'):
963 963 del opts['update']
964 964 ui.debug('--update and --rebase are not compatible, ignoring '
965 965 'the update flag\n')
966 966
967 967 movemarkfrom = repo['.'].node()
968 968 revsprepull = len(repo)
969 969 origpostincoming = commands.postincoming
970 970 def _dummy(*args, **kwargs):
971 971 pass
972 972 commands.postincoming = _dummy
973 973 try:
974 974 orig(ui, repo, *args, **opts)
975 975 finally:
976 976 commands.postincoming = origpostincoming
977 977 revspostpull = len(repo)
978 978 if revspostpull > revsprepull:
979 979 # --rev option from pull conflict with rebase own --rev
980 980 # dropping it
981 981 if 'rev' in opts:
982 982 del opts['rev']
983 983 rebase(ui, repo, **opts)
984 984 branch = repo[None].branch()
985 985 dest = repo[branch].rev()
986 986 if dest != repo['.'].rev():
987 987 # there was nothing to rebase we force an update
988 988 hg.update(repo, dest)
989 989 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
990 990 ui.status(_("updating bookmark %s\n")
991 991 % repo._bookmarkcurrent)
992 992 else:
993 993 if opts.get('tool'):
994 994 raise util.Abort(_('--tool can only be used with --rebase'))
995 995 orig(ui, repo, *args, **opts)
996 996
997 997 def summaryhook(ui, repo):
998 998 if not os.path.exists(repo.join('rebasestate')):
999 999 return
1000 1000 try:
1001 1001 state = restorestatus(repo)[2]
1002 1002 except error.RepoLookupError:
1003 1003 # i18n: column positioning for "hg summary"
1004 1004 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1005 1005 ui.write(msg)
1006 1006 return
1007 1007 numrebased = len([i for i in state.itervalues() if i != -1])
1008 1008 # i18n: column positioning for "hg summary"
1009 1009 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1010 1010 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1011 1011 ui.label(_('%d remaining'), 'rebase.remaining') %
1012 1012 (len(state) - numrebased)))
1013 1013
1014 1014 def uisetup(ui):
1015 1015 'Replace pull with a decorator to provide --rebase option'
1016 1016 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1017 1017 entry[1].append(('', 'rebase', None,
1018 1018 _("rebase working directory to branch head")))
1019 1019 entry[1].append(('t', 'tool', '',
1020 1020 _("specify merge tool for rebase")))
1021 1021 cmdutil.summaryhooks.add('rebase', summaryhook)
1022 1022 cmdutil.unfinishedstates.append(
1023 1023 ['rebasestate', False, False, _('rebase in progress'),
1024 1024 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
General Comments 0
You need to be logged in to leave comments. Login now