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