135
|
1 # manifest.py - manifest revision class for mercurial
|
|
2 #
|
|
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
|
|
4 #
|
|
5 # This software may be used and distributed according to the terms of the
|
|
6 # GNU General Public License version 2, incorporated herein by reference.
|
|
7
|
|
8 from i18n import _
|
|
9 import mdiff, parsers, error, revlog
|
|
10 import array, struct
|
|
11
|
|
12 class manifestdict(dict):
|
|
13 def __init__(self, mapping=None, flags=None):
|
|
14 if mapping is None: mapping = {}
|
|
15 if flags is None: flags = {}
|
|
16 dict.__init__(self, mapping)
|
|
17 self._flags = flags
|
|
18 def flags(self, f):
|
|
19 return self._flags.get(f, "")
|
|
20 def set(self, f, flags):
|
|
21 self._flags[f] = flags
|
|
22 def copy(self):
|
|
23 return manifestdict(dict.copy(self), dict.copy(self._flags))
|
|
24
|
|
25 class manifest(revlog.revlog):
|
|
26 def __init__(self, opener):
|
|
27 self.mapcache = None
|
|
28 self.listcache = None
|
|
29 revlog.revlog.__init__(self, opener, "00manifest.i")
|
|
30
|
|
31 def parse(self, lines):
|
|
32 mfdict = manifestdict()
|
|
33 parsers.parse_manifest(mfdict, mfdict._flags, lines)
|
|
34 return mfdict
|
|
35
|
|
36 def readdelta(self, node):
|
|
37 r = self.rev(node)
|
|
38 return self.parse(mdiff.patchtext(self.revdiff(r - 1, r)))
|
|
39
|
|
40 def read(self, node):
|
|
41 if node == revlog.nullid:
|
|
42 return manifestdict() # don't upset local cache
|
|
43 if self.mapcache and self.mapcache[0] == node:
|
|
44 return self.mapcache[1]
|
|
45 text = self.revision(node)
|
|
46 self.listcache = array.array('c', text)
|
|
47 mapping = self.parse(text)
|
|
48 self.mapcache = (node, mapping)
|
|
49 return mapping
|
|
50
|
|
51 def _search(self, m, s, lo=0, hi=None):
|
|
52 '''return a tuple (start, end) that says where to find s within m.
|
|
53
|
|
54 If the string is found m[start:end] are the line containing
|
|
55 that string. If start == end the string was not found and
|
|
56 they indicate the proper sorted insertion point. This was
|
|
57 taken from bisect_left, and modified to find line start/end as
|
|
58 it goes along.
|
|
59
|
|
60 m should be a buffer or a string
|
|
61 s is a string'''
|
|
62 def advance(i, c):
|
|
63 while i < lenm and m[i] != c:
|
|
64 i += 1
|
|
65 return i
|
|
66 if not s:
|
|
67 return (lo, lo)
|
|
68 lenm = len(m)
|
|
69 if not hi:
|
|
70 hi = lenm
|
|
71 while lo < hi:
|
|
72 mid = (lo + hi) // 2
|
|
73 start = mid
|
|
74 while start > 0 and m[start-1] != '\n':
|
|
75 start -= 1
|
|
76 end = advance(start, '\0')
|
|
77 if m[start:end] < s:
|
|
78 # we know that after the null there are 40 bytes of sha1
|
|
79 # this translates to the bisect lo = mid + 1
|
|
80 lo = advance(end + 40, '\n') + 1
|
|
81 else:
|
|
82 # this translates to the bisect hi = mid
|
|
83 hi = start
|
|
84 end = advance(lo, '\0')
|
|
85 found = m[lo:end]
|
|
86 if cmp(s, found) == 0:
|
|
87 # we know that after the null there are 40 bytes of sha1
|
|
88 end = advance(end + 40, '\n')
|
|
89 return (lo, end+1)
|
|
90 else:
|
|
91 return (lo, lo)
|
|
92
|
|
93 def find(self, node, f):
|
|
94 '''look up entry for a single file efficiently.
|
|
95 return (node, flags) pair if found, (None, None) if not.'''
|
|
96 if self.mapcache and node == self.mapcache[0]:
|
|
97 return self.mapcache[1].get(f), self.mapcache[1].flags(f)
|
|
98 text = self.revision(node)
|
|
99 start, end = self._search(text, f)
|
|
100 if start == end:
|
|
101 return None, None
|
|
102 l = text[start:end]
|
|
103 f, n = l.split('\0')
|
|
104 return revlog.bin(n[:40]), n[40:-1]
|
|
105
|
|
106 def add(self, map, transaction, link, p1=None, p2=None,
|
|
107 changed=None):
|
|
108 # apply the changes collected during the bisect loop to our addlist
|
|
109 # return a delta suitable for addrevision
|
|
110 def addlistdelta(addlist, x):
|
|
111 # start from the bottom up
|
|
112 # so changes to the offsets don't mess things up.
|
|
113 i = len(x)
|
|
114 while i > 0:
|
|
115 i -= 1
|
|
116 start = x[i][0]
|
|
117 end = x[i][1]
|
|
118 if x[i][2]:
|
|
119 addlist[start:end] = array.array('c', x[i][2])
|
|
120 else:
|
|
121 del addlist[start:end]
|
|
122 return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2]
|
|
123 for d in x ])
|
|
124
|
|
125 def checkforbidden(l):
|
|
126 for f in l:
|
|
127 if '\n' in f or '\r' in f:
|
|
128 raise error.RevlogError(
|
|
129 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
|
|
130
|
|
131 # if we're using the listcache, make sure it is valid and
|
|
132 # parented by the same node we're diffing against
|
|
133 if not (changed and self.listcache and p1 and self.mapcache[0] == p1):
|
|
134 files = sorted(map)
|
|
135 checkforbidden(files)
|
|
136
|
|
137 # if this is changed to support newlines in filenames,
|
|
138 # be sure to check the templates/ dir again (especially *-raw.tmpl)
|
|
139 hex, flags = revlog.hex, map.flags
|
|
140 text = ["%s\000%s%s\n" % (f, hex(map[f]), flags(f))
|
|
141 for f in files]
|
|
142 self.listcache = array.array('c', "".join(text))
|
|
143 cachedelta = None
|
|
144 else:
|
|
145 addlist = self.listcache
|
|
146
|
|
147 checkforbidden(changed[0])
|
|
148 # combine the changed lists into one list for sorting
|
|
149 work = [[x, 0] for x in changed[0]]
|
|
150 work[len(work):] = [[x, 1] for x in changed[1]]
|
|
151 work.sort()
|
|
152
|
|
153 delta = []
|
|
154 dstart = None
|
|
155 dend = None
|
|
156 dline = [""]
|
|
157 start = 0
|
|
158 # zero copy representation of addlist as a buffer
|
|
159 addbuf = buffer(addlist)
|
|
160
|
|
161 # start with a readonly loop that finds the offset of
|
|
162 # each line and creates the deltas
|
|
163 for w in work:
|
|
164 f = w[0]
|
|
165 # bs will either be the index of the item or the insert point
|
|
166 start, end = self._search(addbuf, f, start)
|
|
167 if w[1] == 0:
|
|
168 l = "%s\000%s%s\n" % (f, revlog.hex(map[f]), map.flags(f))
|
|
169 else:
|
|
170 l = ""
|
|
171 if start == end and w[1] == 1:
|
|
172 # item we want to delete was not found, error out
|
|
173 raise AssertionError(
|
|
174 _("failed to remove %s from manifest") % f)
|
|
175 if dstart != None and dstart <= start and dend >= start:
|
|
176 if dend < end:
|
|
177 dend = end
|
|
178 if l:
|
|
179 dline.append(l)
|
|
180 else:
|
|
181 if dstart != None:
|
|
182 delta.append([dstart, dend, "".join(dline)])
|
|
183 dstart = start
|
|
184 dend = end
|
|
185 dline = [l]
|
|
186
|
|
187 if dstart != None:
|
|
188 delta.append([dstart, dend, "".join(dline)])
|
|
189 # apply the delta to the addlist, and get a delta for addrevision
|
|
190 cachedelta = addlistdelta(addlist, delta)
|
|
191
|
|
192 # the delta is only valid if we've been processing the tip revision
|
|
193 if self.mapcache[0] != self.tip():
|
|
194 cachedelta = None
|
|
195 self.listcache = addlist
|
|
196
|
|
197 n = self.addrevision(buffer(self.listcache), transaction, link,
|
|
198 p1, p2, cachedelta)
|
|
199 self.mapcache = (n, map)
|
|
200
|
|
201 return n
|