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