comparison mercurial/localrepo.py @ 1469:0847c45ffee6

Merge bundle -r work from Eric Hopper
author Matt Mackall <mpm@selenic.com>
date Thu, 27 Oct 2005 12:26:16 -0700
parents 182879d71922 b6d9ea0bc107
children 7d66ce9895fa
comparison
equal deleted inserted replaced
1456:214f42f23a3b 1469:0847c45ffee6
725 725
726 r.append(l) 726 r.append(l)
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 = []
759 fetch = {} 733 fetch = {}
760 seen = {} 734 seen = {}
901 subset.append(n) 875 subset.append(n)
902 876
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
910 if self.changelog.tip() == nullid: 884 if self.changelog.tip() == nullid:
911 self.ui.status(_("requesting all changes\n")) 885 self.ui.status(_("requesting all changes\n"))
915 889
916 if not fetch: 890 if not fetch:
917 self.ui.status(_("no changes found\n")) 891 self.ui.status(_("no changes found\n"))
918 return 1 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 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):
924 lock = remote.lock() 901 lock = remote.lock()
925 902
943 return 1 920 return 1
944 921
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)
953
954 # construct the link map
955 linkmap = {}
956 for n in nodes:
957 linkmap[self.changelog.rev(n)] = n
958
959 # construct a list of all changed files 1219 # construct a list of all changed files
960 changed = {} 1220 changedfiles = {}
961 for n in nodes: 1221
962 c = self.changelog.read(n) 1222 for chnk in cl.group(nodes, identity,
963 for f in c[3]: 1223 changed_file_collector(changedfiles)):
964 changed[f] = 1 1224 yield chnk
965 changed = changed.keys() 1225 changedfiles = changedfiles.keys()
966 changed.sort() 1226 changedfiles.sort()
967 1227
968 # the changegroup is changesets + manifests + all file revs 1228 mnfst = self.manifest
969 revs = [ self.changelog.rev(n) for n in nodes ] 1229 nodeiter = gennodelst(mnfst)
970 1230 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
971 for y in self.changelog.group(linkmap): yield y 1231 yield chnk
972 for y in self.manifest.group(linkmap): yield y 1232
973 for f in changed: 1233 for fname in changedfiles:
974 yield struct.pack(">l", len(f) + 4) + f 1234 filerevlog = self.file(fname)
975 g = self.file(f).group(linkmap) 1235 nodeiter = gennodelst(filerevlog)
976 for y in g: 1236 nodeiter = list(nodeiter)
977 yield y 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 yield struct.pack(">l", 0) 1243 yield struct.pack(">l", 0)
980 1244
981 return genread(gengroup()) 1245 return util.chunkbuffer(gengroup())
982 1246
983 def addchangegroup(self, source): 1247 def addchangegroup(self, source):
984 1248
985 def getchunk(): 1249 def getchunk():
986 d = source.read(4) 1250 d = source.read(4)