19 from mercurial.commands import templateopts |
19 from mercurial.commands import templateopts |
20 from mercurial.node import nullrev |
20 from mercurial.node import nullrev |
21 from mercurial.lock import release |
21 from mercurial.lock import release |
22 from mercurial.i18n import _ |
22 from mercurial.i18n import _ |
23 import os, errno |
23 import os, errno |
|
24 |
|
25 nullmerge = -2 |
24 |
26 |
25 def rebase(ui, repo, **opts): |
27 def rebase(ui, repo, **opts): |
26 """move changeset (and descendants) to a different branch |
28 """move changeset (and descendants) to a different branch |
27 |
29 |
28 Rebase uses repeated merging to graft changesets from one part of |
30 Rebase uses repeated merging to graft changesets from one part of |
51 abortf = opts.get('abort') |
53 abortf = opts.get('abort') |
52 collapsef = opts.get('collapse', False) |
54 collapsef = opts.get('collapse', False) |
53 extrafn = opts.get('extrafn') |
55 extrafn = opts.get('extrafn') |
54 keepf = opts.get('keep', False) |
56 keepf = opts.get('keep', False) |
55 keepbranchesf = opts.get('keepbranches', False) |
57 keepbranchesf = opts.get('keepbranches', False) |
|
58 detachf = opts.get('detach', False) |
56 |
59 |
57 if contf or abortf: |
60 if contf or abortf: |
58 if contf and abortf: |
61 if contf and abortf: |
59 raise error.ParseError('rebase', |
62 raise error.ParseError('rebase', |
60 _('cannot use both abort and continue')) |
63 _('cannot use both abort and continue')) |
61 if collapsef: |
64 if collapsef: |
62 raise error.ParseError( |
65 raise error.ParseError( |
63 'rebase', _('cannot use collapse with continue or abort')) |
66 'rebase', _('cannot use collapse with continue or abort')) |
|
67 |
|
68 if detachf: |
|
69 raise error.ParseError( |
|
70 'rebase', _('cannot use detach with continue or abort')) |
64 |
71 |
65 if srcf or basef or destf: |
72 if srcf or basef or destf: |
66 raise error.ParseError('rebase', |
73 raise error.ParseError('rebase', |
67 _('abort and continue do not allow specifying revisions')) |
74 _('abort and continue do not allow specifying revisions')) |
68 |
75 |
73 return |
80 return |
74 else: |
81 else: |
75 if srcf and basef: |
82 if srcf and basef: |
76 raise error.ParseError('rebase', _('cannot specify both a ' |
83 raise error.ParseError('rebase', _('cannot specify both a ' |
77 'revision and a base')) |
84 'revision and a base')) |
|
85 if detachf: |
|
86 if not srcf: |
|
87 raise error.ParseError( |
|
88 'rebase', _('detach requires a revision to be specified')) |
|
89 if basef: |
|
90 raise error.ParseError( |
|
91 'rebase', _('cannot specify a base with detach')) |
|
92 |
78 cmdutil.bail_if_changed(repo) |
93 cmdutil.bail_if_changed(repo) |
79 result = buildstate(repo, destf, srcf, basef) |
94 result = buildstate(repo, destf, srcf, basef, detachf) |
80 if not result: |
95 if not result: |
81 # Empty state built, nothing to rebase |
96 # Empty state built, nothing to rebase |
82 ui.status(_('nothing to rebase\n')) |
97 ui.status(_('nothing to rebase\n')) |
83 return |
98 return |
84 else: |
99 else: |
138 if collapsef: |
153 if collapsef: |
139 p1, p2 = defineparents(repo, min(state), target, |
154 p1, p2 = defineparents(repo, min(state), target, |
140 state, targetancestors) |
155 state, targetancestors) |
141 commitmsg = 'Collapsed revision' |
156 commitmsg = 'Collapsed revision' |
142 for rebased in state: |
157 for rebased in state: |
143 if rebased not in skipped: |
158 if rebased not in skipped and state[rebased] != nullmerge: |
144 commitmsg += '\n* %s' % repo[rebased].description() |
159 commitmsg += '\n* %s' % repo[rebased].description() |
145 commitmsg = ui.edit(commitmsg, repo.ui.username()) |
160 commitmsg = ui.edit(commitmsg, repo.ui.username()) |
146 concludenode(repo, rev, p1, external, commitmsg=commitmsg, |
161 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, |
147 extra=extrafn) |
162 extra=extrafn) |
148 |
163 |
149 if 'qtip' in repo.tags(): |
164 if 'qtip' in repo.tags(): |
150 updatemq(repo, state, skipped, **opts) |
165 updatemq(repo, state, skipped, **opts) |
151 |
166 |
152 if not keepf: |
167 if not keepf: |
153 # Remove no more useful revisions |
168 # Remove no more useful revisions |
154 if set(repo.changelog.descendants(min(state))) - set(state): |
169 rebased = [rev for rev in state if state[rev] != nullmerge] |
155 ui.warn(_("warning: new changesets detected on source branch, " |
170 if rebased: |
156 "not stripping\n")) |
171 if set(repo.changelog.descendants(min(rebased))) - set(state): |
157 else: |
172 ui.warn(_("warning: new changesets detected on source branch, " |
158 repair.strip(ui, repo, repo[min(state)].node(), "strip") |
173 "not stripping\n")) |
|
174 else: |
|
175 repair.strip(ui, repo, repo[min(rebased)].node(), "strip") |
159 |
176 |
160 clearstatus(repo) |
177 clearstatus(repo) |
161 ui.status(_("rebase completed\n")) |
178 ui.status(_("rebase completed\n")) |
162 if os.path.exists(repo.sjoin('undo')): |
179 if os.path.exists(repo.sjoin('undo')): |
163 util.unlink(repo.sjoin('undo')) |
180 util.unlink(repo.sjoin('undo')) |
377 strippoint = min(rebased) |
397 strippoint = min(rebased) |
378 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip") |
398 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip") |
379 clearstatus(repo) |
399 clearstatus(repo) |
380 repo.ui.status(_('rebase aborted\n')) |
400 repo.ui.status(_('rebase aborted\n')) |
381 |
401 |
382 def buildstate(repo, dest, src, base): |
402 def buildstate(repo, dest, src, base, detach): |
383 'Define which revisions are going to be rebased and where' |
403 'Define which revisions are going to be rebased and where' |
384 targetancestors = set() |
404 targetancestors = set() |
|
405 detachset = set() |
385 |
406 |
386 if not dest: |
407 if not dest: |
387 # Destination defaults to the latest revision in the current branch |
408 # Destination defaults to the latest revision in the current branch |
388 branch = repo[None].branch() |
409 branch = repo[None].branch() |
389 dest = repo[branch].rev() |
410 dest = repo[branch].rev() |
398 if commonbase == repo[src]: |
419 if commonbase == repo[src]: |
399 raise util.Abort(_('source is ancestor of destination')) |
420 raise util.Abort(_('source is ancestor of destination')) |
400 if commonbase == repo[dest]: |
421 if commonbase == repo[dest]: |
401 raise util.Abort(_('source is descendant of destination')) |
422 raise util.Abort(_('source is descendant of destination')) |
402 source = repo[src].rev() |
423 source = repo[src].rev() |
|
424 if detach: |
|
425 # We need to keep track of source's ancestors up to the common base |
|
426 srcancestors = set(repo.changelog.ancestors(source)) |
|
427 baseancestors = set(repo.changelog.ancestors(commonbase.rev())) |
|
428 detachset = srcancestors - baseancestors |
|
429 detachset.remove(commonbase.rev()) |
403 else: |
430 else: |
404 if base: |
431 if base: |
405 cwd = repo[base].rev() |
432 cwd = repo[base].rev() |
406 else: |
433 else: |
407 cwd = repo['.'].rev() |
434 cwd = repo['.'].rev() |
424 rebasingbranch = cwdancestors - targetancestors |
451 rebasingbranch = cwdancestors - targetancestors |
425 source = min(rebasingbranch) |
452 source = min(rebasingbranch) |
426 |
453 |
427 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source)) |
454 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source)) |
428 state = dict.fromkeys(repo.changelog.descendants(source), nullrev) |
455 state = dict.fromkeys(repo.changelog.descendants(source), nullrev) |
|
456 state.update(dict.fromkeys(detachset, nullmerge)) |
429 state[source] = nullrev |
457 state[source] = nullrev |
430 return repo['.'].rev(), repo[dest].rev(), state |
458 return repo['.'].rev(), repo[dest].rev(), state |
431 |
459 |
432 def pullrebase(orig, ui, repo, *args, **opts): |
460 def pullrebase(orig, ui, repo, *args, **opts): |
433 'Call rebase after pull if the latter has been invoked with --rebase' |
461 'Call rebase after pull if the latter has been invoked with --rebase' |
466 ('b', 'base', '', _('rebase from the base of a given revision')), |
494 ('b', 'base', '', _('rebase from the base of a given revision')), |
467 ('d', 'dest', '', _('rebase onto a given revision')), |
495 ('d', 'dest', '', _('rebase onto a given revision')), |
468 ('', 'collapse', False, _('collapse the rebased changesets')), |
496 ('', 'collapse', False, _('collapse the rebased changesets')), |
469 ('', 'keep', False, _('keep original changesets')), |
497 ('', 'keep', False, _('keep original changesets')), |
470 ('', 'keepbranches', False, _('keep original branch names')), |
498 ('', 'keepbranches', False, _('keep original branch names')), |
|
499 ('', 'detach', False, _('force detaching of source from its original ' |
|
500 'branch')), |
471 ('c', 'continue', False, _('continue an interrupted rebase')), |
501 ('c', 'continue', False, _('continue an interrupted rebase')), |
472 ('a', 'abort', False, _('abort an interrupted rebase')),] + |
502 ('a', 'abort', False, _('abort an interrupted rebase')),] + |
473 templateopts, |
503 templateopts, |
474 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] ' |
504 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--detach] ' |
475 '[--keepbranches] | [-c] | [-a]')), |
505 '[--keep] [--keepbranches] | [-c] | [-a]')), |
476 } |
506 } |