Mercurial > hg
comparison hgext/rebase.py @ 34005:5e83a8fe6bc4
rebase: initial support for multiple destinations
This patch defines `SRC` (a single source revision) and `ALLSRC` (all source
revisions) to be valid names in `--dest` revset if `--src` or `--rev` is
used. So destination could be defined differently according to source
revisions. The names are capitalized to make it clear they are "dynamically
defined", distinguishable from normal revsets (Thanks Augie for the
suggestion).
This is useful, for example, `-r 'orphan()' -d 'calc-dest(SRC)'` to solve
instability, which seems to be a highly wanted feature.
The feature is not completed, namely if `-d` overlaps with `-r`, things
could go wrong. A later patch will handle that case.
The feature is also gated by `experimental.rebase.multidest` config option
which is default off.
Differential Revision: https://phab.mercurial-scm.org/D469
author | Jun Wu <quark@fb.com> |
---|---|
date | Tue, 29 Aug 2017 17:27:37 -0700 |
parents | af609bb3487f |
children | 32528419db64 |
comparison
equal
deleted
inserted
replaced
34004:af609bb3487f | 34005:5e83a8fe6bc4 |
---|---|
44 phases, | 44 phases, |
45 registrar, | 45 registrar, |
46 repair, | 46 repair, |
47 repoview, | 47 repoview, |
48 revset, | 48 revset, |
49 revsetlang, | |
49 scmutil, | 50 scmutil, |
50 smartset, | 51 smartset, |
51 util, | 52 util, |
52 ) | 53 ) |
53 | 54 |
734 | 735 |
735 if ui.configbool('commands', 'rebase.requiredest') and not destf: | 736 if ui.configbool('commands', 'rebase.requiredest') and not destf: |
736 raise error.Abort(_('you must specify a destination'), | 737 raise error.Abort(_('you must specify a destination'), |
737 hint=_('use: hg rebase -d REV')) | 738 hint=_('use: hg rebase -d REV')) |
738 | 739 |
739 if destf: | 740 dest = None |
740 dest = scmutil.revsingle(repo, destf) | |
741 | 741 |
742 if revf: | 742 if revf: |
743 rebaseset = scmutil.revrange(repo, revf) | 743 rebaseset = scmutil.revrange(repo, revf) |
744 if not rebaseset: | 744 if not rebaseset: |
745 ui.status(_('empty "rev" revision set - nothing to rebase\n')) | 745 ui.status(_('empty "rev" revision set - nothing to rebase\n')) |
755 base = scmutil.revrange(repo, [basef or '.']) | 755 base = scmutil.revrange(repo, [basef or '.']) |
756 if not base: | 756 if not base: |
757 ui.status(_('empty "base" revision set - ' | 757 ui.status(_('empty "base" revision set - ' |
758 "can't compute rebase set\n")) | 758 "can't compute rebase set\n")) |
759 return None | 759 return None |
760 if not destf: | 760 if destf: |
761 # --base does not support multiple destinations | |
762 dest = scmutil.revsingle(repo, destf) | |
763 else: | |
761 dest = repo[_destrebase(repo, base, destspace=destspace)] | 764 dest = repo[_destrebase(repo, base, destspace=destspace)] |
762 destf = str(dest) | 765 destf = str(dest) |
763 | 766 |
764 roots = [] # selected children of branching points | 767 roots = [] # selected children of branching points |
765 bpbase = {} # {branchingpoint: [origbase]} | 768 bpbase = {} # {branchingpoint: [origbase]} |
804 | 807 |
805 if not destf: | 808 if not destf: |
806 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)] | 809 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)] |
807 destf = str(dest) | 810 destf = str(dest) |
808 | 811 |
809 # assign dest to each rev in rebaseset | 812 allsrc = revsetlang.formatspec('%ld', rebaseset) |
810 destrev = dest.rev() | 813 alias = {'ALLSRC': allsrc} |
811 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev} | 814 |
815 if dest is None: | |
816 try: | |
817 # fast path: try to resolve dest without SRC alias | |
818 dest = scmutil.revsingle(repo, destf, localalias=alias) | |
819 except error.RepoLookupError: | |
820 if not ui.configbool('experimental', 'rebase.multidest'): | |
821 raise | |
822 # multi-dest path: resolve dest for each SRC separately | |
823 destmap = {} | |
824 for r in rebaseset: | |
825 alias['SRC'] = revsetlang.formatspec('%d', r) | |
826 # use repo.anyrevs instead of scmutil.revsingle because we | |
827 # don't want to abort if destset is empty. | |
828 destset = repo.anyrevs([destf], user=True, localalias=alias) | |
829 size = len(destset) | |
830 if size == 1: | |
831 destmap[r] = destset.first() | |
832 elif size == 0: | |
833 ui.note(_('skipping %s - empty destination\n') % repo[r]) | |
834 else: | |
835 raise error.Abort(_('rebase destination for %s is not ' | |
836 'unique') % repo[r]) | |
837 | |
838 if dest is not None: | |
839 # single-dest case: assign dest to each rev in rebaseset | |
840 destrev = dest.rev() | |
841 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev} | |
842 | |
843 if not destmap: | |
844 ui.status(_('nothing to rebase - empty destination\n')) | |
845 return None | |
812 | 846 |
813 return destmap | 847 return destmap |
814 | 848 |
815 def externalparent(repo, state, destancestors): | 849 def externalparent(repo, state, destancestors): |
816 """Return the revision that should be used as the second parent | 850 """Return the revision that should be used as the second parent |
901 | 935 |
902 rev is what is being rebased. Return a list of two revs, which are the | 936 rev is what is being rebased. Return a list of two revs, which are the |
903 adjusted destinations for rev's p1 and p2, respectively. If a parent is | 937 adjusted destinations for rev's p1 and p2, respectively. If a parent is |
904 nullrev, return dest without adjustment for it. | 938 nullrev, return dest without adjustment for it. |
905 | 939 |
906 For example, when doing rebase -r B+E -d F, rebase will first move B to B1, | 940 For example, when doing rebasing B+E to F, C to G, rebase will first move B |
907 and E's destination will be adjusted from F to B1. | 941 to B1, and E's destination will be adjusted from F to B1. |
908 | 942 |
909 B1 <- written during rebasing B | 943 B1 <- written during rebasing B |
910 | | 944 | |
911 F <- original destination of B, E | 945 F <- original destination of B, E |
912 | | 946 | |
914 | | | 948 | | |
915 | D <- prev, one parent of rev being checked | 949 | D <- prev, one parent of rev being checked |
916 | | | 950 | | |
917 | x <- skipped, ex. no successor or successor in (::dest) | 951 | x <- skipped, ex. no successor or successor in (::dest) |
918 | | | 952 | | |
919 | C | 953 | C <- rebased as C', different destination |
920 | | | 954 | | |
921 | B <- rebased as B1 | 955 | B <- rebased as B1 C' |
922 |/ | 956 |/ | |
923 A | 957 A G <- destination of C, different |
924 | 958 |
925 Another example about merge changeset, rebase -r C+G+H -d K, rebase will | 959 Another example about merge changeset, rebase -r C+G+H -d K, rebase will |
926 first move C to C1, G to G1, and when it's checking H, the adjusted | 960 first move C to C1, G to G1, and when it's checking H, the adjusted |
927 destinations will be [C1, G1]. | 961 destinations will be [C1, G1]. |
928 | 962 |