##// END OF EJS Templates
git: add missing `repository.imanifestdict` methods to `gittreemanifest`...
Matt Harbison -
r53391:db6efd74 default
parent child Browse files
Show More
@@ -1,389 +1,401
1 from __future__ import annotations
1 from __future__ import annotations
2
2
3 import typing
3 import typing
4
4
5 from typing import (
5 from typing import (
6 Any,
6 Any,
7 Iterable,
7 Iterator,
8 Iterator,
8 Set,
9 Set,
9 )
10 )
10
11
11 from mercurial import (
12 from mercurial import (
12 match as matchmod,
13 match as matchmod,
13 pathutil,
14 pathutil,
14 pycompat,
15 pycompat,
15 util,
16 util,
16 )
17 )
17 from mercurial.interfaces import (
18 from mercurial.interfaces import (
18 repository,
19 repository,
19 util as interfaceutil,
20 util as interfaceutil,
20 )
21 )
21 from . import gitutil
22 from . import gitutil
22
23
23 if typing.TYPE_CHECKING:
24 if typing.TYPE_CHECKING:
24 from typing import (
25 from typing import (
25 ByteString, # TODO: change to Buffer for 3.14
26 ByteString, # TODO: change to Buffer for 3.14
26 )
27 )
27
28
28 pygit2 = gitutil.get_pygit2()
29 pygit2 = gitutil.get_pygit2()
29
30
30
31
31 @interfaceutil.implementer(repository.imanifestdict)
32 @interfaceutil.implementer(repository.imanifestdict)
32 class gittreemanifest:
33 class gittreemanifest:
33 """Expose git trees (and optionally a builder's overlay) as a manifestdict.
34 """Expose git trees (and optionally a builder's overlay) as a manifestdict.
34
35
35 Very similar to mercurial.manifest.treemanifest.
36 Very similar to mercurial.manifest.treemanifest.
36 """
37 """
37
38
38 def __init__(self, git_repo, root_tree, pending_changes):
39 def __init__(self, git_repo, root_tree, pending_changes):
39 """Initializer.
40 """Initializer.
40
41
41 Args:
42 Args:
42 git_repo: The git_repo we're walking (required to look up child
43 git_repo: The git_repo we're walking (required to look up child
43 trees).
44 trees).
44 root_tree: The root Git tree object for this manifest.
45 root_tree: The root Git tree object for this manifest.
45 pending_changes: A dict in which pending changes will be
46 pending_changes: A dict in which pending changes will be
46 tracked. The enclosing memgittreemanifestctx will use this to
47 tracked. The enclosing memgittreemanifestctx will use this to
47 construct any required Tree objects in Git during it's
48 construct any required Tree objects in Git during it's
48 `write()` method.
49 `write()` method.
49 """
50 """
50 self._git_repo = git_repo
51 self._git_repo = git_repo
51 self._tree = root_tree
52 self._tree = root_tree
52 if pending_changes is None:
53 if pending_changes is None:
53 pending_changes = {}
54 pending_changes = {}
54 # dict of path: Optional[Tuple(node, flags)]
55 # dict of path: Optional[Tuple(node, flags)]
55 self._pending_changes = pending_changes
56 self._pending_changes = pending_changes
56
57
57 def _resolve_entry(self, path) -> tuple[bytes, bytes]:
58 def _resolve_entry(self, path) -> tuple[bytes, bytes]:
58 """Given a path, load its node and flags, or raise KeyError if missing.
59 """Given a path, load its node and flags, or raise KeyError if missing.
59
60
60 This takes into account any pending writes in the builder.
61 This takes into account any pending writes in the builder.
61 """
62 """
62 upath = pycompat.fsdecode(path)
63 upath = pycompat.fsdecode(path)
63 ent = None
64 ent = None
64 if path in self._pending_changes:
65 if path in self._pending_changes:
65 val = self._pending_changes[path]
66 val = self._pending_changes[path]
66 if val is None:
67 if val is None:
67 raise KeyError
68 raise KeyError
68 return val
69 return val
69 t = self._tree
70 t = self._tree
70 comps = upath.split('/')
71 comps = upath.split('/')
71 te = self._tree
72 te = self._tree
72 for comp in comps[:-1]:
73 for comp in comps[:-1]:
73 te = te[comp]
74 te = te[comp]
74 t = self._git_repo[te.id]
75 t = self._git_repo[te.id]
75 ent = t[comps[-1]]
76 ent = t[comps[-1]]
76 if ent.filemode == pygit2.GIT_FILEMODE_BLOB:
77 if ent.filemode == pygit2.GIT_FILEMODE_BLOB:
77 flags = b''
78 flags = b''
78 elif ent.filemode == pygit2.GIT_FILEMODE_BLOB_EXECUTABLE:
79 elif ent.filemode == pygit2.GIT_FILEMODE_BLOB_EXECUTABLE:
79 flags = b'x'
80 flags = b'x'
80 elif ent.filemode == pygit2.GIT_FILEMODE_LINK:
81 elif ent.filemode == pygit2.GIT_FILEMODE_LINK:
81 flags = b'l'
82 flags = b'l'
82 else:
83 else:
83 raise ValueError('unsupported mode %s' % oct(ent.filemode))
84 raise ValueError('unsupported mode %s' % oct(ent.filemode))
84 return ent.id.raw, flags
85 return ent.id.raw, flags
85
86
86 def __getitem__(self, path: bytes) -> bytes:
87 def __getitem__(self, path: bytes) -> bytes:
87 return self._resolve_entry(path)[0]
88 return self._resolve_entry(path)[0]
88
89
89 def find(self, path: bytes) -> tuple[bytes, bytes]:
90 def find(self, path: bytes) -> tuple[bytes, bytes]:
90 return self._resolve_entry(path)
91 return self._resolve_entry(path)
91
92
92 def __len__(self) -> int:
93 def __len__(self) -> int:
93 return len(list(self.walk(matchmod.always())))
94 return len(list(self.walk(matchmod.always())))
94
95
95 def __nonzero__(self) -> bool:
96 def __nonzero__(self) -> bool:
96 try:
97 try:
97 next(iter(self))
98 next(iter(self))
98 return True
99 return True
99 except StopIteration:
100 except StopIteration:
100 return False
101 return False
101
102
102 __bool__ = __nonzero__
103 __bool__ = __nonzero__
103
104
104 def __contains__(self, path: bytes) -> bool:
105 def __contains__(self, path: bytes) -> bool:
105 try:
106 try:
106 self._resolve_entry(path)
107 self._resolve_entry(path)
107 return True
108 return True
108 except KeyError:
109 except KeyError:
109 return False
110 return False
110
111
111 def iterkeys(self) -> Iterator[bytes]:
112 def iterkeys(self) -> Iterator[bytes]:
112 return self.walk(matchmod.always())
113 return self.walk(matchmod.always())
113
114
114 def keys(self) -> list[bytes]:
115 def keys(self) -> list[bytes]:
115 return list(self.iterkeys())
116 return list(self.iterkeys())
116
117
117 def __iter__(self) -> Iterator[bytes]:
118 def __iter__(self) -> Iterator[bytes]:
118 return self.iterkeys()
119 return self.iterkeys()
119
120
121 def set(self, path: bytes, node: bytes, flags: bytes) -> None:
122 raise NotImplementedError # TODO: implement this
123
120 def __setitem__(self, path: bytes, node: bytes) -> None:
124 def __setitem__(self, path: bytes, node: bytes) -> None:
121 self._pending_changes[path] = node, self.flags(path)
125 self._pending_changes[path] = node, self.flags(path)
122
126
123 def __delitem__(self, path: bytes) -> None:
127 def __delitem__(self, path: bytes) -> None:
124 # TODO: should probably KeyError for already-deleted files?
128 # TODO: should probably KeyError for already-deleted files?
125 self._pending_changes[path] = None
129 self._pending_changes[path] = None
126
130
127 def filesnotin(self, other, match=None) -> Set[bytes]:
131 def filesnotin(self, other, match=None) -> Set[bytes]:
128 if match is not None:
132 if match is not None:
129 match = matchmod.badmatch(match, lambda path, msg: None)
133 match = matchmod.badmatch(match, lambda path, msg: None)
130 sm2 = set(other.walk(match))
134 sm2 = set(other.walk(match))
131 return {f for f in self.walk(match) if f not in sm2}
135 return {f for f in self.walk(match) if f not in sm2}
132 return {f for f in self if f not in other}
136 return {f for f in self if f not in other}
133
137
134 @util.propertycache
138 @util.propertycache
135 def _dirs(self):
139 def _dirs(self):
136 return pathutil.dirs(self)
140 return pathutil.dirs(self)
137
141
142 def dirs(self) -> pathutil.dirs:
143 return self._dirs # TODO: why is there a prpoertycache?
144
138 def hasdir(self, dir: bytes) -> bool:
145 def hasdir(self, dir: bytes) -> bool:
139 return dir in self._dirs
146 return dir in self._dirs
140
147
141 def diff(
148 def diff(
142 self,
149 self,
143 other: Any, # TODO: 'manifestdict' or (better) equivalent interface
150 other: Any, # TODO: 'manifestdict' or (better) equivalent interface
144 match: Any = lambda x: True, # TODO: Optional[matchmod.basematcher] = None,
151 match: Any = lambda x: True, # TODO: Optional[matchmod.basematcher] = None,
145 clean: bool = False,
152 clean: bool = False,
146 ) -> dict[
153 ) -> dict[
147 bytes,
154 bytes,
148 tuple[tuple[bytes | None, bytes], tuple[bytes | None, bytes]] | None,
155 tuple[tuple[bytes | None, bytes], tuple[bytes | None, bytes]] | None,
149 ]:
156 ]:
150 """Finds changes between the current manifest and m2.
157 """Finds changes between the current manifest and m2.
151
158
152 The result is returned as a dict with filename as key and
159 The result is returned as a dict with filename as key and
153 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
160 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
154 nodeid in the current/other manifest and fl1/fl2 is the flag
161 nodeid in the current/other manifest and fl1/fl2 is the flag
155 in the current/other manifest. Where the file does not exist,
162 in the current/other manifest. Where the file does not exist,
156 the nodeid will be None and the flags will be the empty
163 the nodeid will be None and the flags will be the empty
157 string.
164 string.
158 """
165 """
159 result = {}
166 result = {}
160
167
161 def _iterativediff(t1, t2, subdir):
168 def _iterativediff(t1, t2, subdir):
162 """compares two trees and appends new tree nodes to examine to
169 """compares two trees and appends new tree nodes to examine to
163 the stack"""
170 the stack"""
164 if t1 is None:
171 if t1 is None:
165 t1 = {}
172 t1 = {}
166 if t2 is None:
173 if t2 is None:
167 t2 = {}
174 t2 = {}
168
175
169 for e1 in t1:
176 for e1 in t1:
170 realname = subdir + pycompat.fsencode(e1.name)
177 realname = subdir + pycompat.fsencode(e1.name)
171
178
172 if e1.type == pygit2.GIT_OBJ_TREE:
179 if e1.type == pygit2.GIT_OBJ_TREE:
173 try:
180 try:
174 e2 = t2[e1.name]
181 e2 = t2[e1.name]
175 if e2.type != pygit2.GIT_OBJ_TREE:
182 if e2.type != pygit2.GIT_OBJ_TREE:
176 e2 = None
183 e2 = None
177 except KeyError:
184 except KeyError:
178 e2 = None
185 e2 = None
179
186
180 stack.append((realname + b'/', e1, e2))
187 stack.append((realname + b'/', e1, e2))
181 else:
188 else:
182 n1, fl1 = self.find(realname)
189 n1, fl1 = self.find(realname)
183
190
184 try:
191 try:
185 e2 = t2[e1.name]
192 e2 = t2[e1.name]
186 n2, fl2 = other.find(realname)
193 n2, fl2 = other.find(realname)
187 except KeyError:
194 except KeyError:
188 e2 = None
195 e2 = None
189 n2, fl2 = (None, b'')
196 n2, fl2 = (None, b'')
190
197
191 if e2 is not None and e2.type == pygit2.GIT_OBJ_TREE:
198 if e2 is not None and e2.type == pygit2.GIT_OBJ_TREE:
192 stack.append((realname + b'/', None, e2))
199 stack.append((realname + b'/', None, e2))
193
200
194 if not match(realname):
201 if not match(realname):
195 continue
202 continue
196
203
197 if n1 != n2 or fl1 != fl2:
204 if n1 != n2 or fl1 != fl2:
198 result[realname] = ((n1, fl1), (n2, fl2))
205 result[realname] = ((n1, fl1), (n2, fl2))
199 elif clean:
206 elif clean:
200 result[realname] = None
207 result[realname] = None
201
208
202 for e2 in t2:
209 for e2 in t2:
203 if e2.name in t1:
210 if e2.name in t1:
204 continue
211 continue
205
212
206 realname = subdir + pycompat.fsencode(e2.name)
213 realname = subdir + pycompat.fsencode(e2.name)
207
214
208 if e2.type == pygit2.GIT_OBJ_TREE:
215 if e2.type == pygit2.GIT_OBJ_TREE:
209 stack.append((realname + b'/', None, e2))
216 stack.append((realname + b'/', None, e2))
210 elif match(realname):
217 elif match(realname):
211 n2, fl2 = other.find(realname)
218 n2, fl2 = other.find(realname)
212 result[realname] = ((None, b''), (n2, fl2))
219 result[realname] = ((None, b''), (n2, fl2))
213
220
214 stack = []
221 stack = []
215 _iterativediff(self._tree, other._tree, b'')
222 _iterativediff(self._tree, other._tree, b'')
216 while stack:
223 while stack:
217 subdir, t1, t2 = stack.pop()
224 subdir, t1, t2 = stack.pop()
218 # stack is populated in the function call
225 # stack is populated in the function call
219 _iterativediff(t1, t2, subdir)
226 _iterativediff(t1, t2, subdir)
220
227
221 return result
228 return result
222
229
223 def setflag(self, path: bytes, flag: bytes) -> None:
230 def setflag(self, path: bytes, flag: bytes) -> None:
224 node, unused_flag = self._resolve_entry(path)
231 node, unused_flag = self._resolve_entry(path)
225 self._pending_changes[path] = node, flag
232 self._pending_changes[path] = node, flag
226
233
227 def get(self, path: bytes, default=None) -> bytes | None:
234 def get(self, path: bytes, default=None) -> bytes | None:
228 try:
235 try:
229 return self._resolve_entry(path)[0]
236 return self._resolve_entry(path)[0]
230 except KeyError:
237 except KeyError:
231 return default
238 return default
232
239
233 def flags(self, path: bytes) -> bytes:
240 def flags(self, path: bytes) -> bytes:
234 try:
241 try:
235 return self._resolve_entry(path)[1]
242 return self._resolve_entry(path)[1]
236 except KeyError:
243 except KeyError:
237 return b''
244 return b''
238
245
239 def copy(self) -> 'gittreemanifest':
246 def copy(self) -> 'gittreemanifest':
240 return gittreemanifest(
247 return gittreemanifest(
241 self._git_repo, self._tree, dict(self._pending_changes)
248 self._git_repo, self._tree, dict(self._pending_changes)
242 )
249 )
243
250
244 def items(self) -> Iterator[tuple[bytes, bytes]]:
251 def items(self) -> Iterator[tuple[bytes, bytes]]:
245 for f in self:
252 for f in self:
246 # TODO: build a proper iterator version of this
253 # TODO: build a proper iterator version of this
247 yield f, self[f]
254 yield f, self[f]
248
255
249 def iteritems(self) -> Iterator[tuple[bytes, bytes]]:
256 def iteritems(self) -> Iterator[tuple[bytes, bytes]]:
250 return self.items()
257 return self.items()
251
258
252 def iterentries(self) -> Iterator[tuple[bytes, bytes, bytes]]:
259 def iterentries(self) -> Iterator[tuple[bytes, bytes, bytes]]:
253 for f in self:
260 for f in self:
254 # TODO: build a proper iterator version of this
261 # TODO: build a proper iterator version of this
255 yield f, *self._resolve_entry(f)
262 yield f, *self._resolve_entry(f)
256
263
257 def text(self) -> ByteString:
264 def text(self) -> ByteString:
258 # TODO can this method move out of the manifest iface?
265 # TODO can this method move out of the manifest iface?
259 raise NotImplementedError
266 raise NotImplementedError
260
267
268 def fastdelta(
269 self, base: ByteString, changes: Iterable[tuple[bytes, bool]]
270 ) -> tuple[ByteString, ByteString]:
271 raise NotImplementedError # TODO: implement this
272
261 def _walkonetree(self, tree, match, subdir):
273 def _walkonetree(self, tree, match, subdir):
262 for te in tree:
274 for te in tree:
263 # TODO: can we prune dir walks with the matcher?
275 # TODO: can we prune dir walks with the matcher?
264 realname = subdir + pycompat.fsencode(te.name)
276 realname = subdir + pycompat.fsencode(te.name)
265 if te.type == pygit2.GIT_OBJ_TREE:
277 if te.type == pygit2.GIT_OBJ_TREE:
266 for inner in self._walkonetree(
278 for inner in self._walkonetree(
267 self._git_repo[te.id], match, realname + b'/'
279 self._git_repo[te.id], match, realname + b'/'
268 ):
280 ):
269 yield inner
281 yield inner
270 elif match(realname):
282 elif match(realname):
271 yield pycompat.fsencode(realname)
283 yield pycompat.fsencode(realname)
272
284
273 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
285 def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
274 # TODO: this is a very lazy way to merge in the pending
286 # TODO: this is a very lazy way to merge in the pending
275 # changes. There is absolutely room for optimization here by
287 # changes. There is absolutely room for optimization here by
276 # being clever about walking over the sets...
288 # being clever about walking over the sets...
277 baseline = set(self._walkonetree(self._tree, match, b''))
289 baseline = set(self._walkonetree(self._tree, match, b''))
278 deleted = {p for p, v in self._pending_changes.items() if v is None}
290 deleted = {p for p, v in self._pending_changes.items() if v is None}
279 pend = {p for p in self._pending_changes if match(p)}
291 pend = {p for p in self._pending_changes if match(p)}
280 return iter(sorted((baseline | pend) - deleted))
292 return iter(sorted((baseline | pend) - deleted))
281
293
282
294
283 class gittreemanifestctx(repository.imanifestrevisionstored):
295 class gittreemanifestctx(repository.imanifestrevisionstored):
284 def __init__(self, repo, gittree):
296 def __init__(self, repo, gittree):
285 self._repo = repo
297 self._repo = repo
286 self._tree = gittree
298 self._tree = gittree
287
299
288 def read(self):
300 def read(self):
289 return gittreemanifest(self._repo, self._tree, None)
301 return gittreemanifest(self._repo, self._tree, None)
290
302
291 def readfast(self, shallow: bool = False):
303 def readfast(self, shallow: bool = False):
292 return self.read()
304 return self.read()
293
305
294 def copy(self):
306 def copy(self):
295 # NB: it's important that we return a memgittreemanifestctx
307 # NB: it's important that we return a memgittreemanifestctx
296 # because the caller expects a mutable manifest.
308 # because the caller expects a mutable manifest.
297 return memgittreemanifestctx(self._repo, self._tree)
309 return memgittreemanifestctx(self._repo, self._tree)
298
310
299 def find(self, path: bytes) -> tuple[bytes, bytes]:
311 def find(self, path: bytes) -> tuple[bytes, bytes]:
300 return self.read().find(path)
312 return self.read().find(path)
301
313
302
314
303 class memgittreemanifestctx(repository.imanifestrevisionwritable):
315 class memgittreemanifestctx(repository.imanifestrevisionwritable):
304 def __init__(self, repo, tree):
316 def __init__(self, repo, tree):
305 self._repo = repo
317 self._repo = repo
306 self._tree = tree
318 self._tree = tree
307 # dict of path: Optional[Tuple(node, flags)]
319 # dict of path: Optional[Tuple(node, flags)]
308 self._pending_changes = {}
320 self._pending_changes = {}
309
321
310 def read(self):
322 def read(self):
311 return gittreemanifest(self._repo, self._tree, self._pending_changes)
323 return gittreemanifest(self._repo, self._tree, self._pending_changes)
312
324
313 def copy(self):
325 def copy(self):
314 # TODO: if we have a builder in play, what should happen here?
326 # TODO: if we have a builder in play, what should happen here?
315 # Maybe we can shuffle copy() into the immutable interface.
327 # Maybe we can shuffle copy() into the immutable interface.
316 return memgittreemanifestctx(self._repo, self._tree)
328 return memgittreemanifestctx(self._repo, self._tree)
317
329
318 def write(self, transaction, link, p1, p2, added, removed, match=None):
330 def write(self, transaction, link, p1, p2, added, removed, match=None):
319 # We're not (for now, anyway) going to audit filenames, so we
331 # We're not (for now, anyway) going to audit filenames, so we
320 # can ignore added and removed.
332 # can ignore added and removed.
321
333
322 # TODO what does this match argument get used for? hopefully
334 # TODO what does this match argument get used for? hopefully
323 # just narrow?
335 # just narrow?
324 assert not match or isinstance(match, matchmod.alwaysmatcher)
336 assert not match or isinstance(match, matchmod.alwaysmatcher)
325
337
326 touched_dirs = pathutil.dirs(list(self._pending_changes))
338 touched_dirs = pathutil.dirs(list(self._pending_changes))
327 trees = {
339 trees = {
328 b'': self._tree,
340 b'': self._tree,
329 }
341 }
330 # path: treebuilder
342 # path: treebuilder
331 builders = {
343 builders = {
332 b'': self._repo.TreeBuilder(self._tree),
344 b'': self._repo.TreeBuilder(self._tree),
333 }
345 }
334 # get a TreeBuilder for every tree in the touched_dirs set
346 # get a TreeBuilder for every tree in the touched_dirs set
335 for d in sorted(touched_dirs, key=lambda x: (len(x), x)):
347 for d in sorted(touched_dirs, key=lambda x: (len(x), x)):
336 if d == b'':
348 if d == b'':
337 # loaded root tree above
349 # loaded root tree above
338 continue
350 continue
339 comps = d.split(b'/')
351 comps = d.split(b'/')
340 full = b''
352 full = b''
341 for part in comps:
353 for part in comps:
342 parent = trees[full]
354 parent = trees[full]
343 try:
355 try:
344 parent_tree_id = parent[pycompat.fsdecode(part)].id
356 parent_tree_id = parent[pycompat.fsdecode(part)].id
345 new = self._repo[parent_tree_id]
357 new = self._repo[parent_tree_id]
346 except KeyError:
358 except KeyError:
347 # new directory
359 # new directory
348 new = None
360 new = None
349 full += b'/' + part
361 full += b'/' + part
350 if new is not None:
362 if new is not None:
351 # existing directory
363 # existing directory
352 trees[full] = new
364 trees[full] = new
353 builders[full] = self._repo.TreeBuilder(new)
365 builders[full] = self._repo.TreeBuilder(new)
354 else:
366 else:
355 # new directory, use an empty dict to easily
367 # new directory, use an empty dict to easily
356 # generate KeyError as any nested new dirs get
368 # generate KeyError as any nested new dirs get
357 # created.
369 # created.
358 trees[full] = {}
370 trees[full] = {}
359 builders[full] = self._repo.TreeBuilder()
371 builders[full] = self._repo.TreeBuilder()
360 for f, info in self._pending_changes.items():
372 for f, info in self._pending_changes.items():
361 if b'/' not in f:
373 if b'/' not in f:
362 dirname = b''
374 dirname = b''
363 basename = f
375 basename = f
364 else:
376 else:
365 dirname, basename = f.rsplit(b'/', 1)
377 dirname, basename = f.rsplit(b'/', 1)
366 dirname = b'/' + dirname
378 dirname = b'/' + dirname
367 if info is None:
379 if info is None:
368 builders[dirname].remove(pycompat.fsdecode(basename))
380 builders[dirname].remove(pycompat.fsdecode(basename))
369 else:
381 else:
370 n, fl = info
382 n, fl = info
371 mode = {
383 mode = {
372 b'': pygit2.GIT_FILEMODE_BLOB,
384 b'': pygit2.GIT_FILEMODE_BLOB,
373 b'x': pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
385 b'x': pygit2.GIT_FILEMODE_BLOB_EXECUTABLE,
374 b'l': pygit2.GIT_FILEMODE_LINK,
386 b'l': pygit2.GIT_FILEMODE_LINK,
375 }[fl]
387 }[fl]
376 builders[dirname].insert(
388 builders[dirname].insert(
377 pycompat.fsdecode(basename), gitutil.togitnode(n), mode
389 pycompat.fsdecode(basename), gitutil.togitnode(n), mode
378 )
390 )
379 # This visits the buffered TreeBuilders in deepest-first
391 # This visits the buffered TreeBuilders in deepest-first
380 # order, bubbling up the edits.
392 # order, bubbling up the edits.
381 for b in sorted(builders, key=len, reverse=True):
393 for b in sorted(builders, key=len, reverse=True):
382 if b == b'':
394 if b == b'':
383 break
395 break
384 cb = builders[b]
396 cb = builders[b]
385 dn, bn = b.rsplit(b'/', 1)
397 dn, bn = b.rsplit(b'/', 1)
386 builders[dn].insert(
398 builders[dn].insert(
387 pycompat.fsdecode(bn), cb.write(), pygit2.GIT_FILEMODE_TREE
399 pycompat.fsdecode(bn), cb.write(), pygit2.GIT_FILEMODE_TREE
388 )
400 )
389 return builders[b''].write().raw
401 return builders[b''].write().raw
General Comments 0
You need to be logged in to leave comments. Login now