46 ('e', 'edit', False, _('invoke editor on commit messages')), |
46 ('e', 'edit', False, _('invoke editor on commit messages')), |
47 ('l', 'logfile', '', |
47 ('l', 'logfile', '', |
48 _('read collapse commit message from file'), _('FILE')), |
48 _('read collapse commit message from file'), _('FILE')), |
49 ('', 'keep', False, _('keep original changesets')), |
49 ('', 'keep', False, _('keep original changesets')), |
50 ('', 'keepbranches', False, _('keep original branch names')), |
50 ('', 'keepbranches', False, _('keep original branch names')), |
51 ('D', 'detach', False, _('force detaching of source from its original ' |
51 ('D', 'detach', False, _('(DEPRECATED)')), |
52 'branch')), |
|
53 ('t', 'tool', '', _('specify merge tool')), |
52 ('t', 'tool', '', _('specify merge tool')), |
54 ('c', 'continue', False, _('continue an interrupted rebase')), |
53 ('c', 'continue', False, _('continue an interrupted rebase')), |
55 ('a', 'abort', False, _('abort an interrupted rebase'))] + |
54 ('a', 'abort', False, _('abort an interrupted rebase'))] + |
56 templateopts, |
55 templateopts, |
57 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n' |
56 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n' |
129 collapsef = opts.get('collapse', False) |
128 collapsef = opts.get('collapse', False) |
130 collapsemsg = cmdutil.logmessage(ui, opts) |
129 collapsemsg = cmdutil.logmessage(ui, opts) |
131 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion |
130 extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion |
132 keepf = opts.get('keep', False) |
131 keepf = opts.get('keep', False) |
133 keepbranchesf = opts.get('keepbranches', False) |
132 keepbranchesf = opts.get('keepbranches', False) |
134 detachf = opts.get('detach', False) |
|
135 # keepopen is not meant for use on the command line, but by |
133 # keepopen is not meant for use on the command line, but by |
136 # other extensions |
134 # other extensions |
137 keepopen = opts.get('keepopen', False) |
135 keepopen = opts.get('keepopen', False) |
138 |
136 |
139 if collapsemsg and not collapsef: |
137 if collapsemsg and not collapsef: |
166 raise util.Abort(_('cannot specify both a ' |
162 raise util.Abort(_('cannot specify both a ' |
167 'revision and a base')) |
163 'revision and a base')) |
168 if revf and srcf: |
164 if revf and srcf: |
169 raise util.Abort(_('cannot specify both a ' |
165 raise util.Abort(_('cannot specify both a ' |
170 'revision and a source')) |
166 'revision and a source')) |
171 if detachf: |
|
172 if not (srcf or revf): |
|
173 raise util.Abort( |
|
174 _('detach requires a revision to be specified')) |
|
175 if basef: |
|
176 raise util.Abort(_('cannot specify a base with detach')) |
|
177 |
167 |
178 cmdutil.bailifchanged(repo) |
168 cmdutil.bailifchanged(repo) |
179 |
169 |
180 if not destf: |
170 if not destf: |
181 # Destination defaults to the latest revision in the |
171 # Destination defaults to the latest revision in the |
213 elif not keepf and not repo[root].mutable(): |
203 elif not keepf and not repo[root].mutable(): |
214 raise util.Abort(_("can't rebase immutable changeset %s") |
204 raise util.Abort(_("can't rebase immutable changeset %s") |
215 % repo[root], |
205 % repo[root], |
216 hint=_('see hg help phases for details')) |
206 hint=_('see hg help phases for details')) |
217 else: |
207 else: |
218 result = buildstate(repo, dest, rebaseset, detachf, collapsef) |
208 result = buildstate(repo, dest, rebaseset, collapsef) |
219 |
209 |
220 if not result: |
210 if not result: |
221 # Empty state built, nothing to rebase |
211 # Empty state built, nothing to rebase |
222 ui.status(_('nothing to rebase\n')) |
212 ui.status(_('nothing to rebase\n')) |
223 return 1 |
213 return 1 |
590 repair.strip(repo.ui, repo, repo[strippoint].node()) |
580 repair.strip(repo.ui, repo, repo[strippoint].node()) |
591 clearstatus(repo) |
581 clearstatus(repo) |
592 repo.ui.warn(_('rebase aborted\n')) |
582 repo.ui.warn(_('rebase aborted\n')) |
593 return 0 |
583 return 0 |
594 |
584 |
595 def buildstate(repo, dest, rebaseset, detach, collapse): |
585 def buildstate(repo, dest, rebaseset, collapse): |
596 '''Define which revisions are going to be rebased and where |
586 '''Define which revisions are going to be rebased and where |
597 |
587 |
598 repo: repo |
588 repo: repo |
599 dest: context |
589 dest: context |
600 rebaseset: set of rev |
590 rebaseset: set of rev |
601 detach: boolean''' |
591 ''' |
602 |
592 |
603 # This check isn't strictly necessary, since mq detects commits over an |
593 # This check isn't strictly necessary, since mq detects commits over an |
604 # applied patch. But it prevents messing up the working directory when |
594 # applied patch. But it prevents messing up the working directory when |
605 # a partially completed rebase is blocked by mq. |
595 # a partially completed rebase is blocked by mq. |
606 if 'qtip' in repo.tags() and (dest.node() in |
596 if 'qtip' in repo.tags() and (dest.node() in |
607 [s.node for s in repo.mq.applied]): |
597 [s.node for s in repo.mq.applied]): |
608 raise util.Abort(_('cannot rebase onto an applied mq patch')) |
598 raise util.Abort(_('cannot rebase onto an applied mq patch')) |
609 |
599 |
610 detachset = set() |
|
611 roots = list(repo.set('roots(%ld)', rebaseset)) |
600 roots = list(repo.set('roots(%ld)', rebaseset)) |
612 if not roots: |
601 if not roots: |
613 raise util.Abort(_('no matching revisions')) |
602 raise util.Abort(_('no matching revisions')) |
614 if len(roots) > 1: |
603 if len(roots) > 1: |
615 raise util.Abort(_("can't rebase multiple roots")) |
604 raise util.Abort(_("can't rebase multiple roots")) |
621 if commonbase == dest: |
610 if commonbase == dest: |
622 samebranch = root.branch() == dest.branch() |
611 samebranch = root.branch() == dest.branch() |
623 if not collapse and samebranch and root in dest.children(): |
612 if not collapse and samebranch and root in dest.children(): |
624 repo.ui.debug('source is a child of destination\n') |
613 repo.ui.debug('source is a child of destination\n') |
625 return None |
614 return None |
626 # rebase on ancestor, force detach |
|
627 detach = True |
|
628 if detach: |
|
629 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root) |
|
630 |
615 |
631 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root)) |
616 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root)) |
632 state = dict.fromkeys(rebaseset, nullrev) |
617 state = dict.fromkeys(rebaseset, nullrev) |
633 state.update(dict.fromkeys(detachset, nullmerge)) |
618 # Rebase tries to turn <dest> into a parent of <root> while |
|
619 # preserving the number of parents of rebased changesets: |
|
620 # |
|
621 # - A changeset with a single parent will always be rebased as a |
|
622 # changeset with a single parent. |
|
623 # |
|
624 # - A merge will be rebased as merge unless its parents are both |
|
625 # ancestors of <dest> or are themselves in the rebased set and |
|
626 # pruned while rebased. |
|
627 # |
|
628 # If one parent of <root> is an ancestor of <dest>, the rebased |
|
629 # version of this parent will be <dest>. This is always true with |
|
630 # --base option. |
|
631 # |
|
632 # Otherwise, we need to *replace* the original parents with |
|
633 # <dest>. This "detaches" the rebased set from its former location |
|
634 # and rebases it onto <dest>. Changes introduced by ancestors of |
|
635 # <root> not common with <dest> (the detachset, marked as |
|
636 # nullmerge) are "removed" from the rebased changesets. |
|
637 # |
|
638 # - If <root> has a single parent, set it to <dest>. |
|
639 # |
|
640 # - If <root> is a merge, we cannot decide which parent to |
|
641 # replace, the rebase operation is not clearly defined. |
|
642 # |
|
643 # The table below sums up this behavior: |
|
644 # |
|
645 # +--------------------+----------------------+-------------------------+ |
|
646 # | | one parent | merge | |
|
647 # +--------------------+----------------------+-------------------------+ |
|
648 # | parent in ::<dest> | new parent is <dest> | parents in ::<dest> are | |
|
649 # | | | remapped to <dest> | |
|
650 # +--------------------+----------------------+-------------------------+ |
|
651 # | unrelated source | new parent is <dest> | ambiguous, abort | |
|
652 # +--------------------+----------------------+-------------------------+ |
|
653 # |
|
654 # The actual abort is handled by `defineparents` |
|
655 if len(root.parents()) <= 1: |
|
656 # (strict) ancestors of <root> not ancestors of <dest> |
|
657 detachset = repo.revs('::%d - ::%d - %d', root, commonbase, root) |
|
658 state.update(dict.fromkeys(detachset, nullmerge)) |
634 return repo['.'].rev(), dest.rev(), state |
659 return repo['.'].rev(), dest.rev(), state |
635 |
660 |
636 def pullrebase(orig, ui, repo, *args, **opts): |
661 def pullrebase(orig, ui, repo, *args, **opts): |
637 'Call rebase after pull if the latter has been invoked with --rebase' |
662 'Call rebase after pull if the latter has been invoked with --rebase' |
638 if opts.get('rebase'): |
663 if opts.get('rebase'): |