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 | copy = False |
|
693 | copy = False | |
694 | if other.dev() != -1: |
|
694 | if other.dev() != -1: | |
695 | abspath = os.path.abspath(source) |
|
695 | abspath = os.path.abspath(source) | |
696 | if not opts['pull']: |
|
696 | if not opts['pull'] and not opts['rev']: | |
697 | copy = True |
|
697 | copy = True | |
698 |
|
698 | |||
699 | if copy: |
|
699 | if copy: | |
@@ -723,8 +723,14 b' def clone(ui, source, dest=None, **opts)' | |||||
723 | repo = hg.repository(ui, dest) |
|
723 | repo = hg.repository(ui, dest) | |
724 |
|
724 | |||
725 | else: |
|
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 | repo = hg.repository(ui, dest, create=1) |
|
732 | repo = hg.repository(ui, dest, create=1) | |
727 | repo.pull(other) |
|
733 | repo.pull(other, heads = revs) | |
728 |
|
734 | |||
729 | f = repo.opener("hgrc", "w", text=True) |
|
735 | f = repo.opener("hgrc", "w", text=True) | |
730 | f.write("[paths]\n") |
|
736 | f.write("[paths]\n") | |
@@ -1396,7 +1402,7 b' def incoming(ui, repo, source="default",' | |||||
1396 | o = repo.findincoming(other) |
|
1402 | o = repo.findincoming(other) | |
1397 | if not o: |
|
1403 | if not o: | |
1398 | return |
|
1404 | return | |
1399 |
o = other. |
|
1405 | o = other.changelog.nodesbetween(o)[0] | |
1400 | if opts['newest_first']: |
|
1406 | if opts['newest_first']: | |
1401 | o.reverse() |
|
1407 | o.reverse() | |
1402 | for n in o: |
|
1408 | for n in o: | |
@@ -1561,7 +1567,7 b' def outgoing(ui, repo, dest="default-pus' | |||||
1561 | dest = ui.expandpath(dest, repo.root) |
|
1567 | dest = ui.expandpath(dest, repo.root) | |
1562 | other = hg.repository(ui, dest) |
|
1568 | other = hg.repository(ui, dest) | |
1563 | o = repo.findoutgoing(other) |
|
1569 | o = repo.findoutgoing(other) | |
1564 |
o = repo. |
|
1570 | o = repo.changelog.nodesbetween(o)[0] | |
1565 | if opts['newest_first']: |
|
1571 | if opts['newest_first']: | |
1566 | o.reverse() |
|
1572 | o.reverse() | |
1567 | for n in o: |
|
1573 | for n in o: | |
@@ -1643,7 +1649,12 b' def pull(ui, repo, source="default", **o' | |||||
1643 | ui.setconfig("ui", "remotecmd", opts['remotecmd']) |
|
1649 | ui.setconfig("ui", "remotecmd", opts['remotecmd']) | |
1644 |
|
1650 | |||
1645 | other = hg.repository(ui, source) |
|
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 | if not r: |
|
1658 | if not r: | |
1648 | if opts['update']: |
|
1659 | if opts['update']: | |
1649 | return update(ui, repo) |
|
1660 | return update(ui, repo) | |
@@ -2193,6 +2204,7 b' table = {' | |||||
2193 | [('U', 'noupdate', None, _('do not update the new working directory')), |
|
2204 | [('U', 'noupdate', None, _('do not update the new working directory')), | |
2194 | ('e', 'ssh', "", _('specify ssh command to use')), |
|
2205 | ('e', 'ssh', "", _('specify ssh command to use')), | |
2195 | ('', 'pull', None, _('use pull protocol to copy metadata')), |
|
2206 | ('', 'pull', None, _('use pull protocol to copy metadata')), | |
|
2207 | ('r', 'rev', [], _('a changeset you would like to have after cloning')), | |||
2196 | ('', 'remotecmd', "", _('specify hg command to run on the remote side'))], |
|
2208 | ('', 'remotecmd', "", _('specify hg command to run on the remote side'))], | |
2197 | _('hg clone [OPTION]... SOURCE [DEST]')), |
|
2209 | _('hg clone [OPTION]... SOURCE [DEST]')), | |
2198 | "^commit|ci": |
|
2210 | "^commit|ci": | |
@@ -2304,8 +2316,9 b' table = {' | |||||
2304 | (pull, |
|
2316 | (pull, | |
2305 | [('u', 'update', None, _('update the working directory to tip after pull')), |
|
2317 | [('u', 'update', None, _('update the working directory to tip after pull')), | |
2306 | ('e', 'ssh', "", _('specify ssh command to use')), |
|
2318 | ('e', 'ssh', "", _('specify ssh command to use')), | |
|
2319 | ('r', 'rev', [], _('a specific revision you would like to pull')), | |||
2307 | ('', 'remotecmd', "", _('specify hg command to run on the remote side'))], |
|
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 | "^push": |
|
2322 | "^push": | |
2310 | (push, |
|
2323 | (push, | |
2311 | [('f', 'force', None, _('force push')), |
|
2324 | [('f', 'force', None, _('force push')), |
@@ -727,32 +727,6 b' class localrepository:' | |||||
727 |
|
727 | |||
728 | return r |
|
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 | def findincoming(self, remote, base=None, heads=None): |
|
730 | def findincoming(self, remote, base=None, heads=None): | |
757 | m = self.changelog.nodemap |
|
731 | m = self.changelog.nodemap | |
758 | search = [] |
|
732 | search = [] | |
@@ -903,7 +877,7 b' class localrepository:' | |||||
903 | # this is the set of all roots we have to push |
|
877 | # this is the set of all roots we have to push | |
904 | return subset |
|
878 | return subset | |
905 |
|
879 | |||
906 | def pull(self, remote): |
|
880 | def pull(self, remote, heads = None): | |
907 | lock = self.lock() |
|
881 | lock = self.lock() | |
908 |
|
882 | |||
909 | # if we have an empty repo, fetch everything |
|
883 | # if we have an empty repo, fetch everything | |
@@ -917,7 +891,10 b' class localrepository:' | |||||
917 | self.ui.status(_("no changes found\n")) |
|
891 | self.ui.status(_("no changes found\n")) | |
918 | return 1 |
|
892 | return 1 | |
919 |
|
893 | |||
|
894 | if heads is None: | |||
920 | cg = remote.changegroup(fetch) |
|
895 | cg = remote.changegroup(fetch) | |
|
896 | else: | |||
|
897 | cg = remote.changegroupsubset(fetch, heads) | |||
921 | return self.addchangegroup(cg) |
|
898 | return self.addchangegroup(cg) | |
922 |
|
899 | |||
923 | def push(self, remote, force=False): |
|
900 | def push(self, remote, force=False): | |
@@ -945,40 +922,327 b' class localrepository:' | |||||
945 | cg = self.changegroup(update) |
|
922 | cg = self.changegroup(update) | |
946 | return remote.addchangegroup(cg) |
|
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 | def changegroup(self, basenodes): |
|
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 | def gengroup(): |
|
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 |
|
1222 | for chnk in cl.group(nodes, identity, | |
955 | linkmap = {} |
|
1223 | changed_file_collector(changedfiles)): | |
956 | for n in nodes: |
|
1224 | yield chnk | |
957 | linkmap[self.changelog.rev(n)] = n |
|
1225 | changedfiles = changedfiles.keys() | |
|
1226 | changedfiles.sort() | |||
958 |
|
1227 | |||
959 | # construct a list of all changed files |
|
1228 | mnfst = self.manifest | |
960 | changed = {} |
|
1229 | nodeiter = gennodelst(mnfst) | |
961 | for n in nodes: |
|
1230 | for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)): | |
962 |
|
|
1231 | yield chnk | |
963 | for f in c[3]: |
|
|||
964 | changed[f] = 1 |
|
|||
965 | changed = changed.keys() |
|
|||
966 | changed.sort() |
|
|||
967 |
|
1232 | |||
968 | # the changegroup is changesets + manifests + all file revs |
|
1233 | for fname in changedfiles: | |
969 | revs = [ self.changelog.rev(n) for n in nodes ] |
|
1234 | filerevlog = self.file(fname) | |
970 |
|
1235 | nodeiter = gennodelst(filerevlog) | ||
971 | for y in self.changelog.group(linkmap): yield y |
|
1236 | nodeiter = list(nodeiter) | |
972 | for y in self.manifest.group(linkmap): yield y |
|
1237 | if nodeiter: | |
973 | for f in changed: |
|
1238 | yield struct.pack(">l", len(fname) + 4) + fname | |
974 | yield struct.pack(">l", len(f) + 4) + f |
|
1239 | lookup = lookuprevlink_func(filerevlog) | |
975 | g = self.file(f).group(linkmap) |
|
1240 | for chnk in filerevlog.group(nodeiter, lookup): | |
976 |
|
|
1241 | yield chnk | |
977 | yield y |
|
|||
978 |
|
1242 | |||
979 | yield struct.pack(">l", 0) |
|
1243 | yield struct.pack(">l", 0) | |
980 |
|
1244 | |||
981 |
return |
|
1245 | return util.chunkbuffer(gengroup()) | |
982 |
|
1246 | |||
983 | def addchangegroup(self, source): |
|
1247 | def addchangegroup(self, source): | |
984 |
|
1248 |
@@ -249,6 +249,157 b' class revlog:' | |||||
249 | visit.append(p) |
|
249 | visit.append(p) | |
250 | return reachable |
|
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 | def heads(self, stop=None): |
|
403 | def heads(self, stop=None): | |
253 | """return the list of all nodes that have no children""" |
|
404 | """return the list of all nodes that have no children""" | |
254 | p = {} |
|
405 | p = {} | |
@@ -482,7 +633,7 b' class revlog:' | |||||
482 | #print "next x" |
|
633 | #print "next x" | |
483 | gx = x.next() |
|
634 | gx = x.next() | |
484 |
|
635 | |||
485 | def group(self, linkmap): |
|
636 | def group(self, nodelist, lookup, infocollect = None): | |
486 | """calculate a delta group |
|
637 | """calculate a delta group | |
487 |
|
638 | |||
488 | Given a list of changeset revs, return a set of deltas and |
|
639 | Given a list of changeset revs, return a set of deltas and | |
@@ -491,14 +642,8 b' class revlog:' | |||||
491 | have this parent as it has all history before these |
|
642 | have this parent as it has all history before these | |
492 | changesets. parent is parent[0] |
|
643 | changesets. parent is parent[0] | |
493 | """ |
|
644 | """ | |
494 | revs = [] |
|
645 | revs = [self.rev(n) for n in nodelist] | |
495 | needed = {} |
|
646 | needed = dict.fromkeys(revs, 1) | |
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 |
|
|||
502 |
|
647 | |||
503 | # if we don't have any revisions touched by these changesets, bail |
|
648 | # if we don't have any revisions touched by these changesets, bail | |
504 | if not revs: |
|
649 | if not revs: | |
@@ -566,6 +711,9 b' class revlog:' | |||||
566 | a, b = revs[d], revs[d + 1] |
|
711 | a, b = revs[d], revs[d + 1] | |
567 | n = self.node(b) |
|
712 | n = self.node(b) | |
568 |
|
713 | |||
|
714 | if infocollect is not None: | |||
|
715 | infocollect(n) | |||
|
716 | ||||
569 | # do we need to construct a new delta? |
|
717 | # do we need to construct a new delta? | |
570 | if a + 1 != b or self.base(b) == b: |
|
718 | if a + 1 != b or self.base(b) == b: | |
571 | if a >= 0: |
|
719 | if a >= 0: | |
@@ -587,7 +735,7 b' class revlog:' | |||||
587 | d = chunks[b] |
|
735 | d = chunks[b] | |
588 |
|
736 | |||
589 | p = self.parents(n) |
|
737 | p = self.parents(n) | |
590 |
meta = n + p[0] + p[1] + l |
|
738 | meta = n + p[0] + p[1] + lookup(n) | |
591 | l = struct.pack(">l", len(meta) + len(d) + 4) |
|
739 | l = struct.pack(">l", len(meta) + len(d) + 4) | |
592 | yield l |
|
740 | yield l | |
593 | yield meta |
|
741 | yield meta |
General Comments 0
You need to be logged in to leave comments.
Login now