Show More
@@ -0,0 +1,59 b'' | |||
|
1 | #!/bin/bash | |
|
2 | ||
|
3 | hg init test | |
|
4 | cd test | |
|
5 | cat >>afile <<EOF | |
|
6 | 0 | |
|
7 | EOF | |
|
8 | hg add afile | |
|
9 | hg commit -m "0.0" | |
|
10 | cat >>afile <<EOF | |
|
11 | 1 | |
|
12 | EOF | |
|
13 | hg commit -m "0.1" | |
|
14 | cat >>afile <<EOF | |
|
15 | 2 | |
|
16 | EOF | |
|
17 | hg commit -m "0.2" | |
|
18 | cat >>afile <<EOF | |
|
19 | 3 | |
|
20 | EOF | |
|
21 | hg commit -m "0.3" | |
|
22 | hg update -C 0 | |
|
23 | cat >>afile <<EOF | |
|
24 | 1 | |
|
25 | EOF | |
|
26 | hg commit -m "1.1" | |
|
27 | cat >>afile <<EOF | |
|
28 | 2 | |
|
29 | EOF | |
|
30 | hg commit -m "1.2" | |
|
31 | cat >fred <<EOF | |
|
32 | a line | |
|
33 | EOF | |
|
34 | cat >>afile <<EOF | |
|
35 | 3 | |
|
36 | EOF | |
|
37 | hg add fred | |
|
38 | hg commit -m "1.3" | |
|
39 | hg mv afile adifferentfile | |
|
40 | hg commit -m "1.3m" | |
|
41 | hg update -C 3 | |
|
42 | hg mv afile anotherfile | |
|
43 | hg commit -m "0.3m" | |
|
44 | hg debugindex .hg/data/afile.i | |
|
45 | hg debugindex .hg/data/adifferentfile.i | |
|
46 | hg debugindex .hg/data/anotherfile.i | |
|
47 | hg debugindex .hg/data/fred.i | |
|
48 | hg debugindex .hg/00manifest.i | |
|
49 | hg verify | |
|
50 | cd .. | |
|
51 | for i in 0 1 2 3 4 5 6 7 8; do | |
|
52 | hg clone -r "$i" test test-"$i" | |
|
53 | cd test-"$i" | |
|
54 | hg verify | |
|
55 | cd .. | |
|
56 | done | |
|
57 | cd test-8 | |
|
58 | hg pull ../test-7 | |
|
59 | hg verify |
@@ -0,0 +1,126 b'' | |||
|
1 | rev offset length base linkrev nodeid p1 p2 | |
|
2 | 0 0 3 0 0 362fef284ce2 000000000000 000000000000 | |
|
3 | 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000 | |
|
4 | 2 8 7 2 2 4c982badb186 125144f7e028 000000000000 | |
|
5 | 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000 | |
|
6 | rev offset length base linkrev nodeid p1 p2 | |
|
7 | 0 0 75 0 7 905359268f77 000000000000 000000000000 | |
|
8 | rev offset length base linkrev nodeid p1 p2 | |
|
9 | 0 0 75 0 8 905359268f77 000000000000 000000000000 | |
|
10 | rev offset length base linkrev nodeid p1 p2 | |
|
11 | 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000 | |
|
12 | rev offset length base linkrev nodeid p1 p2 | |
|
13 | 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000 | |
|
14 | 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000 | |
|
15 | 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000 | |
|
16 | 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000 | |
|
17 | 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000 | |
|
18 | 5 250 68 3 7 3b45cc2ab868 de68e904d169 000000000000 | |
|
19 | 6 318 54 6 8 24d86153a002 f54c32f13478 000000000000 | |
|
20 | checking changesets | |
|
21 | checking manifests | |
|
22 | crosschecking files in changesets and manifests | |
|
23 | checking files | |
|
24 | 4 files, 9 changesets, 7 total revisions | |
|
25 | requesting all changes | |
|
26 | adding changesets | |
|
27 | adding manifests | |
|
28 | adding file changes | |
|
29 | added 1 changesets with 1 changes to 1 files | |
|
30 | checking changesets | |
|
31 | checking manifests | |
|
32 | crosschecking files in changesets and manifests | |
|
33 | checking files | |
|
34 | 1 files, 1 changesets, 1 total revisions | |
|
35 | requesting all changes | |
|
36 | adding changesets | |
|
37 | adding manifests | |
|
38 | adding file changes | |
|
39 | added 2 changesets with 2 changes to 1 files | |
|
40 | checking changesets | |
|
41 | checking manifests | |
|
42 | crosschecking files in changesets and manifests | |
|
43 | checking files | |
|
44 | 1 files, 2 changesets, 2 total revisions | |
|
45 | requesting all changes | |
|
46 | adding changesets | |
|
47 | adding manifests | |
|
48 | adding file changes | |
|
49 | added 3 changesets with 3 changes to 1 files | |
|
50 | checking changesets | |
|
51 | checking manifests | |
|
52 | crosschecking files in changesets and manifests | |
|
53 | checking files | |
|
54 | 1 files, 3 changesets, 3 total revisions | |
|
55 | requesting all changes | |
|
56 | adding changesets | |
|
57 | adding manifests | |
|
58 | adding file changes | |
|
59 | added 4 changesets with 4 changes to 1 files | |
|
60 | checking changesets | |
|
61 | checking manifests | |
|
62 | crosschecking files in changesets and manifests | |
|
63 | checking files | |
|
64 | 1 files, 4 changesets, 4 total revisions | |
|
65 | requesting all changes | |
|
66 | adding changesets | |
|
67 | adding manifests | |
|
68 | adding file changes | |
|
69 | added 2 changesets with 2 changes to 1 files | |
|
70 | checking changesets | |
|
71 | checking manifests | |
|
72 | crosschecking files in changesets and manifests | |
|
73 | checking files | |
|
74 | 1 files, 2 changesets, 2 total revisions | |
|
75 | requesting all changes | |
|
76 | adding changesets | |
|
77 | adding manifests | |
|
78 | adding file changes | |
|
79 | added 3 changesets with 3 changes to 1 files | |
|
80 | checking changesets | |
|
81 | checking manifests | |
|
82 | crosschecking files in changesets and manifests | |
|
83 | checking files | |
|
84 | 1 files, 3 changesets, 3 total revisions | |
|
85 | requesting all changes | |
|
86 | adding changesets | |
|
87 | adding manifests | |
|
88 | adding file changes | |
|
89 | added 4 changesets with 5 changes to 2 files | |
|
90 | checking changesets | |
|
91 | checking manifests | |
|
92 | crosschecking files in changesets and manifests | |
|
93 | checking files | |
|
94 | 2 files, 4 changesets, 5 total revisions | |
|
95 | requesting all changes | |
|
96 | adding changesets | |
|
97 | adding manifests | |
|
98 | adding file changes | |
|
99 | added 5 changesets with 6 changes to 3 files | |
|
100 | checking changesets | |
|
101 | checking manifests | |
|
102 | crosschecking files in changesets and manifests | |
|
103 | checking files | |
|
104 | 3 files, 5 changesets, 6 total revisions | |
|
105 | requesting all changes | |
|
106 | adding changesets | |
|
107 | adding manifests | |
|
108 | adding file changes | |
|
109 | added 5 changesets with 5 changes to 2 files | |
|
110 | checking changesets | |
|
111 | checking manifests | |
|
112 | crosschecking files in changesets and manifests | |
|
113 | checking files | |
|
114 | 2 files, 5 changesets, 5 total revisions | |
|
115 | pulling from ../test-7 | |
|
116 | searching for changes | |
|
117 | adding changesets | |
|
118 | adding manifests | |
|
119 | adding file changes | |
|
120 | added 4 changesets with 2 changes to 3 files (+1 heads) | |
|
121 | (run 'hg update' to get a working copy) | |
|
122 | checking changesets | |
|
123 | checking manifests | |
|
124 | crosschecking files in changesets and manifests | |
|
125 | checking files | |
|
126 | 4 files, 9 changesets, 7 total revisions |
@@ -693,7 +693,7 b' def clone(ui, source, dest=None, **opts)' | |||
|
693 | 693 | copy = False |
|
694 | 694 | if other.dev() != -1: |
|
695 | 695 | abspath = os.path.abspath(source) |
|
696 | if not opts['pull']: | |
|
696 | if not opts['pull'] and not opts['rev']: | |
|
697 | 697 | copy = True |
|
698 | 698 | |
|
699 | 699 | if copy: |
@@ -723,8 +723,14 b' def clone(ui, source, dest=None, **opts)' | |||
|
723 | 723 | repo = hg.repository(ui, dest) |
|
724 | 724 | |
|
725 | 725 | else: |
|
726 | revs = None | |
|
727 | if opts['rev']: | |
|
728 | if not other.local(): | |
|
729 | raise util.Abort("clone -r not supported yet for remote repositories.") | |
|
730 | else: | |
|
731 | revs = [other.lookup(rev) for rev in opts['rev']] | |
|
726 | 732 | repo = hg.repository(ui, dest, create=1) |
|
727 | repo.pull(other) | |
|
733 | repo.pull(other, heads = revs) | |
|
728 | 734 | |
|
729 | 735 | f = repo.opener("hgrc", "w", text=True) |
|
730 | 736 | f.write("[paths]\n") |
@@ -1396,7 +1402,7 b' def incoming(ui, repo, source="default",' | |||
|
1396 | 1402 | o = repo.findincoming(other) |
|
1397 | 1403 | if not o: |
|
1398 | 1404 | return |
|
1399 |
o = other. |
|
|
1405 | o = other.changelog.nodesbetween(o)[0] | |
|
1400 | 1406 | if opts['newest_first']: |
|
1401 | 1407 | o.reverse() |
|
1402 | 1408 | for n in o: |
@@ -1561,7 +1567,7 b' def outgoing(ui, repo, dest="default-pus' | |||
|
1561 | 1567 | dest = ui.expandpath(dest, repo.root) |
|
1562 | 1568 | other = hg.repository(ui, dest) |
|
1563 | 1569 | o = repo.findoutgoing(other) |
|
1564 |
o = repo. |
|
|
1570 | o = repo.changelog.nodesbetween(o)[0] | |
|
1565 | 1571 | if opts['newest_first']: |
|
1566 | 1572 | o.reverse() |
|
1567 | 1573 | for n in o: |
@@ -1643,7 +1649,12 b' def pull(ui, repo, source="default", **o' | |||
|
1643 | 1649 | ui.setconfig("ui", "remotecmd", opts['remotecmd']) |
|
1644 | 1650 | |
|
1645 | 1651 | other = hg.repository(ui, source) |
|
1646 | r = repo.pull(other) | |
|
1652 | revs = None | |
|
1653 | if opts['rev'] and not other.local(): | |
|
1654 | raise util.Abort("pull -r doesn't work for remote repositories yet") | |
|
1655 | elif opts['rev']: | |
|
1656 | revs = [other.lookup(rev) for rev in opts['rev']] | |
|
1657 | r = repo.pull(other, heads=revs) | |
|
1647 | 1658 | if not r: |
|
1648 | 1659 | if opts['update']: |
|
1649 | 1660 | return update(ui, repo) |
@@ -2193,6 +2204,7 b' table = {' | |||
|
2193 | 2204 | [('U', 'noupdate', None, _('do not update the new working directory')), |
|
2194 | 2205 | ('e', 'ssh', "", _('specify ssh command to use')), |
|
2195 | 2206 | ('', 'pull', None, _('use pull protocol to copy metadata')), |
|
2207 | ('r', 'rev', [], _('a changeset you would like to have after cloning')), | |
|
2196 | 2208 | ('', 'remotecmd', "", _('specify hg command to run on the remote side'))], |
|
2197 | 2209 | _('hg clone [OPTION]... SOURCE [DEST]')), |
|
2198 | 2210 | "^commit|ci": |
@@ -2304,8 +2316,9 b' table = {' | |||
|
2304 | 2316 | (pull, |
|
2305 | 2317 | [('u', 'update', None, _('update the working directory to tip after pull')), |
|
2306 | 2318 | ('e', 'ssh', "", _('specify ssh command to use')), |
|
2319 | ('r', 'rev', [], _('a specific revision you would like to pull')), | |
|
2307 | 2320 | ('', 'remotecmd', "", _('specify hg command to run on the remote side'))], |
|
2308 | _('hg pull [-u] [-e FILE] [--remotecmd FILE] [SOURCE]')), | |
|
2321 | _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')), | |
|
2309 | 2322 | "^push": |
|
2310 | 2323 | (push, |
|
2311 | 2324 | [('f', 'force', None, _('force push')), |
@@ -727,32 +727,6 b' class localrepository:' | |||
|
727 | 727 | |
|
728 | 728 | return r |
|
729 | 729 | |
|
730 | def newer(self, nodes): | |
|
731 | m = {} | |
|
732 | nl = [] | |
|
733 | pm = {} | |
|
734 | cl = self.changelog | |
|
735 | t = l = cl.count() | |
|
736 | ||
|
737 | # find the lowest numbered node | |
|
738 | for n in nodes: | |
|
739 | l = min(l, cl.rev(n)) | |
|
740 | m[n] = 1 | |
|
741 | ||
|
742 | for i in xrange(l, t): | |
|
743 | n = cl.node(i) | |
|
744 | if n in m: # explicitly listed | |
|
745 | pm[n] = 1 | |
|
746 | nl.append(n) | |
|
747 | continue | |
|
748 | for p in cl.parents(n): | |
|
749 | if p in pm: # parent listed | |
|
750 | pm[n] = 1 | |
|
751 | nl.append(n) | |
|
752 | break | |
|
753 | ||
|
754 | return nl | |
|
755 | ||
|
756 | 730 | def findincoming(self, remote, base=None, heads=None): |
|
757 | 731 | m = self.changelog.nodemap |
|
758 | 732 | search = [] |
@@ -903,7 +877,7 b' class localrepository:' | |||
|
903 | 877 | # this is the set of all roots we have to push |
|
904 | 878 | return subset |
|
905 | 879 | |
|
906 | def pull(self, remote): | |
|
880 | def pull(self, remote, heads = None): | |
|
907 | 881 | lock = self.lock() |
|
908 | 882 | |
|
909 | 883 | # if we have an empty repo, fetch everything |
@@ -917,7 +891,10 b' class localrepository:' | |||
|
917 | 891 | self.ui.status(_("no changes found\n")) |
|
918 | 892 | return 1 |
|
919 | 893 | |
|
920 | cg = remote.changegroup(fetch) | |
|
894 | if heads is None: | |
|
895 | cg = remote.changegroup(fetch) | |
|
896 | else: | |
|
897 | cg = remote.changegroupsubset(fetch, heads) | |
|
921 | 898 | return self.addchangegroup(cg) |
|
922 | 899 | |
|
923 | 900 | def push(self, remote, force=False): |
@@ -945,40 +922,327 b' class localrepository:' | |||
|
945 | 922 | cg = self.changegroup(update) |
|
946 | 923 | return remote.addchangegroup(cg) |
|
947 | 924 | |
|
925 | def changegroupsubset(self, bases, heads): | |
|
926 | """This function generates a changegroup consisting of all the nodes | |
|
927 | that are descendents of any of the bases, and ancestors of any of | |
|
928 | the heads. | |
|
929 | ||
|
930 | It is fairly complex as determining which filenodes and which | |
|
931 | manifest nodes need to be included for the changeset to be complete | |
|
932 | is non-trivial. | |
|
933 | ||
|
934 | Another wrinkle is doing the reverse, figuring out which changeset in | |
|
935 | the changegroup a particular filenode or manifestnode belongs to.""" | |
|
936 | ||
|
937 | # Set up some initial variables | |
|
938 | # Make it easy to refer to self.changelog | |
|
939 | cl = self.changelog | |
|
940 | # msng is short for missing - compute the list of changesets in this | |
|
941 | # changegroup. | |
|
942 | msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads) | |
|
943 | # Some bases may turn out to be superfluous, and some heads may be | |
|
944 | # too. nodesbetween will return the minimal set of bases and heads | |
|
945 | # necessary to re-create the changegroup. | |
|
946 | ||
|
947 | # Known heads are the list of heads that it is assumed the recipient | |
|
948 | # of this changegroup will know about. | |
|
949 | knownheads = {} | |
|
950 | # We assume that all parents of bases are known heads. | |
|
951 | for n in bases: | |
|
952 | for p in cl.parents(n): | |
|
953 | if p != nullid: | |
|
954 | knownheads[p] = 1 | |
|
955 | knownheads = knownheads.keys() | |
|
956 | if knownheads: | |
|
957 | # Now that we know what heads are known, we can compute which | |
|
958 | # changesets are known. The recipient must know about all | |
|
959 | # changesets required to reach the known heads from the null | |
|
960 | # changeset. | |
|
961 | has_cl_set, junk, junk = cl.nodesbetween(None, knownheads) | |
|
962 | junk = None | |
|
963 | # Transform the list into an ersatz set. | |
|
964 | has_cl_set = dict.fromkeys(has_cl_set) | |
|
965 | else: | |
|
966 | # If there were no known heads, the recipient cannot be assumed to | |
|
967 | # know about any changesets. | |
|
968 | has_cl_set = {} | |
|
969 | ||
|
970 | # Make it easy to refer to self.manifest | |
|
971 | mnfst = self.manifest | |
|
972 | # We don't know which manifests are missing yet | |
|
973 | msng_mnfst_set = {} | |
|
974 | # Nor do we know which filenodes are missing. | |
|
975 | msng_filenode_set = {} | |
|
976 | ||
|
977 | junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex | |
|
978 | junk = None | |
|
979 | ||
|
980 | # A changeset always belongs to itself, so the changenode lookup | |
|
981 | # function for a changenode is identity. | |
|
982 | def identity(x): | |
|
983 | return x | |
|
984 | ||
|
985 | # A function generating function. Sets up an environment for the | |
|
986 | # inner function. | |
|
987 | def cmp_by_rev_func(revlog): | |
|
988 | # Compare two nodes by their revision number in the environment's | |
|
989 | # revision history. Since the revision number both represents the | |
|
990 | # most efficient order to read the nodes in, and represents a | |
|
991 | # topological sorting of the nodes, this function is often useful. | |
|
992 | def cmp_by_rev(a, b): | |
|
993 | return cmp(revlog.rev(a), revlog.rev(b)) | |
|
994 | return cmp_by_rev | |
|
995 | ||
|
996 | # If we determine that a particular file or manifest node must be a | |
|
997 | # node that the recipient of the changegroup will already have, we can | |
|
998 | # also assume the recipient will have all the parents. This function | |
|
999 | # prunes them from the set of missing nodes. | |
|
1000 | def prune_parents(revlog, hasset, msngset): | |
|
1001 | haslst = hasset.keys() | |
|
1002 | haslst.sort(cmp_by_rev_func(revlog)) | |
|
1003 | for node in haslst: | |
|
1004 | parentlst = [p for p in revlog.parents(node) if p != nullid] | |
|
1005 | while parentlst: | |
|
1006 | n = parentlst.pop() | |
|
1007 | if n not in hasset: | |
|
1008 | hasset[n] = 1 | |
|
1009 | p = [p for p in revlog.parents(n) if p != nullid] | |
|
1010 | parentlst.extend(p) | |
|
1011 | for n in hasset: | |
|
1012 | msngset.pop(n, None) | |
|
1013 | ||
|
1014 | # This is a function generating function used to set up an environment | |
|
1015 | # for the inner function to execute in. | |
|
1016 | def manifest_and_file_collector(changedfileset): | |
|
1017 | # This is an information gathering function that gathers | |
|
1018 | # information from each changeset node that goes out as part of | |
|
1019 | # the changegroup. The information gathered is a list of which | |
|
1020 | # manifest nodes are potentially required (the recipient may | |
|
1021 | # already have them) and total list of all files which were | |
|
1022 | # changed in any changeset in the changegroup. | |
|
1023 | # | |
|
1024 | # We also remember the first changenode we saw any manifest | |
|
1025 | # referenced by so we can later determine which changenode 'owns' | |
|
1026 | # the manifest. | |
|
1027 | def collect_manifests_and_files(clnode): | |
|
1028 | c = cl.read(clnode) | |
|
1029 | for f in c[3]: | |
|
1030 | # This is to make sure we only have one instance of each | |
|
1031 | # filename string for each filename. | |
|
1032 | changedfileset.setdefault(f, f) | |
|
1033 | msng_mnfst_set.setdefault(c[0], clnode) | |
|
1034 | return collect_manifests_and_files | |
|
1035 | ||
|
1036 | # Figure out which manifest nodes (of the ones we think might be part | |
|
1037 | # of the changegroup) the recipient must know about and remove them | |
|
1038 | # from the changegroup. | |
|
1039 | def prune_manifests(): | |
|
1040 | has_mnfst_set = {} | |
|
1041 | for n in msng_mnfst_set: | |
|
1042 | # If a 'missing' manifest thinks it belongs to a changenode | |
|
1043 | # the recipient is assumed to have, obviously the recipient | |
|
1044 | # must have that manifest. | |
|
1045 | linknode = cl.node(mnfst.linkrev(n)) | |
|
1046 | if linknode in has_cl_set: | |
|
1047 | has_mnfst_set[n] = 1 | |
|
1048 | prune_parents(mnfst, has_mnfst_set, msng_mnfst_set) | |
|
1049 | ||
|
1050 | # Use the information collected in collect_manifests_and_files to say | |
|
1051 | # which changenode any manifestnode belongs to. | |
|
1052 | def lookup_manifest_link(mnfstnode): | |
|
1053 | return msng_mnfst_set[mnfstnode] | |
|
1054 | ||
|
1055 | # A function generating function that sets up the initial environment | |
|
1056 | # the inner function. | |
|
1057 | def filenode_collector(changedfiles): | |
|
1058 | next_rev = [0] | |
|
1059 | # This gathers information from each manifestnode included in the | |
|
1060 | # changegroup about which filenodes the manifest node references | |
|
1061 | # so we can include those in the changegroup too. | |
|
1062 | # | |
|
1063 | # It also remembers which changenode each filenode belongs to. It | |
|
1064 | # does this by assuming the a filenode belongs to the changenode | |
|
1065 | # the first manifest that references it belongs to. | |
|
1066 | def collect_msng_filenodes(mnfstnode): | |
|
1067 | r = mnfst.rev(mnfstnode) | |
|
1068 | if r == next_rev[0]: | |
|
1069 | # If the last rev we looked at was the one just previous, | |
|
1070 | # we only need to see a diff. | |
|
1071 | delta = mdiff.patchtext(mnfst.delta(mnfstnode)) | |
|
1072 | # For each line in the delta | |
|
1073 | for dline in delta.splitlines(): | |
|
1074 | # get the filename and filenode for that line | |
|
1075 | f, fnode = dline.split('\0') | |
|
1076 | fnode = bin(fnode[:40]) | |
|
1077 | f = changedfiles.get(f, None) | |
|
1078 | # And if the file is in the list of files we care | |
|
1079 | # about. | |
|
1080 | if f is not None: | |
|
1081 | # Get the changenode this manifest belongs to | |
|
1082 | clnode = msng_mnfst_set[mnfstnode] | |
|
1083 | # Create the set of filenodes for the file if | |
|
1084 | # there isn't one already. | |
|
1085 | ndset = msng_filenode_set.setdefault(f, {}) | |
|
1086 | # And set the filenode's changelog node to the | |
|
1087 | # manifest's if it hasn't been set already. | |
|
1088 | ndset.setdefault(fnode, clnode) | |
|
1089 | else: | |
|
1090 | # Otherwise we need a full manifest. | |
|
1091 | m = mnfst.read(mnfstnode) | |
|
1092 | # For every file in we care about. | |
|
1093 | for f in changedfiles: | |
|
1094 | fnode = m.get(f, None) | |
|
1095 | # If it's in the manifest | |
|
1096 | if fnode is not None: | |
|
1097 | # See comments above. | |
|
1098 | clnode = msng_mnfst_set[mnfstnode] | |
|
1099 | ndset = msng_filenode_set.setdefault(f, {}) | |
|
1100 | ndset.setdefault(fnode, clnode) | |
|
1101 | # Remember the revision we hope to see next. | |
|
1102 | next_rev[0] = r + 1 | |
|
1103 | return collect_msng_filenodes | |
|
1104 | ||
|
1105 | # We have a list of filenodes we think we need for a file, lets remove | |
|
1106 | # all those we now the recipient must have. | |
|
1107 | def prune_filenodes(f, filerevlog): | |
|
1108 | msngset = msng_filenode_set[f] | |
|
1109 | hasset = {} | |
|
1110 | # If a 'missing' filenode thinks it belongs to a changenode we | |
|
1111 | # assume the recipient must have, then the recipient must have | |
|
1112 | # that filenode. | |
|
1113 | for n in msngset: | |
|
1114 | clnode = cl.node(filerevlog.linkrev(n)) | |
|
1115 | if clnode in has_cl_set: | |
|
1116 | hasset[n] = 1 | |
|
1117 | prune_parents(filerevlog, hasset, msngset) | |
|
1118 | ||
|
1119 | # A function generator function that sets up the a context for the | |
|
1120 | # inner function. | |
|
1121 | def lookup_filenode_link_func(fname): | |
|
1122 | msngset = msng_filenode_set[fname] | |
|
1123 | # Lookup the changenode the filenode belongs to. | |
|
1124 | def lookup_filenode_link(fnode): | |
|
1125 | return msngset[fnode] | |
|
1126 | return lookup_filenode_link | |
|
1127 | ||
|
1128 | # Now that we have all theses utility functions to help out and | |
|
1129 | # logically divide up the task, generate the group. | |
|
1130 | def gengroup(): | |
|
1131 | # The set of changed files starts empty. | |
|
1132 | changedfiles = {} | |
|
1133 | # Create a changenode group generator that will call our functions | |
|
1134 | # back to lookup the owning changenode and collect information. | |
|
1135 | group = cl.group(msng_cl_lst, identity, | |
|
1136 | manifest_and_file_collector(changedfiles)) | |
|
1137 | for chnk in group: | |
|
1138 | yield chnk | |
|
1139 | ||
|
1140 | # The list of manifests has been collected by the generator | |
|
1141 | # calling our functions back. | |
|
1142 | prune_manifests() | |
|
1143 | msng_mnfst_lst = msng_mnfst_set.keys() | |
|
1144 | # Sort the manifestnodes by revision number. | |
|
1145 | msng_mnfst_lst.sort(cmp_by_rev_func(mnfst)) | |
|
1146 | # Create a generator for the manifestnodes that calls our lookup | |
|
1147 | # and data collection functions back. | |
|
1148 | group = mnfst.group(msng_mnfst_lst, lookup_manifest_link, | |
|
1149 | filenode_collector(changedfiles)) | |
|
1150 | for chnk in group: | |
|
1151 | yield chnk | |
|
1152 | ||
|
1153 | # These are no longer needed, dereference and toss the memory for | |
|
1154 | # them. | |
|
1155 | msng_mnfst_lst = None | |
|
1156 | msng_mnfst_set.clear() | |
|
1157 | ||
|
1158 | changedfiles = changedfiles.keys() | |
|
1159 | changedfiles.sort() | |
|
1160 | # Go through all our files in order sorted by name. | |
|
1161 | for fname in changedfiles: | |
|
1162 | filerevlog = self.file(fname) | |
|
1163 | # Toss out the filenodes that the recipient isn't really | |
|
1164 | # missing. | |
|
1165 | prune_filenodes(fname, filerevlog) | |
|
1166 | msng_filenode_lst = msng_filenode_set[fname].keys() | |
|
1167 | # If any filenodes are left, generate the group for them, | |
|
1168 | # otherwise don't bother. | |
|
1169 | if len(msng_filenode_lst) > 0: | |
|
1170 | yield struct.pack(">l", len(fname) + 4) + fname | |
|
1171 | # Sort the filenodes by their revision # | |
|
1172 | msng_filenode_lst.sort(cmp_by_rev_func(filerevlog)) | |
|
1173 | # Create a group generator and only pass in a changenode | |
|
1174 | # lookup function as we need to collect no information | |
|
1175 | # from filenodes. | |
|
1176 | group = filerevlog.group(msng_filenode_lst, | |
|
1177 | lookup_filenode_link_func(fname)) | |
|
1178 | for chnk in group: | |
|
1179 | yield chnk | |
|
1180 | # Don't need this anymore, toss it to free memory. | |
|
1181 | del msng_filenode_set[fname] | |
|
1182 | # Signal that no more groups are left. | |
|
1183 | yield struct.pack(">l", 0) | |
|
1184 | ||
|
1185 | return util.chunkbuffer(gengroup()) | |
|
1186 | ||
|
948 | 1187 | def changegroup(self, basenodes): |
|
949 | genread = util.chunkbuffer | |
|
1188 | """Generate a changegroup of all nodes that we have that a recipient | |
|
1189 | doesn't. | |
|
1190 | ||
|
1191 | This is much easier than the previous function as we can assume that | |
|
1192 | the recipient has any changenode we aren't sending them.""" | |
|
1193 | cl = self.changelog | |
|
1194 | nodes = cl.nodesbetween(basenodes, None)[0] | |
|
1195 | revset = dict.fromkeys([cl.rev(n) for n in nodes]) | |
|
1196 | ||
|
1197 | def identity(x): | |
|
1198 | return x | |
|
1199 | ||
|
1200 | def gennodelst(revlog): | |
|
1201 | for r in xrange(0, revlog.count()): | |
|
1202 | n = revlog.node(r) | |
|
1203 | if revlog.linkrev(n) in revset: | |
|
1204 | yield n | |
|
1205 | ||
|
1206 | def changed_file_collector(changedfileset): | |
|
1207 | def collect_changed_files(clnode): | |
|
1208 | c = cl.read(clnode) | |
|
1209 | for fname in c[3]: | |
|
1210 | changedfileset[fname] = 1 | |
|
1211 | return collect_changed_files | |
|
1212 | ||
|
1213 | def lookuprevlink_func(revlog): | |
|
1214 | def lookuprevlink(n): | |
|
1215 | return cl.node(revlog.linkrev(n)) | |
|
1216 | return lookuprevlink | |
|
950 | 1217 | |
|
951 | 1218 | def gengroup(): |
|
952 | nodes = self.newer(basenodes) | |
|
1219 | # construct a list of all changed files | |
|
1220 | changedfiles = {} | |
|
953 | 1221 | |
|
954 | # construct the link map | |
|
955 | linkmap = {} | |
|
956 | for n in nodes: | |
|
957 | linkmap[self.changelog.rev(n)] = n | |
|
1222 | for chnk in cl.group(nodes, identity, | |
|
1223 | changed_file_collector(changedfiles)): | |
|
1224 | yield chnk | |
|
1225 | changedfiles = changedfiles.keys() | |
|
1226 | changedfiles.sort() | |
|
958 | 1227 | |
|
959 | # construct a list of all changed files | |
|
960 | changed = {} | |
|
961 | for n in nodes: | |
|
962 |
|
|
|
963 | for f in c[3]: | |
|
964 | changed[f] = 1 | |
|
965 | changed = changed.keys() | |
|
966 | changed.sort() | |
|
1228 | mnfst = self.manifest | |
|
1229 | nodeiter = gennodelst(mnfst) | |
|
1230 | for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)): | |
|
1231 | yield chnk | |
|
967 | 1232 | |
|
968 | # the changegroup is changesets + manifests + all file revs | |
|
969 | revs = [ self.changelog.rev(n) for n in nodes ] | |
|
970 | ||
|
971 | for y in self.changelog.group(linkmap): yield y | |
|
972 | for y in self.manifest.group(linkmap): yield y | |
|
973 | for f in changed: | |
|
974 | yield struct.pack(">l", len(f) + 4) + f | |
|
975 | g = self.file(f).group(linkmap) | |
|
976 |
|
|
|
977 | yield y | |
|
1233 | for fname in changedfiles: | |
|
1234 | filerevlog = self.file(fname) | |
|
1235 | nodeiter = gennodelst(filerevlog) | |
|
1236 | nodeiter = list(nodeiter) | |
|
1237 | if nodeiter: | |
|
1238 | yield struct.pack(">l", len(fname) + 4) + fname | |
|
1239 | lookup = lookuprevlink_func(filerevlog) | |
|
1240 | for chnk in filerevlog.group(nodeiter, lookup): | |
|
1241 | yield chnk | |
|
978 | 1242 | |
|
979 | 1243 | yield struct.pack(">l", 0) |
|
980 | 1244 | |
|
981 |
return |
|
|
1245 | return util.chunkbuffer(gengroup()) | |
|
982 | 1246 | |
|
983 | 1247 | def addchangegroup(self, source): |
|
984 | 1248 |
@@ -249,6 +249,157 b' class revlog:' | |||
|
249 | 249 | visit.append(p) |
|
250 | 250 | return reachable |
|
251 | 251 | |
|
252 | def nodesbetween(self, roots=None, heads=None): | |
|
253 | """Return a tuple containing three elements. Elements 1 and 2 contain | |
|
254 | a final list bases and heads after all the unreachable ones have been | |
|
255 | pruned. Element 0 contains a topologically sorted list of all | |
|
256 | ||
|
257 | nodes that satisfy these constraints: | |
|
258 | 1. All nodes must be descended from a node in roots (the nodes on | |
|
259 | roots are considered descended from themselves). | |
|
260 | 2. All nodes must also be ancestors of a node in heads (the nodes in | |
|
261 | heads are considered to be their own ancestors). | |
|
262 | ||
|
263 | If roots is unspecified, nullid is assumed as the only root. | |
|
264 | If heads is unspecified, it is taken to be the output of the | |
|
265 | heads method (i.e. a list of all nodes in the repository that | |
|
266 | have no children).""" | |
|
267 | nonodes = ([], [], []) | |
|
268 | if roots is not None: | |
|
269 | roots = list(roots) | |
|
270 | if not roots: | |
|
271 | return nonodes | |
|
272 | lowestrev = min([self.rev(n) for n in roots]) | |
|
273 | else: | |
|
274 | roots = [nullid] # Everybody's a descendent of nullid | |
|
275 | lowestrev = -1 | |
|
276 | if (lowestrev == -1) and (heads is None): | |
|
277 | # We want _all_ the nodes! | |
|
278 | return ([self.node(r) for r in xrange(0, self.count())], | |
|
279 | [nullid], list(self.heads())) | |
|
280 | if heads is None: | |
|
281 | # All nodes are ancestors, so the latest ancestor is the last | |
|
282 | # node. | |
|
283 | highestrev = self.count() - 1 | |
|
284 | # Set ancestors to None to signal that every node is an ancestor. | |
|
285 | ancestors = None | |
|
286 | # Set heads to an empty dictionary for later discovery of heads | |
|
287 | heads = {} | |
|
288 | else: | |
|
289 | heads = list(heads) | |
|
290 | if not heads: | |
|
291 | return nonodes | |
|
292 | ancestors = {} | |
|
293 | # Start at the top and keep marking parents until we're done. | |
|
294 | nodestotag = heads[:] | |
|
295 | # Turn heads into a dictionary so we can remove 'fake' heads. | |
|
296 | # Also, later we will be using it to filter out the heads we can't | |
|
297 | # find from roots. | |
|
298 | heads = dict.fromkeys(heads, 0) | |
|
299 | # Remember where the top was so we can use it as a limit later. | |
|
300 | highestrev = max([self.rev(n) for n in nodestotag]) | |
|
301 | while nodestotag: | |
|
302 | # grab a node to tag | |
|
303 | n = nodestotag.pop() | |
|
304 | # Never tag nullid | |
|
305 | if n == nullid: | |
|
306 | continue | |
|
307 | # A node's revision number represents its place in a | |
|
308 | # topologically sorted list of nodes. | |
|
309 | r = self.rev(n) | |
|
310 | if r >= lowestrev: | |
|
311 | if n not in ancestors: | |
|
312 | # If we are possibly a descendent of one of the roots | |
|
313 | # and we haven't already been marked as an ancestor | |
|
314 | ancestors[n] = 1 # Mark as ancestor | |
|
315 | # Add non-nullid parents to list of nodes to tag. | |
|
316 | nodestotag.extend([p for p in self.parents(n) if | |
|
317 | p != nullid]) | |
|
318 | elif n in heads: # We've seen it before, is it a fake head? | |
|
319 | # So it is, real heads should not be the ancestors of | |
|
320 | # any other heads. | |
|
321 | heads.pop(n) | |
|
322 | if not ancestors: | |
|
323 | return nonodes | |
|
324 | # Now that we have our set of ancestors, we want to remove any | |
|
325 | # roots that are not ancestors. | |
|
326 | ||
|
327 | # If one of the roots was nullid, everything is included anyway. | |
|
328 | if lowestrev > -1: | |
|
329 | # But, since we weren't, let's recompute the lowest rev to not | |
|
330 | # include roots that aren't ancestors. | |
|
331 | ||
|
332 | # Filter out roots that aren't ancestors of heads | |
|
333 | roots = [n for n in roots if n in ancestors] | |
|
334 | # Recompute the lowest revision | |
|
335 | if roots: | |
|
336 | lowestrev = min([self.rev(n) for n in roots]) | |
|
337 | else: | |
|
338 | # No more roots? Return empty list | |
|
339 | return nonodes | |
|
340 | else: | |
|
341 | # We are descending from nullid, and don't need to care about | |
|
342 | # any other roots. | |
|
343 | lowestrev = -1 | |
|
344 | roots = [nullid] | |
|
345 | # Transform our roots list into a 'set' (i.e. a dictionary where the | |
|
346 | # values don't matter. | |
|
347 | descendents = dict.fromkeys(roots, 1) | |
|
348 | # Also, keep the original roots so we can filter out roots that aren't | |
|
349 | # 'real' roots (i.e. are descended from other roots). | |
|
350 | roots = descendents.copy() | |
|
351 | # Our topologically sorted list of output nodes. | |
|
352 | orderedout = [] | |
|
353 | # Don't start at nullid since we don't want nullid in our output list, | |
|
354 | # and if nullid shows up in descedents, empty parents will look like | |
|
355 | # they're descendents. | |
|
356 | for r in xrange(max(lowestrev, 0), highestrev + 1): | |
|
357 | n = self.node(r) | |
|
358 | isdescendent = False | |
|
359 | if lowestrev == -1: # Everybody is a descendent of nullid | |
|
360 | isdescendent = True | |
|
361 | elif n in descendents: | |
|
362 | # n is already a descendent | |
|
363 | isdescendent = True | |
|
364 | # This check only needs to be done here because all the roots | |
|
365 | # will start being marked is descendents before the loop. | |
|
366 | if n in roots: | |
|
367 | # If n was a root, check if it's a 'real' root. | |
|
368 | p = tuple(self.parents(n)) | |
|
369 | # If any of its parents are descendents, it's not a root. | |
|
370 | if (p[0] in descendents) or (p[1] in descendents): | |
|
371 | roots.pop(n) | |
|
372 | else: | |
|
373 | p = tuple(self.parents(n)) | |
|
374 | # A node is a descendent if either of its parents are | |
|
375 | # descendents. (We seeded the dependents list with the roots | |
|
376 | # up there, remember?) | |
|
377 | if (p[0] in descendents) or (p[1] in descendents): | |
|
378 | descendents[n] = 1 | |
|
379 | isdescendent = True | |
|
380 | if isdescendent and ((ancestors is None) or (n in ancestors)): | |
|
381 | # Only include nodes that are both descendents and ancestors. | |
|
382 | orderedout.append(n) | |
|
383 | if (ancestors is not None) and (n in heads): | |
|
384 | # We're trying to figure out which heads are reachable | |
|
385 | # from roots. | |
|
386 | # Mark this head as having been reached | |
|
387 | heads[n] = 1 | |
|
388 | elif ancestors is None: | |
|
389 | # Otherwise, we're trying to discover the heads. | |
|
390 | # Assume this is a head because if it isn't, the next step | |
|
391 | # will eventually remove it. | |
|
392 | heads[n] = 1 | |
|
393 | # But, obviously its parents aren't. | |
|
394 | for p in self.parents(n): | |
|
395 | heads.pop(p, None) | |
|
396 | heads = [n for n in heads.iterkeys() if heads[n] != 0] | |
|
397 | roots = roots.keys() | |
|
398 | assert orderedout | |
|
399 | assert roots | |
|
400 | assert heads | |
|
401 | return (orderedout, roots, heads) | |
|
402 | ||
|
252 | 403 | def heads(self, stop=None): |
|
253 | 404 | """return the list of all nodes that have no children""" |
|
254 | 405 | p = {} |
@@ -482,7 +633,7 b' class revlog:' | |||
|
482 | 633 | #print "next x" |
|
483 | 634 | gx = x.next() |
|
484 | 635 | |
|
485 | def group(self, linkmap): | |
|
636 | def group(self, nodelist, lookup, infocollect = None): | |
|
486 | 637 | """calculate a delta group |
|
487 | 638 | |
|
488 | 639 | Given a list of changeset revs, return a set of deltas and |
@@ -491,14 +642,8 b' class revlog:' | |||
|
491 | 642 | have this parent as it has all history before these |
|
492 | 643 | changesets. parent is parent[0] |
|
493 | 644 | """ |
|
494 | revs = [] | |
|
495 | needed = {} | |
|
496 | ||
|
497 | # find file nodes/revs that match changeset revs | |
|
498 | for i in xrange(0, self.count()): | |
|
499 | if self.index[i][3] in linkmap: | |
|
500 | revs.append(i) | |
|
501 | needed[i] = 1 | |
|
645 | revs = [self.rev(n) for n in nodelist] | |
|
646 | needed = dict.fromkeys(revs, 1) | |
|
502 | 647 | |
|
503 | 648 | # if we don't have any revisions touched by these changesets, bail |
|
504 | 649 | if not revs: |
@@ -566,6 +711,9 b' class revlog:' | |||
|
566 | 711 | a, b = revs[d], revs[d + 1] |
|
567 | 712 | n = self.node(b) |
|
568 | 713 | |
|
714 | if infocollect is not None: | |
|
715 | infocollect(n) | |
|
716 | ||
|
569 | 717 | # do we need to construct a new delta? |
|
570 | 718 | if a + 1 != b or self.base(b) == b: |
|
571 | 719 | if a >= 0: |
@@ -587,7 +735,7 b' class revlog:' | |||
|
587 | 735 | d = chunks[b] |
|
588 | 736 | |
|
589 | 737 | p = self.parents(n) |
|
590 |
meta = n + p[0] + p[1] + l |
|
|
738 | meta = n + p[0] + p[1] + lookup(n) | |
|
591 | 739 | l = struct.pack(">l", len(meta) + len(d) + 4) |
|
592 | 740 | yield l |
|
593 | 741 | yield meta |
General Comments 0
You need to be logged in to leave comments.
Login now