##// END OF EJS Templates
git: correctly check for type of object when walking
Josef 'Jeff' Sipek -
r45447:935c9f34 default
parent child Browse files
Show More
@@ -1,297 +1,297 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 == r'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 if not match(realname):
177 continue
177 continue
178 yield pycompat.fsencode(realname)
178 yield pycompat.fsencode(realname)
179
179
180 def walk(self, match):
180 def walk(self, match):
181 # TODO: this is a very lazy way to merge in the pending
181 # TODO: this is a very lazy way to merge in the pending
182 # changes. There is absolutely room for optimization here by
182 # changes. There is absolutely room for optimization here by
183 # being clever about walking over the sets...
183 # being clever about walking over the sets...
184 baseline = set(self._walkonetree(self._tree, match, b''))
184 baseline = set(self._walkonetree(self._tree, match, b''))
185 deleted = {p for p, v in self._pending_changes.items() if v is None}
185 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)}
186 pend = {p for p in self._pending_changes if match(p)}
187 return iter(sorted((baseline | pend) - deleted))
187 return iter(sorted((baseline | pend) - deleted))
188
188
189
189
190 @interfaceutil.implementer(repository.imanifestrevisionstored)
190 @interfaceutil.implementer(repository.imanifestrevisionstored)
191 class gittreemanifestctx(object):
191 class gittreemanifestctx(object):
192 def __init__(self, repo, gittree):
192 def __init__(self, repo, gittree):
193 self._repo = repo
193 self._repo = repo
194 self._tree = gittree
194 self._tree = gittree
195
195
196 def read(self):
196 def read(self):
197 return gittreemanifest(self._repo, self._tree, None)
197 return gittreemanifest(self._repo, self._tree, None)
198
198
199 def readfast(self, shallow=False):
199 def readfast(self, shallow=False):
200 return self.read()
200 return self.read()
201
201
202 def copy(self):
202 def copy(self):
203 # NB: it's important that we return a memgittreemanifestctx
203 # NB: it's important that we return a memgittreemanifestctx
204 # because the caller expects a mutable manifest.
204 # because the caller expects a mutable manifest.
205 return memgittreemanifestctx(self._repo, self._tree)
205 return memgittreemanifestctx(self._repo, self._tree)
206
206
207 def find(self, path):
207 def find(self, path):
208 return self.read()[path]
208 return self.read()[path]
209
209
210
210
211 @interfaceutil.implementer(repository.imanifestrevisionwritable)
211 @interfaceutil.implementer(repository.imanifestrevisionwritable)
212 class memgittreemanifestctx(object):
212 class memgittreemanifestctx(object):
213 def __init__(self, repo, tree):
213 def __init__(self, repo, tree):
214 self._repo = repo
214 self._repo = repo
215 self._tree = tree
215 self._tree = tree
216 # dict of path: Optional[Tuple(node, flags)]
216 # dict of path: Optional[Tuple(node, flags)]
217 self._pending_changes = {}
217 self._pending_changes = {}
218
218
219 def read(self):
219 def read(self):
220 return gittreemanifest(self._repo, self._tree, self._pending_changes)
220 return gittreemanifest(self._repo, self._tree, self._pending_changes)
221
221
222 def copy(self):
222 def copy(self):
223 # TODO: if we have a builder in play, what should happen here?
223 # TODO: if we have a builder in play, what should happen here?
224 # Maybe we can shuffle copy() into the immutable interface.
224 # Maybe we can shuffle copy() into the immutable interface.
225 return memgittreemanifestctx(self._repo, self._tree)
225 return memgittreemanifestctx(self._repo, self._tree)
226
226
227 def write(self, transaction, link, p1, p2, added, removed, match=None):
227 def write(self, transaction, link, p1, p2, added, removed, match=None):
228 # We're not (for now, anyway) going to audit filenames, so we
228 # We're not (for now, anyway) going to audit filenames, so we
229 # can ignore added and removed.
229 # can ignore added and removed.
230
230
231 # TODO what does this match argument get used for? hopefully
231 # TODO what does this match argument get used for? hopefully
232 # just narrow?
232 # just narrow?
233 assert not match or isinstance(match, matchmod.alwaysmatcher)
233 assert not match or isinstance(match, matchmod.alwaysmatcher)
234
234
235 touched_dirs = pathutil.dirs(list(self._pending_changes))
235 touched_dirs = pathutil.dirs(list(self._pending_changes))
236 trees = {
236 trees = {
237 b'': self._tree,
237 b'': self._tree,
238 }
238 }
239 # path: treebuilder
239 # path: treebuilder
240 builders = {
240 builders = {
241 b'': self._repo.TreeBuilder(self._tree),
241 b'': self._repo.TreeBuilder(self._tree),
242 }
242 }
243 # get a TreeBuilder for every tree in the touched_dirs set
243 # get a TreeBuilder for every tree in the touched_dirs set
244 for d in sorted(touched_dirs, key=lambda x: (len(x), x)):
244 for d in sorted(touched_dirs, key=lambda x: (len(x), x)):
245 if d == b'':
245 if d == b'':
246 # loaded root tree above
246 # loaded root tree above
247 continue
247 continue
248 comps = d.split(b'/')
248 comps = d.split(b'/')
249 full = b''
249 full = b''
250 for part in comps:
250 for part in comps:
251 parent = trees[full]
251 parent = trees[full]
252 try:
252 try:
253 new = self._repo[parent[pycompat.fsdecode(part)]]
253 new = self._repo[parent[pycompat.fsdecode(part)]]
254 except KeyError:
254 except KeyError:
255 # new directory
255 # new directory
256 new = None
256 new = None
257 full += b'/' + part
257 full += b'/' + part
258 if new is not None:
258 if new is not None:
259 # existing directory
259 # existing directory
260 trees[full] = new
260 trees[full] = new
261 builders[full] = self._repo.TreeBuilder(new)
261 builders[full] = self._repo.TreeBuilder(new)
262 else:
262 else:
263 # new directory, use an empty dict to easily
263 # new directory, use an empty dict to easily
264 # generate KeyError as any nested new dirs get
264 # generate KeyError as any nested new dirs get
265 # created.
265 # created.
266 trees[full] = {}
266 trees[full] = {}
267 builders[full] = self._repo.TreeBuilder()
267 builders[full] = self._repo.TreeBuilder()
268 for f, info in self._pending_changes.items():
268 for f, info in self._pending_changes.items():
269 if b'/' not in f:
269 if b'/' not in f:
270 dirname = b''
270 dirname = b''
271 basename = f
271 basename = f
272 else:
272 else:
273 dirname, basename = f.rsplit(b'/', 1)
273 dirname, basename = f.rsplit(b'/', 1)
274 dirname = b'/' + dirname
274 dirname = b'/' + dirname
275 if info is None:
275 if info is None:
276 builders[dirname].remove(pycompat.fsdecode(basename))
276 builders[dirname].remove(pycompat.fsdecode(basename))
277 else:
277 else:
278 n, fl = info
278 n, fl = info
279 mode = {
279 mode = {
280 b'': pygit2.GIT_FILEMODE_BLOB,
280 b'': pygit2.GIT_FILEMODE_BLOB,
281 b'x': pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
281 b'x': pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
282 b'l': pygit2.GIT_FILEMODE_LINK,
282 b'l': pygit2.GIT_FILEMODE_LINK,
283 }[fl]
283 }[fl]
284 builders[dirname].insert(
284 builders[dirname].insert(
285 pycompat.fsdecode(basename), gitutil.togitnode(n), mode
285 pycompat.fsdecode(basename), gitutil.togitnode(n), mode
286 )
286 )
287 # This visits the buffered TreeBuilders in deepest-first
287 # This visits the buffered TreeBuilders in deepest-first
288 # order, bubbling up the edits.
288 # order, bubbling up the edits.
289 for b in sorted(builders, key=len, reverse=True):
289 for b in sorted(builders, key=len, reverse=True):
290 if b == b'':
290 if b == b'':
291 break
291 break
292 cb = builders[b]
292 cb = builders[b]
293 dn, bn = b.rsplit(b'/', 1)
293 dn, bn = b.rsplit(b'/', 1)
294 builders[dn].insert(
294 builders[dn].insert(
295 pycompat.fsdecode(bn), cb.write(), pygit2.GIT_FILEMODE_TREE
295 pycompat.fsdecode(bn), cb.write(), pygit2.GIT_FILEMODE_TREE
296 )
296 )
297 return builders[b''].write().raw
297 return builders[b''].write().raw
General Comments 0
You need to be logged in to leave comments. Login now