##// END OF EJS Templates
git: add readfast() method to manifest...
Augie Fackler -
r44963:7518ea76 default
parent child Browse files
Show More
@@ -1,293 +1,296
1 1 from __future__ import absolute_import
2 2
3 3 import pygit2
4 4
5 5 from mercurial import (
6 6 match as matchmod,
7 7 pathutil,
8 8 pycompat,
9 9 util,
10 10 )
11 11 from mercurial.interfaces import (
12 12 repository,
13 13 util as interfaceutil,
14 14 )
15 15 from . import gitutil
16 16
17 17
18 18 @interfaceutil.implementer(repository.imanifestdict)
19 19 class gittreemanifest(object):
20 20 """Expose git trees (and optionally a builder's overlay) as a manifestdict.
21 21
22 22 Very similar to mercurial.manifest.treemanifest.
23 23 """
24 24
25 25 def __init__(self, git_repo, root_tree, pending_changes):
26 26 """Initializer.
27 27
28 28 Args:
29 29 git_repo: The git_repo we're walking (required to look up child
30 30 trees).
31 31 root_tree: The root Git tree object for this manifest.
32 32 pending_changes: A dict in which pending changes will be
33 33 tracked. The enclosing memgittreemanifestctx will use this to
34 34 construct any required Tree objects in Git during it's
35 35 `write()` method.
36 36 """
37 37 self._git_repo = git_repo
38 38 self._tree = root_tree
39 39 if pending_changes is None:
40 40 pending_changes = {}
41 41 # dict of path: Optional[Tuple(node, flags)]
42 42 self._pending_changes = pending_changes
43 43
44 44 def _resolve_entry(self, path):
45 45 """Given a path, load its node and flags, or raise KeyError if missing.
46 46
47 47 This takes into account any pending writes in the builder.
48 48 """
49 49 upath = pycompat.fsdecode(path)
50 50 ent = None
51 51 if path in self._pending_changes:
52 52 val = self._pending_changes[path]
53 53 if val is None:
54 54 raise KeyError
55 55 return val
56 56 t = self._tree
57 57 comps = upath.split('/')
58 58 for comp in comps[:-1]:
59 59 te = self._tree[comp]
60 60 t = self._git_repo[te.id]
61 61 ent = t[comps[-1]]
62 62 if ent.filemode == pygit2.GIT_FILEMODE_BLOB:
63 63 flags = b''
64 64 elif ent.filemode == pygit2.GIT_FILEMODE_BLOB_EXECUTABLE:
65 65 flags = b'x'
66 66 elif ent.filemode == pygit2.GIT_FILEMODE_LINK:
67 67 flags = b'l'
68 68 else:
69 69 raise ValueError('unsupported mode %s' % oct(ent.filemode))
70 70 return ent.id.raw, flags
71 71
72 72 def __getitem__(self, path):
73 73 return self._resolve_entry(path)[0]
74 74
75 75 def find(self, path):
76 76 return self._resolve_entry(path)
77 77
78 78 def __len__(self):
79 79 return len(list(self.walk(matchmod.always())))
80 80
81 81 def __nonzero__(self):
82 82 try:
83 83 next(iter(self))
84 84 return True
85 85 except StopIteration:
86 86 return False
87 87
88 88 __bool__ = __nonzero__
89 89
90 90 def __contains__(self, path):
91 91 try:
92 92 self._resolve_entry(path)
93 93 return True
94 94 except KeyError:
95 95 return False
96 96
97 97 def iterkeys(self):
98 98 return self.walk(matchmod.always())
99 99
100 100 def keys(self):
101 101 return list(self.iterkeys())
102 102
103 103 def __iter__(self):
104 104 return self.iterkeys()
105 105
106 106 def __setitem__(self, path, node):
107 107 self._pending_changes[path] = node, self.flags(path)
108 108
109 109 def __delitem__(self, path):
110 110 # TODO: should probably KeyError for already-deleted files?
111 111 self._pending_changes[path] = None
112 112
113 113 def filesnotin(self, other, match=None):
114 114 if match is not None:
115 115 match = matchmod.badmatch(match, lambda path, msg: None)
116 116 sm2 = set(other.walk(match))
117 117 return {f for f in self.walk(match) if f not in sm2}
118 118 return {f for f in self if f not in other}
119 119
120 120 @util.propertycache
121 121 def _dirs(self):
122 122 return pathutil.dirs(self)
123 123
124 124 def hasdir(self, dir):
125 125 return dir in self._dirs
126 126
127 127 def diff(self, other, match=None, clean=False):
128 128 # TODO
129 129 assert False
130 130
131 131 def setflag(self, path, flag):
132 132 node, unused_flag = self._resolve_entry(path)
133 133 self._pending_changes[path] = node, flag
134 134
135 135 def get(self, path, default=None):
136 136 try:
137 137 return self._resolve_entry(path)[0]
138 138 except KeyError:
139 139 return default
140 140
141 141 def flags(self, path):
142 142 try:
143 143 return self._resolve_entry(path)[1]
144 144 except KeyError:
145 145 return b''
146 146
147 147 def copy(self):
148 148 pass
149 149
150 150 def items(self):
151 151 for f in self:
152 152 # TODO: build a proper iterator version of this
153 153 yield self[f]
154 154
155 155 def iteritems(self):
156 156 return self.items()
157 157
158 158 def iterentries(self):
159 159 for f in self:
160 160 # TODO: build a proper iterator version of this
161 161 yield self._resolve_entry(f)
162 162
163 163 def text(self):
164 164 assert False # TODO can this method move out of the manifest iface?
165 165
166 166 def _walkonetree(self, tree, match, subdir):
167 167 for te in tree:
168 168 # TODO: can we prune dir walks with the matcher?
169 169 realname = subdir + pycompat.fsencode(te.name)
170 170 if te.type == r'tree':
171 171 for inner in self._walkonetree(
172 172 self._git_repo[te.id], match, realname + b'/'
173 173 ):
174 174 yield inner
175 175 if not match(realname):
176 176 continue
177 177 yield pycompat.fsencode(realname)
178 178
179 179 def walk(self, match):
180 180 # TODO: this is a very lazy way to merge in the pending
181 181 # changes. There is absolutely room for optimization here by
182 182 # being clever about walking over the sets...
183 183 baseline = set(self._walkonetree(self._tree, match, b''))
184 184 deleted = {p for p, v in self._pending_changes.items() if v is None}
185 185 pend = {p for p in self._pending_changes if match(p)}
186 186 return iter(sorted((baseline | pend) - deleted))
187 187
188 188
189 189 @interfaceutil.implementer(repository.imanifestrevisionstored)
190 190 class gittreemanifestctx(object):
191 191 def __init__(self, repo, gittree):
192 192 self._repo = repo
193 193 self._tree = gittree
194 194
195 195 def read(self):
196 196 return gittreemanifest(self._repo, self._tree, None)
197 197
198 def readfast(self, shallow=False):
199 return self.read()
200
198 201 def copy(self):
199 202 # NB: it's important that we return a memgittreemanifestctx
200 203 # because the caller expects a mutable manifest.
201 204 return memgittreemanifestctx(self._repo, self._tree)
202 205
203 206 def find(self, path):
204 207 self.read()[path]
205 208
206 209
207 210 @interfaceutil.implementer(repository.imanifestrevisionwritable)
208 211 class memgittreemanifestctx(object):
209 212 def __init__(self, repo, tree):
210 213 self._repo = repo
211 214 self._tree = tree
212 215 # dict of path: Optional[Tuple(node, flags)]
213 216 self._pending_changes = {}
214 217
215 218 def read(self):
216 219 return gittreemanifest(self._repo, self._tree, self._pending_changes)
217 220
218 221 def copy(self):
219 222 # TODO: if we have a builder in play, what should happen here?
220 223 # Maybe we can shuffle copy() into the immutable interface.
221 224 return memgittreemanifestctx(self._repo, self._tree)
222 225
223 226 def write(self, transaction, link, p1, p2, added, removed, match=None):
224 227 # We're not (for now, anyway) going to audit filenames, so we
225 228 # can ignore added and removed.
226 229
227 230 # TODO what does this match argument get used for? hopefully
228 231 # just narrow?
229 232 assert not match or isinstance(match, matchmod.alwaysmatcher)
230 233
231 234 touched_dirs = pathutil.dirs(self._pending_changes)
232 235 trees = {
233 236 b'': self._tree,
234 237 }
235 238 # path: treebuilder
236 239 builders = {
237 240 b'': self._repo.TreeBuilder(self._tree),
238 241 }
239 242 # get a TreeBuilder for every tree in the touched_dirs set
240 243 for d in sorted(touched_dirs, key=lambda x: (len(x), x)):
241 244 if d == b'':
242 245 # loaded root tree above
243 246 continue
244 247 comps = d.split(b'/')
245 248 full = b''
246 249 for part in comps:
247 250 parent = trees[full]
248 251 try:
249 252 new = self._repo[parent[pycompat.fsdecode(part)]]
250 253 except KeyError:
251 254 # new directory
252 255 new = None
253 256 full += b'/' + part
254 257 if new is not None:
255 258 # existing directory
256 259 trees[full] = new
257 260 builders[full] = self._repo.TreeBuilder(new)
258 261 else:
259 262 # new directory, use an empty dict to easily
260 263 # generate KeyError as any nested new dirs get
261 264 # created.
262 265 trees[full] = {}
263 266 builders[full] = self._repo.TreeBuilder()
264 267 for f, info in self._pending_changes.items():
265 268 if b'/' not in f:
266 269 dirname = b''
267 270 basename = f
268 271 else:
269 272 dirname, basename = f.rsplit(b'/', 1)
270 273 dirname = b'/' + dirname
271 274 if info is None:
272 275 builders[dirname].remove(pycompat.fsdecode(basename))
273 276 else:
274 277 n, fl = info
275 278 mode = {
276 279 b'': pygit2.GIT_FILEMODE_BLOB,
277 280 b'x': pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
278 281 b'l': pygit2.GIT_FILEMODE_LINK,
279 282 }[fl]
280 283 builders[dirname].insert(
281 284 pycompat.fsdecode(basename), gitutil.togitnode(n), mode
282 285 )
283 286 # This visits the buffered TreeBuilders in deepest-first
284 287 # order, bubbling up the edits.
285 288 for b in sorted(builders, key=len, reverse=True):
286 289 if b == b'':
287 290 break
288 291 cb = builders[b]
289 292 dn, bn = b.rsplit(b'/', 1)
290 293 builders[dn].insert(
291 294 pycompat.fsdecode(bn), cb.write(), pygit2.GIT_FILEMODE_TREE
292 295 )
293 296 return builders[b''].write().raw
General Comments 0
You need to be logged in to leave comments. Login now