##// END OF EJS Templates
rebase: use '>= 0' to know is a revision was rebased...
Pierre-Yves David -
r23489:fe4157d8 default
parent child Browse files
Show More
@@ -1,1039 +1,1039
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 commonanc = repo.revs('ancestor(%ld, %d)', base, dest).first()
277 277 if commonanc is not None:
278 278 rebaseset = repo.revs('(%d::(%ld) - %d)::',
279 279 commonanc, base, commonanc)
280 280 else:
281 281 rebaseset = []
282 282
283 283 if not rebaseset:
284 284 # transform to list because smartsets are not comparable to
285 285 # lists. This should be improved to honor laziness of
286 286 # smartset.
287 287 if list(base) == [dest.rev()]:
288 288 if basef:
289 289 ui.status(_('nothing to rebase - %s is both "base"'
290 290 ' and destination\n') % dest)
291 291 else:
292 292 ui.status(_('nothing to rebase - working directory '
293 293 'parent is also destination\n'))
294 294 elif not repo.revs('%ld - ::%d', base, dest):
295 295 if basef:
296 296 ui.status(_('nothing to rebase - "base" %s is '
297 297 'already an ancestor of destination '
298 298 '%s\n') %
299 299 ('+'.join(str(repo[r]) for r in base),
300 300 dest))
301 301 else:
302 302 ui.status(_('nothing to rebase - working '
303 303 'directory parent is already an '
304 304 'ancestor of destination %s\n') % dest)
305 305 else: # can it happen?
306 306 ui.status(_('nothing to rebase from %s to %s\n') %
307 307 ('+'.join(str(repo[r]) for r in base), dest))
308 308 return 1
309 309
310 310 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
311 311 if (not (keepf or allowunstable)
312 312 and repo.revs('first(children(%ld) - %ld)',
313 313 rebaseset, rebaseset)):
314 314 raise util.Abort(
315 315 _("can't remove original changesets with"
316 316 " unrebased descendants"),
317 317 hint=_('use --keep to keep original changesets'))
318 318
319 319 result = buildstate(repo, dest, rebaseset, collapsef)
320 320 if not result:
321 321 # Empty state built, nothing to rebase
322 322 ui.status(_('nothing to rebase\n'))
323 323 return 1
324 324
325 325 root = min(rebaseset)
326 326 if not keepf and not repo[root].mutable():
327 327 raise util.Abort(_("can't rebase immutable changeset %s")
328 328 % repo[root],
329 329 hint=_('see hg help phases for details'))
330 330
331 331 originalwd, target, state = result
332 332 if collapsef:
333 333 targetancestors = repo.changelog.ancestors([target],
334 334 inclusive=True)
335 335 external = externalparent(repo, state, targetancestors)
336 336
337 337 if dest.closesbranch() and not keepbranchesf:
338 338 ui.status(_('reopening closed branch head %s\n') % dest)
339 339
340 340 if keepbranchesf:
341 341 # insert _savebranch at the start of extrafns so if
342 342 # there's a user-provided extrafn it can clobber branch if
343 343 # desired
344 344 extrafns.insert(0, _savebranch)
345 345 if collapsef:
346 346 branches = set()
347 347 for rev in state:
348 348 branches.add(repo[rev].branch())
349 349 if len(branches) > 1:
350 350 raise util.Abort(_('cannot collapse multiple named '
351 351 'branches'))
352 352
353 353 # Rebase
354 354 if not targetancestors:
355 355 targetancestors = repo.changelog.ancestors([target], inclusive=True)
356 356
357 357 # Keep track of the current bookmarks in order to reset them later
358 358 currentbookmarks = repo._bookmarks.copy()
359 359 activebookmark = activebookmark or repo._bookmarkcurrent
360 360 if activebookmark:
361 361 bookmarks.unsetcurrent(repo)
362 362
363 363 extrafn = _makeextrafn(extrafns)
364 364
365 365 sortedstate = sorted(state)
366 366 total = len(sortedstate)
367 367 pos = 0
368 368 for rev in sortedstate:
369 369 pos += 1
370 370 if state[rev] == -1:
371 371 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, repo[rev])),
372 372 _('changesets'), total)
373 373 p1, p2, base = defineparents(repo, rev, target, state,
374 374 targetancestors)
375 375 storestatus(repo, originalwd, target, state, collapsef, keepf,
376 376 keepbranchesf, external, activebookmark)
377 377 if len(repo.parents()) == 2:
378 378 repo.ui.debug('resuming interrupted rebase\n')
379 379 else:
380 380 try:
381 381 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
382 382 'rebase')
383 383 stats = rebasenode(repo, rev, p1, base, state,
384 384 collapsef, target)
385 385 if stats and stats[3] > 0:
386 386 raise error.InterventionRequired(
387 387 _('unresolved conflicts (see hg '
388 388 'resolve, then hg rebase --continue)'))
389 389 finally:
390 390 ui.setconfig('ui', 'forcemerge', '', 'rebase')
391 391 if not collapsef:
392 392 merging = p2 != nullrev
393 393 editform = cmdutil.mergeeditform(merging, 'rebase')
394 394 editor = cmdutil.getcommiteditor(editform=editform, **opts)
395 395 newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
396 396 editor=editor)
397 397 else:
398 398 # Skip commit if we are collapsing
399 399 repo.dirstate.beginparentchange()
400 400 repo.setparents(repo[p1].node())
401 401 repo.dirstate.endparentchange()
402 402 newnode = None
403 403 # Update the state
404 404 if newnode is not None:
405 405 state[rev] = repo[newnode].rev()
406 406 else:
407 407 if not collapsef:
408 408 ui.note(_('no changes, revision %d skipped\n') % rev)
409 409 ui.debug('next revision set to %s\n' % p1)
410 410 skipped.add(rev)
411 411 state[rev] = p1
412 412
413 413 ui.progress(_('rebasing'), None)
414 414 ui.note(_('rebase merging completed\n'))
415 415
416 416 if collapsef and not keepopen:
417 417 p1, p2, _base = defineparents(repo, min(state), target,
418 418 state, targetancestors)
419 419 editopt = opts.get('edit')
420 420 editform = 'rebase.collapse'
421 421 if collapsemsg:
422 422 commitmsg = collapsemsg
423 423 else:
424 424 commitmsg = 'Collapsed revision'
425 425 for rebased in state:
426 426 if rebased not in skipped and state[rebased] > nullmerge:
427 427 commitmsg += '\n* %s' % repo[rebased].description()
428 428 editopt = True
429 429 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
430 430 newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
431 431 extrafn=extrafn, editor=editor)
432 432 if newnode is None:
433 433 newrev = target
434 434 else:
435 435 newrev = repo[newnode].rev()
436 436 for oldrev in state.iterkeys():
437 437 if state[oldrev] > nullmerge:
438 438 state[oldrev] = newrev
439 439
440 440 if 'qtip' in repo.tags():
441 441 updatemq(repo, state, skipped, **opts)
442 442
443 443 if currentbookmarks:
444 444 # Nodeids are needed to reset bookmarks
445 445 nstate = {}
446 446 for k, v in state.iteritems():
447 447 if v > nullmerge:
448 448 nstate[repo[k].node()] = repo[v].node()
449 449 # XXX this is the same as dest.node() for the non-continue path --
450 450 # this should probably be cleaned up
451 451 targetnode = repo[target].node()
452 452
453 453 # restore original working directory
454 454 # (we do this before stripping)
455 455 newwd = state.get(originalwd, originalwd)
456 456 if newwd < 0:
457 457 # original directory is a parent of rebase set root or ignored
458 458 newwd = originalwd
459 459 if newwd not in [c.rev() for c in repo[None].parents()]:
460 460 ui.note(_("update back to initial working directory parent\n"))
461 461 hg.updaterepo(repo, newwd, False)
462 462
463 463 if not keepf:
464 464 collapsedas = None
465 465 if collapsef:
466 466 collapsedas = newnode
467 467 clearrebased(ui, repo, state, skipped, collapsedas)
468 468
469 469 if currentbookmarks:
470 470 updatebookmarks(repo, targetnode, nstate, currentbookmarks)
471 471 if activebookmark not in repo._bookmarks:
472 472 # active bookmark was divergent one and has been deleted
473 473 activebookmark = None
474 474
475 475 clearstatus(repo)
476 476 ui.note(_("rebase completed\n"))
477 477 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
478 478 if skipped:
479 479 ui.note(_("%d revisions have been skipped\n") % len(skipped))
480 480
481 481 if (activebookmark and
482 482 repo['.'].node() == repo._bookmarks[activebookmark]):
483 483 bookmarks.setcurrent(repo, activebookmark)
484 484
485 485 finally:
486 486 release(lock, wlock)
487 487
488 488 def externalparent(repo, state, targetancestors):
489 489 """Return the revision that should be used as the second parent
490 490 when the revisions in state is collapsed on top of targetancestors.
491 491 Abort if there is more than one parent.
492 492 """
493 493 parents = set()
494 494 source = min(state)
495 495 for rev in state:
496 496 if rev == source:
497 497 continue
498 498 for p in repo[rev].parents():
499 499 if (p.rev() not in state
500 500 and p.rev() not in targetancestors):
501 501 parents.add(p.rev())
502 502 if not parents:
503 503 return nullrev
504 504 if len(parents) == 1:
505 505 return parents.pop()
506 506 raise util.Abort(_('unable to collapse on top of %s, there is more '
507 507 'than one external parent: %s') %
508 508 (max(targetancestors),
509 509 ', '.join(str(p) for p in sorted(parents))))
510 510
511 511 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
512 512 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
513 513 but also store useful information in extra.
514 514 Return node of committed revision.'''
515 515 try:
516 516 repo.dirstate.beginparentchange()
517 517 repo.setparents(repo[p1].node(), repo[p2].node())
518 518 repo.dirstate.endparentchange()
519 519 ctx = repo[rev]
520 520 if commitmsg is None:
521 521 commitmsg = ctx.description()
522 522 extra = {'rebase_source': ctx.hex()}
523 523 if extrafn:
524 524 extrafn(ctx, extra)
525 525
526 526 backup = repo.ui.backupconfig('phases', 'new-commit')
527 527 try:
528 528 targetphase = max(ctx.phase(), phases.draft)
529 529 repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
530 530 # Commit might fail if unresolved files exist
531 531 newnode = repo.commit(text=commitmsg, user=ctx.user(),
532 532 date=ctx.date(), extra=extra, editor=editor)
533 533 finally:
534 534 repo.ui.restoreconfig(backup)
535 535
536 536 repo.dirstate.setbranch(repo[newnode].branch())
537 537 return newnode
538 538 except util.Abort:
539 539 # Invalidate the previous setparents
540 540 repo.dirstate.invalidate()
541 541 raise
542 542
543 543 def rebasenode(repo, rev, p1, base, state, collapse, target):
544 544 'Rebase a single revision rev on top of p1 using base as merge ancestor'
545 545 # Merge phase
546 546 # Update to target and merge it with local
547 547 if repo['.'].rev() != p1:
548 548 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
549 549 merge.update(repo, p1, False, True, False)
550 550 else:
551 551 repo.ui.debug(" already in target\n")
552 552 repo.dirstate.write()
553 553 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
554 554 if base is not None:
555 555 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
556 556 # When collapsing in-place, the parent is the common ancestor, we
557 557 # have to allow merging with it.
558 558 stats = merge.update(repo, rev, True, True, False, base, collapse,
559 559 labels=['dest', 'source'])
560 560 if collapse:
561 561 copies.duplicatecopies(repo, rev, target)
562 562 else:
563 563 # If we're not using --collapse, we need to
564 564 # duplicate copies between the revision we're
565 565 # rebasing and its first parent, but *not*
566 566 # duplicate any copies that have already been
567 567 # performed in the destination.
568 568 p1rev = repo[rev].p1().rev()
569 569 copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
570 570 return stats
571 571
572 572 def nearestrebased(repo, rev, state):
573 573 """return the nearest ancestors of rev in the rebase result"""
574 574 rebased = [r for r in state if state[r] > nullmerge]
575 575 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
576 576 if candidates:
577 577 return state[candidates.first()]
578 578 else:
579 579 return None
580 580
581 581 def defineparents(repo, rev, target, state, targetancestors):
582 582 'Return the new parent relationship of the revision that will be rebased'
583 583 parents = repo[rev].parents()
584 584 p1 = p2 = nullrev
585 585
586 586 p1n = parents[0].rev()
587 587 if p1n in targetancestors:
588 588 p1 = target
589 589 elif p1n in state:
590 590 if state[p1n] == nullmerge:
591 591 p1 = target
592 592 elif state[p1n] == revignored:
593 593 p1 = nearestrebased(repo, p1n, state)
594 594 if p1 is None:
595 595 p1 = target
596 596 else:
597 597 p1 = state[p1n]
598 598 else: # p1n external
599 599 p1 = target
600 600 p2 = p1n
601 601
602 602 if len(parents) == 2 and parents[1].rev() not in targetancestors:
603 603 p2n = parents[1].rev()
604 604 # interesting second parent
605 605 if p2n in state:
606 606 if p1 == target: # p1n in targetancestors or external
607 607 p1 = state[p2n]
608 608 elif state[p2n] == revignored:
609 609 p2 = nearestrebased(repo, p2n, state)
610 610 if p2 is None:
611 611 # no ancestors rebased yet, detach
612 612 p2 = target
613 613 else:
614 614 p2 = state[p2n]
615 615 else: # p2n external
616 616 if p2 != nullrev: # p1n external too => rev is a merged revision
617 617 raise util.Abort(_('cannot use revision %d as base, result '
618 618 'would have 3 parents') % rev)
619 619 p2 = p2n
620 620 repo.ui.debug(" future parents are %d and %d\n" %
621 621 (repo[p1].rev(), repo[p2].rev()))
622 622
623 623 if rev == min(state):
624 624 # Case (1) initial changeset of a non-detaching rebase.
625 625 # Let the merge mechanism find the base itself.
626 626 base = None
627 627 elif not repo[rev].p2():
628 628 # Case (2) detaching the node with a single parent, use this parent
629 629 base = repo[rev].p1().rev()
630 630 else:
631 631 # In case of merge, we need to pick the right parent as merge base.
632 632 #
633 633 # Imagine we have:
634 634 # - M: currently rebase revision in this step
635 635 # - A: one parent of M
636 636 # - B: second parent of M
637 637 # - D: destination of this merge step (p1 var)
638 638 #
639 639 # If we are rebasing on D, D is the successors of A or B. The right
640 640 # merge base is the one D succeed to. We pretend it is B for the rest
641 641 # of this comment
642 642 #
643 643 # If we pick B as the base, the merge involves:
644 644 # - changes from B to M (actual changeset payload)
645 645 # - changes from B to D (induced by rebase) as D is a rebased
646 646 # version of B)
647 647 # Which exactly represent the rebase operation.
648 648 #
649 649 # If we pick the A as the base, the merge involves
650 650 # - changes from A to M (actual changeset payload)
651 651 # - changes from A to D (with include changes between unrelated A and B
652 652 # plus changes induced by rebase)
653 653 # Which does not represent anything sensible and creates a lot of
654 654 # conflicts.
655 655 for p in repo[rev].parents():
656 656 if state.get(p.rev()) == p1:
657 657 base = p.rev()
658 658 break
659 659 else: # fallback when base not found
660 660 base = None
661 661
662 662 # Raise because this function is called wrong (see issue 4106)
663 663 raise AssertionError('no base found to rebase on '
664 664 '(defineparents called wrong)')
665 665 return p1, p2, base
666 666
667 667 def isagitpatch(repo, patchname):
668 668 'Return true if the given patch is in git format'
669 669 mqpatch = os.path.join(repo.mq.path, patchname)
670 670 for line in patch.linereader(file(mqpatch, 'rb')):
671 671 if line.startswith('diff --git'):
672 672 return True
673 673 return False
674 674
675 675 def updatemq(repo, state, skipped, **opts):
676 676 'Update rebased mq patches - finalize and then import them'
677 677 mqrebase = {}
678 678 mq = repo.mq
679 679 original_series = mq.fullseries[:]
680 680 skippedpatches = set()
681 681
682 682 for p in mq.applied:
683 683 rev = repo[p.node].rev()
684 684 if rev in state:
685 685 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
686 686 (rev, p.name))
687 687 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
688 688 else:
689 689 # Applied but not rebased, not sure this should happen
690 690 skippedpatches.add(p.name)
691 691
692 692 if mqrebase:
693 693 mq.finish(repo, mqrebase.keys())
694 694
695 695 # We must start import from the newest revision
696 696 for rev in sorted(mqrebase, reverse=True):
697 697 if rev not in skipped:
698 698 name, isgit = mqrebase[rev]
699 699 repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name))
700 700 mq.qimport(repo, (), patchname=name, git=isgit,
701 701 rev=[str(state[rev])])
702 702 else:
703 703 # Rebased and skipped
704 704 skippedpatches.add(mqrebase[rev][0])
705 705
706 706 # Patches were either applied and rebased and imported in
707 707 # order, applied and removed or unapplied. Discard the removed
708 708 # ones while preserving the original series order and guards.
709 709 newseries = [s for s in original_series
710 710 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
711 711 mq.fullseries[:] = newseries
712 712 mq.seriesdirty = True
713 713 mq.savedirty()
714 714
715 715 def updatebookmarks(repo, targetnode, nstate, originalbookmarks):
716 716 'Move bookmarks to their correct changesets, and delete divergent ones'
717 717 marks = repo._bookmarks
718 718 for k, v in originalbookmarks.iteritems():
719 719 if v in nstate:
720 720 # update the bookmarks for revs that have moved
721 721 marks[k] = nstate[v]
722 722 bookmarks.deletedivergent(repo, [targetnode], k)
723 723
724 724 marks.write()
725 725
726 726 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
727 727 external, activebookmark):
728 728 'Store the current status to allow recovery'
729 729 f = repo.opener("rebasestate", "w")
730 730 f.write(repo[originalwd].hex() + '\n')
731 731 f.write(repo[target].hex() + '\n')
732 732 f.write(repo[external].hex() + '\n')
733 733 f.write('%d\n' % int(collapse))
734 734 f.write('%d\n' % int(keep))
735 735 f.write('%d\n' % int(keepbranches))
736 736 f.write('%s\n' % (activebookmark or ''))
737 737 for d, v in state.iteritems():
738 738 oldrev = repo[d].hex()
739 739 if v > nullmerge:
740 740 newrev = repo[v].hex()
741 741 else:
742 742 newrev = v
743 743 f.write("%s:%s\n" % (oldrev, newrev))
744 744 f.close()
745 745 repo.ui.debug('rebase status stored\n')
746 746
747 747 def clearstatus(repo):
748 748 'Remove the status files'
749 749 util.unlinkpath(repo.join("rebasestate"), ignoremissing=True)
750 750
751 751 def restorestatus(repo):
752 752 'Restore a previously stored status'
753 753 try:
754 754 keepbranches = None
755 755 target = None
756 756 collapse = False
757 757 external = nullrev
758 758 activebookmark = None
759 759 state = {}
760 760 f = repo.opener("rebasestate")
761 761 for i, l in enumerate(f.read().splitlines()):
762 762 if i == 0:
763 763 originalwd = repo[l].rev()
764 764 elif i == 1:
765 765 target = repo[l].rev()
766 766 elif i == 2:
767 767 external = repo[l].rev()
768 768 elif i == 3:
769 769 collapse = bool(int(l))
770 770 elif i == 4:
771 771 keep = bool(int(l))
772 772 elif i == 5:
773 773 keepbranches = bool(int(l))
774 774 elif i == 6 and not (len(l) == 81 and ':' in l):
775 775 # line 6 is a recent addition, so for backwards compatibility
776 776 # check that the line doesn't look like the oldrev:newrev lines
777 777 activebookmark = l
778 778 else:
779 779 oldrev, newrev = l.split(':')
780 780 if newrev in (str(nullmerge), str(revignored)):
781 781 state[repo[oldrev].rev()] = int(newrev)
782 782 else:
783 783 state[repo[oldrev].rev()] = repo[newrev].rev()
784 784
785 785 if keepbranches is None:
786 786 raise util.Abort(_('.hg/rebasestate is incomplete'))
787 787
788 788 skipped = set()
789 789 # recompute the set of skipped revs
790 790 if not collapse:
791 791 seen = set([target])
792 792 for old, new in sorted(state.items()):
793 793 if new != nullrev and new in seen:
794 794 skipped.add(old)
795 795 seen.add(new)
796 796 repo.ui.debug('computed skipped revs: %s\n' %
797 797 (' '.join(str(r) for r in sorted(skipped)) or None))
798 798 repo.ui.debug('rebase status resumed\n')
799 799 return (originalwd, target, state, skipped,
800 800 collapse, keep, keepbranches, external, activebookmark)
801 801 except IOError, err:
802 802 if err.errno != errno.ENOENT:
803 803 raise
804 804 raise util.Abort(_('no rebase in progress'))
805 805
806 806 def inrebase(repo, originalwd, state):
807 807 '''check whether the working dir is in an interrupted rebase'''
808 808 parents = [p.rev() for p in repo.parents()]
809 809 if originalwd in parents:
810 810 return True
811 811
812 812 for newrev in state.itervalues():
813 813 if newrev in parents:
814 814 return True
815 815
816 816 return False
817 817
818 818 def abort(repo, originalwd, target, state):
819 819 'Restore the repository to its original state'
820 dstates = [s for s in state.values() if s > nullrev]
820 dstates = [s for s in state.values() if s >= 0]
821 821 immutable = [d for d in dstates if not repo[d].mutable()]
822 822 cleanup = True
823 823 if immutable:
824 824 repo.ui.warn(_("warning: can't clean up immutable changesets %s\n")
825 825 % ', '.join(str(repo[r]) for r in immutable),
826 826 hint=_('see hg help phases for details'))
827 827 cleanup = False
828 828
829 829 descendants = set()
830 830 if dstates:
831 831 descendants = set(repo.changelog.descendants(dstates))
832 832 if descendants - set(dstates):
833 833 repo.ui.warn(_("warning: new changesets detected on target branch, "
834 834 "can't strip\n"))
835 835 cleanup = False
836 836
837 837 if cleanup:
838 838 # Update away from the rebase if necessary
839 839 if inrebase(repo, originalwd, state):
840 840 merge.update(repo, originalwd, False, True, False)
841 841
842 842 # Strip from the first rebased revision
843 rebased = filter(lambda x: x > -1 and x != target, state.values())
843 rebased = filter(lambda x: x >= 0 and x != target, state.values())
844 844 if rebased:
845 845 strippoints = [c.node() for c in repo.set('roots(%ld)', rebased)]
846 846 # no backup of rebased cset versions needed
847 847 repair.strip(repo.ui, repo, strippoints)
848 848
849 849 clearstatus(repo)
850 850 repo.ui.warn(_('rebase aborted\n'))
851 851 return 0
852 852
853 853 def buildstate(repo, dest, rebaseset, collapse):
854 854 '''Define which revisions are going to be rebased and where
855 855
856 856 repo: repo
857 857 dest: context
858 858 rebaseset: set of rev
859 859 '''
860 860
861 861 # This check isn't strictly necessary, since mq detects commits over an
862 862 # applied patch. But it prevents messing up the working directory when
863 863 # a partially completed rebase is blocked by mq.
864 864 if 'qtip' in repo.tags() and (dest.node() in
865 865 [s.node for s in repo.mq.applied]):
866 866 raise util.Abort(_('cannot rebase onto an applied mq patch'))
867 867
868 868 roots = list(repo.set('roots(%ld)', rebaseset))
869 869 if not roots:
870 870 raise util.Abort(_('no matching revisions'))
871 871 roots.sort()
872 872 state = {}
873 873 detachset = set()
874 874 for root in roots:
875 875 commonbase = root.ancestor(dest)
876 876 if commonbase == root:
877 877 raise util.Abort(_('source is ancestor of destination'))
878 878 if commonbase == dest:
879 879 samebranch = root.branch() == dest.branch()
880 880 if not collapse and samebranch and root in dest.children():
881 881 repo.ui.debug('source is a child of destination\n')
882 882 return None
883 883
884 884 repo.ui.debug('rebase onto %d starting from %s\n' % (dest, root))
885 885 state.update(dict.fromkeys(rebaseset, nullrev))
886 886 # Rebase tries to turn <dest> into a parent of <root> while
887 887 # preserving the number of parents of rebased changesets:
888 888 #
889 889 # - A changeset with a single parent will always be rebased as a
890 890 # changeset with a single parent.
891 891 #
892 892 # - A merge will be rebased as merge unless its parents are both
893 893 # ancestors of <dest> or are themselves in the rebased set and
894 894 # pruned while rebased.
895 895 #
896 896 # If one parent of <root> is an ancestor of <dest>, the rebased
897 897 # version of this parent will be <dest>. This is always true with
898 898 # --base option.
899 899 #
900 900 # Otherwise, we need to *replace* the original parents with
901 901 # <dest>. This "detaches" the rebased set from its former location
902 902 # and rebases it onto <dest>. Changes introduced by ancestors of
903 903 # <root> not common with <dest> (the detachset, marked as
904 904 # nullmerge) are "removed" from the rebased changesets.
905 905 #
906 906 # - If <root> has a single parent, set it to <dest>.
907 907 #
908 908 # - If <root> is a merge, we cannot decide which parent to
909 909 # replace, the rebase operation is not clearly defined.
910 910 #
911 911 # The table below sums up this behavior:
912 912 #
913 913 # +------------------+----------------------+-------------------------+
914 914 # | | one parent | merge |
915 915 # +------------------+----------------------+-------------------------+
916 916 # | parent in | new parent is <dest> | parents in ::<dest> are |
917 917 # | ::<dest> | | remapped to <dest> |
918 918 # +------------------+----------------------+-------------------------+
919 919 # | unrelated source | new parent is <dest> | ambiguous, abort |
920 920 # +------------------+----------------------+-------------------------+
921 921 #
922 922 # The actual abort is handled by `defineparents`
923 923 if len(root.parents()) <= 1:
924 924 # ancestors of <root> not ancestors of <dest>
925 925 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
926 926 [root.rev()]))
927 927 for r in detachset:
928 928 if r not in state:
929 929 state[r] = nullmerge
930 930 if len(roots) > 1:
931 931 # If we have multiple roots, we may have "hole" in the rebase set.
932 932 # Rebase roots that descend from those "hole" should not be detached as
933 933 # other root are. We use the special `revignored` to inform rebase that
934 934 # the revision should be ignored but that `defineparents` should search
935 935 # a rebase destination that make sense regarding rebased topology.
936 936 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
937 937 for ignored in set(rebasedomain) - set(rebaseset):
938 938 state[ignored] = revignored
939 939 return repo['.'].rev(), dest.rev(), state
940 940
941 941 def clearrebased(ui, repo, state, skipped, collapsedas=None):
942 942 """dispose of rebased revision at the end of the rebase
943 943
944 944 If `collapsedas` is not None, the rebase was a collapse whose result if the
945 945 `collapsedas` node."""
946 946 if obsolete.isenabled(repo, obsolete.createmarkersopt):
947 947 markers = []
948 948 for rev, newrev in sorted(state.items()):
949 949 if newrev >= 0:
950 950 if rev in skipped:
951 951 succs = ()
952 952 elif collapsedas is not None:
953 953 succs = (repo[collapsedas],)
954 954 else:
955 955 succs = (repo[newrev],)
956 956 markers.append((repo[rev], succs))
957 957 if markers:
958 958 obsolete.createmarkers(repo, markers)
959 959 else:
960 960 rebased = [rev for rev in state if state[rev] > nullmerge]
961 961 if rebased:
962 962 stripped = []
963 963 for root in repo.set('roots(%ld)', rebased):
964 964 if set(repo.changelog.descendants([root.rev()])) - set(state):
965 965 ui.warn(_("warning: new changesets detected "
966 966 "on source branch, not stripping\n"))
967 967 else:
968 968 stripped.append(root.node())
969 969 if stripped:
970 970 # backup the old csets by default
971 971 repair.strip(ui, repo, stripped, "all")
972 972
973 973
974 974 def pullrebase(orig, ui, repo, *args, **opts):
975 975 'Call rebase after pull if the latter has been invoked with --rebase'
976 976 if opts.get('rebase'):
977 977 if opts.get('update'):
978 978 del opts['update']
979 979 ui.debug('--update and --rebase are not compatible, ignoring '
980 980 'the update flag\n')
981 981
982 982 movemarkfrom = repo['.'].node()
983 983 revsprepull = len(repo)
984 984 origpostincoming = commands.postincoming
985 985 def _dummy(*args, **kwargs):
986 986 pass
987 987 commands.postincoming = _dummy
988 988 try:
989 989 orig(ui, repo, *args, **opts)
990 990 finally:
991 991 commands.postincoming = origpostincoming
992 992 revspostpull = len(repo)
993 993 if revspostpull > revsprepull:
994 994 # --rev option from pull conflict with rebase own --rev
995 995 # dropping it
996 996 if 'rev' in opts:
997 997 del opts['rev']
998 998 rebase(ui, repo, **opts)
999 999 branch = repo[None].branch()
1000 1000 dest = repo[branch].rev()
1001 1001 if dest != repo['.'].rev():
1002 1002 # there was nothing to rebase we force an update
1003 1003 hg.update(repo, dest)
1004 1004 if bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
1005 1005 ui.status(_("updating bookmark %s\n")
1006 1006 % repo._bookmarkcurrent)
1007 1007 else:
1008 1008 if opts.get('tool'):
1009 1009 raise util.Abort(_('--tool can only be used with --rebase'))
1010 1010 orig(ui, repo, *args, **opts)
1011 1011
1012 1012 def summaryhook(ui, repo):
1013 1013 if not os.path.exists(repo.join('rebasestate')):
1014 1014 return
1015 1015 try:
1016 1016 state = restorestatus(repo)[2]
1017 1017 except error.RepoLookupError:
1018 1018 # i18n: column positioning for "hg summary"
1019 1019 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1020 1020 ui.write(msg)
1021 1021 return
1022 numrebased = len([i for i in state.itervalues() if i != -1])
1022 numrebased = len([i for i in state.itervalues() if i >= 0])
1023 1023 # i18n: column positioning for "hg summary"
1024 1024 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1025 1025 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1026 1026 ui.label(_('%d remaining'), 'rebase.remaining') %
1027 1027 (len(state) - numrebased)))
1028 1028
1029 1029 def uisetup(ui):
1030 1030 'Replace pull with a decorator to provide --rebase option'
1031 1031 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1032 1032 entry[1].append(('', 'rebase', None,
1033 1033 _("rebase working directory to branch head")))
1034 1034 entry[1].append(('t', 'tool', '',
1035 1035 _("specify merge tool for rebase")))
1036 1036 cmdutil.summaryhooks.add('rebase', summaryhook)
1037 1037 cmdutil.unfinishedstates.append(
1038 1038 ['rebasestate', False, False, _('rebase in progress'),
1039 1039 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
General Comments 0
You need to be logged in to leave comments. Login now