Merge lp:~jelmer/brz-git/submodules into lp:brz-git
- submodules
- Merge into trunk
Proposed by
Jelmer Vernooij
Status: | Merged |
---|---|
Approved by: | Jelmer Vernooij |
Approved revision: | 1918 |
Merge reported by: | The Breezy Bot |
Merged at revision: | not available |
Proposed branch: | lp:~jelmer/brz-git/submodules |
Merge into: | lp:brz-git |
Diff against target: |
751 lines (+325/-56) 6 files modified
branch.py (+14/-0) commit.py (+2/-3) mapping.py (+1/-1) repository.py (+1/-1) tree.py (+182/-29) workingtree.py (+125/-22) |
To merge this branch: | bzr merge lp:~jelmer/brz-git/submodules |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jelmer Vernooij | Approve | ||
Review via email: mp+345129@code.launchpad.net |
Commit message
Add basic support for submodules.
Description of the change
To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) : | # |
review:
Approve
Revision history for this message
The Breezy Bot (the-breezy-bot) wrote : | # |
lp:~jelmer/brz-git/submodules
updated
- 1918. By Jelmer Vernooij
-
Fix remaining test.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'branch.py' |
2 | --- branch.py 2018-04-02 15:43:48 +0000 |
3 | +++ branch.py 2018-05-05 18:49:25 +0000 |
4 | @@ -699,6 +699,20 @@ |
5 | from .memorytree import GitMemoryTree |
6 | return GitMemoryTree(self, self.repository._git.object_store, self.head) |
7 | |
8 | + def reference_parent(self, path, file_id=None, possible_transports=None): |
9 | + """Return the parent branch for a tree-reference file_id |
10 | + |
11 | + :param path: The path of the file_id in the tree |
12 | + :param file_id: Optional file_id of the tree reference |
13 | + :return: A branch associated with the file_id |
14 | + """ |
15 | + # FIXME should provide multiple branches, based on config |
16 | + url = urlutils.join(self.user_url, path) |
17 | + return branch.Branch.open( |
18 | + url, |
19 | + possible_transports=possible_transports) |
20 | + |
21 | + |
22 | |
23 | def _quick_lookup_revno(local_branch, remote_branch, revid): |
24 | if type(revid) is not str: |
25 | |
26 | === modified file 'commit.py' |
27 | --- commit.py 2018-03-27 03:09:48 +0000 |
28 | +++ commit.py 2018-05-05 18:49:25 +0000 |
29 | @@ -45,6 +45,7 @@ |
30 | Blob, |
31 | Commit, |
32 | ) |
33 | +from dulwich.index import read_submodule_head |
34 | from dulwich.repo import Repo |
35 | |
36 | |
37 | @@ -85,8 +86,6 @@ |
38 | return self._any_changes |
39 | |
40 | def record_iter_changes(self, workingtree, basis_revid, iter_changes): |
41 | - def treeref_sha1(path, file_id): |
42 | - return Repo.open(os.path.join(workingtree.basedir, path)).head() |
43 | seen_root = False |
44 | for (file_id, path, changed_content, versioned, parent, name, kind, |
45 | executable) in iter_changes: |
46 | @@ -129,7 +128,7 @@ |
47 | entry.symlink_target = symlink_target |
48 | st = None |
49 | elif kind[1] == "tree-reference": |
50 | - sha = treeref_sha1(path[1], file_id) |
51 | + sha = read_submodule_head(workingtree.abspath(path[1])) |
52 | reference_revision = workingtree.get_reference_revision(path[1], file_id) |
53 | entry.reference_revision = reference_revision |
54 | st = None |
55 | |
56 | === modified file 'mapping.py' |
57 | --- mapping.py 2018-04-07 15:40:32 +0000 |
58 | +++ mapping.py 2018-05-05 18:49:25 +0000 |
59 | @@ -599,7 +599,7 @@ |
60 | |
61 | def entry_mode(entry): |
62 | """Determine the git file mode for an inventory entry.""" |
63 | - return object_mode(entry.kind, entry.executable) |
64 | + return object_mode(entry.kind, getattr(entry, 'executable', False)) |
65 | |
66 | |
67 | def extract_unusual_modes(rev): |
68 | |
69 | === modified file 'repository.py' |
70 | --- repository.py 2018-04-08 17:06:15 +0000 |
71 | +++ repository.py 2018-05-05 18:49:25 +0000 |
72 | @@ -642,7 +642,7 @@ |
73 | """Git repository format.""" |
74 | |
75 | supports_versioned_directories = False |
76 | - supports_tree_reference = False |
77 | + supports_tree_reference = True |
78 | rich_root_data = True |
79 | supports_leaving_lock = False |
80 | fast_deltas = True |
81 | |
82 | === modified file 'tree.py' |
83 | --- tree.py 2018-04-02 21:10:21 +0000 |
84 | +++ tree.py 2018-05-05 18:49:25 +0000 |
85 | @@ -34,11 +34,14 @@ |
86 | Blob, |
87 | Tree, |
88 | ZERO_SHA, |
89 | + S_IFGITLINK, |
90 | + S_ISGITLINK, |
91 | ) |
92 | import stat |
93 | import posixpath |
94 | |
95 | from ... import ( |
96 | + controldir as _mod_controldir, |
97 | delta, |
98 | errors, |
99 | lock, |
100 | @@ -176,10 +179,43 @@ |
101 | self.symlink_target) |
102 | |
103 | |
104 | +class GitTreeSubmodule(_mod_tree.TreeLink): |
105 | + |
106 | + __slots__ = ['file_id', 'name', 'parent_id', 'reference_revision'] |
107 | + |
108 | + def __init__(self, file_id, name, parent_id, reference_revision=None): |
109 | + self.file_id = file_id |
110 | + self.name = name |
111 | + self.parent_id = parent_id |
112 | + self.reference_revision = reference_revision |
113 | + |
114 | + @property |
115 | + def kind(self): |
116 | + return 'tree-reference' |
117 | + |
118 | + def __repr__(self): |
119 | + return "%s(file_id=%r, name=%r, parent_id=%r, reference_revision=%r)" % ( |
120 | + type(self).__name__, self.file_id, self.name, self.parent_id, |
121 | + self.reference_revision) |
122 | + |
123 | + def __eq__(self, other): |
124 | + return (self.kind == other.kind and |
125 | + self.file_id == other.file_id and |
126 | + self.name == other.name and |
127 | + self.parent_id == other.parent_id and |
128 | + self.reference_revision == other.reference_revision) |
129 | + |
130 | + def copy(self): |
131 | + return self.__class__( |
132 | + self.file_id, self.name, self.parent_id, |
133 | + self.reference_revision) |
134 | + |
135 | + |
136 | entry_factory = { |
137 | 'directory': GitTreeDirectory, |
138 | 'file': GitTreeFile, |
139 | 'symlink': GitTreeSymlink, |
140 | + 'tree-reference': GitTreeSubmodule, |
141 | } |
142 | |
143 | |
144 | @@ -223,6 +259,11 @@ |
145 | self.tree = commit.tree |
146 | self._fileid_map = self.mapping.get_fileid_map(self.store.__getitem__, self.tree) |
147 | |
148 | + def _get_nested_repository(self, path): |
149 | + nested_repo_transport = self._repository.user_transport.clone(path) |
150 | + nested_controldir = _mod_controldir.ControlDir.open_from_transport(nested_repo_transport) |
151 | + return nested_controldir.find_repository() |
152 | + |
153 | def supports_rename_tracking(self): |
154 | return False |
155 | |
156 | @@ -470,6 +511,15 @@ |
157 | else: |
158 | return None |
159 | |
160 | + def get_reference_revision(self, path, file_id=None): |
161 | + """See RevisionTree.get_symlink_target.""" |
162 | + (store, mode, hexsha) = self._lookup_path(path) |
163 | + if S_ISGITLINK(mode): |
164 | + nested_repo = self._get_nested_repository(path) |
165 | + return nested_repo.lookup_foreign_revision_id(hexsha) |
166 | + else: |
167 | + return None |
168 | + |
169 | def _comparison_data(self, entry, path): |
170 | if entry is None: |
171 | return None, False, None |
172 | @@ -488,6 +538,10 @@ |
173 | return (kind, len(contents), executable, osutils.sha_string(contents)) |
174 | elif kind == 'symlink': |
175 | return (kind, None, None, store[hexsha].data) |
176 | + elif kind == 'tree-reference': |
177 | + nested_repo = self._get_nested_repository(path) |
178 | + return (kind, None, None, |
179 | + nested_repo.lookup_foreign_revision_id(hexsha)) |
180 | else: |
181 | return (kind, None, None, None) |
182 | |
183 | @@ -746,7 +800,8 @@ |
184 | def is_versioned(self, path): |
185 | with self.lock_read(): |
186 | path = path.rstrip('/').encode('utf-8') |
187 | - return (path in self.index or self._has_dir(path)) |
188 | + (index, subpath) = self._lookup_index(path) |
189 | + return (subpath in index or self._has_dir(path)) |
190 | |
191 | def _has_dir(self, path): |
192 | if path == "": |
193 | @@ -759,7 +814,8 @@ |
194 | if self._lock_mode is None: |
195 | raise errors.ObjectNotLocked(self) |
196 | self._versioned_dirs = set() |
197 | - for p in self.index: |
198 | + # TODO(jelmer): Browse over all indexes |
199 | + for p, i in self._recurse_index_entries(): |
200 | self._ensure_versioned_dir(posixpath.dirname(p)) |
201 | |
202 | def _ensure_versioned_dir(self, dirname): |
203 | @@ -814,11 +870,21 @@ |
204 | raise errors.InvalidNormalization(path) |
205 | self._index_add_entry(path, kind) |
206 | |
207 | - def _index_del_entry(self, path): |
208 | - del self.index[path] |
209 | + def _read_submodule_head(self, path): |
210 | + raise NotImplementedError(self._read_submodule_head) |
211 | + |
212 | + def _lookup_index(self, encoded_path): |
213 | + if not isinstance(encoded_path, bytes): |
214 | + raise TypeError(encoded_path) |
215 | + # TODO(jelmer): Look in other indexes |
216 | + return self.index, encoded_path |
217 | + |
218 | + def _index_del_entry(self, index, path): |
219 | + del index[path] |
220 | + # TODO(jelmer): Keep track of dirty per index |
221 | self._index_dirty = True |
222 | |
223 | - def _index_add_entry(self, path, kind, flags=0): |
224 | + def _index_add_entry(self, path, kind, flags=0, reference_revision=None): |
225 | if not isinstance(path, basestring): |
226 | raise TypeError(path) |
227 | if kind == "directory": |
228 | @@ -834,22 +900,40 @@ |
229 | stat_val = os.stat_result( |
230 | (stat.S_IFREG | 0644, 0, 0, 0, 0, 0, 0, 0, 0, 0)) |
231 | blob.set_raw_string(file.read()) |
232 | + # Add object to the repository if it didn't exist yet |
233 | + if not blob.id in self.store: |
234 | + self.store.add_object(blob) |
235 | + hexsha = blob.id |
236 | elif kind == "symlink": |
237 | blob = Blob() |
238 | try: |
239 | stat_val = self._lstat(path) |
240 | - except (errors.NoSuchFile, OSError): |
241 | + except EnvironmentError: |
242 | # TODO: Rather than come up with something here, use the |
243 | # old index |
244 | stat_val = os.stat_result( |
245 | (stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0)) |
246 | blob.set_raw_string( |
247 | self.get_symlink_target(path).encode("utf-8")) |
248 | + # Add object to the repository if it didn't exist yet |
249 | + if not blob.id in self.store: |
250 | + self.store.add_object(blob) |
251 | + hexsha = blob.id |
252 | + elif kind == "tree-reference": |
253 | + if reference_revision is not None: |
254 | + hexsha = self.branch.lookup_bzr_revision_id(reference_revision)[0] |
255 | + else: |
256 | + hexsha = self._read_submodule_head(path) |
257 | + if hexsha is None: |
258 | + raise errors.NoCommits(path) |
259 | + try: |
260 | + stat_val = self._lstat(path) |
261 | + except EnvironmentError: |
262 | + stat_val = os.stat_result( |
263 | + (S_IFGITLINK, 0, 0, 0, 0, 0, 0, 0, 0, 0)) |
264 | + stat_val = os.stat_result((S_IFGITLINK, ) + stat_val[1:]) |
265 | else: |
266 | raise AssertionError("unknown kind '%s'" % kind) |
267 | - # Add object to the repository if it didn't exist yet |
268 | - if not blob.id in self.store: |
269 | - self.store.add_object(blob) |
270 | # Add an entry to the index or update the existing entry |
271 | ensure_normalized_path(path) |
272 | encoded_path = path.encode("utf-8") |
273 | @@ -857,11 +941,23 @@ |
274 | # TODO(jelmer): Why do we need to do this? |
275 | trace.mutter('ignoring path with invalid newline in it: %r', path) |
276 | return |
277 | + (index, index_path) = self._lookup_index(encoded_path) |
278 | + index[index_path] = index_entry_from_stat(stat_val, hexsha, flags) |
279 | self._index_dirty = True |
280 | - self.index[encoded_path] = index_entry_from_stat( |
281 | - stat_val, blob.id, flags) |
282 | if self._versioned_dirs is not None: |
283 | - self._ensure_versioned_dir(encoded_path) |
284 | + self._ensure_versioned_dir(index_path) |
285 | + |
286 | + def _recurse_index_entries(self, index=None, basepath=""): |
287 | + # Iterate over all index entries |
288 | + with self.lock_read(): |
289 | + if index is None: |
290 | + index = self.index |
291 | + for path, value in index.iteritems(): |
292 | + yield (posixpath.join(basepath, path), value) |
293 | + (ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value |
294 | + if S_ISGITLINK(mode): |
295 | + pass # TODO(jelmer): dive into submodule |
296 | + |
297 | |
298 | def iter_entries_by_dir(self, specific_files=None, yield_parents=False): |
299 | if yield_parents: |
300 | @@ -876,7 +972,7 @@ |
301 | if specific_files is None or u"" in specific_files: |
302 | ret[(None, u"")] = root_ie |
303 | dir_ids = {u"": root_ie.file_id} |
304 | - for path, value in self.index.iteritems(): |
305 | + for path, value in self._recurse_index_entries(): |
306 | if self.mapping.is_special_file(path): |
307 | continue |
308 | path = path.decode("utf-8") |
309 | @@ -895,6 +991,11 @@ |
310 | ret[(posixpath.dirname(path), path)] = file_ie |
311 | return ((path, ie) for ((_, path), ie) in sorted(ret.items())) |
312 | |
313 | + def iter_references(self): |
314 | + # TODO(jelmer): Implement a more efficient version of this |
315 | + for path, entry in self.iter_entries_by_dir(): |
316 | + if entry.kind == 'tree-reference': |
317 | + yield path, self.mapping.generate_file_id(b'') |
318 | |
319 | def _get_dir_ie(self, path, parent_id): |
320 | file_id = self.path2id(path) |
321 | @@ -916,6 +1017,8 @@ |
322 | ie = entry_factory[kind](file_id, name, parent_id) |
323 | if kind == 'symlink': |
324 | ie.symlink_target = self.get_symlink_target(path, file_id) |
325 | + elif kind == 'tree-reference': |
326 | + ie.reference_revision = self.get_reference_revision(path, file_id) |
327 | else: |
328 | try: |
329 | data = self.get_file_text(path, file_id) |
330 | @@ -953,14 +1056,16 @@ |
331 | raise errors.ObjectNotLocked(self) |
332 | encoded_path = path.encode("utf-8") |
333 | count = 0 |
334 | + (index, subpath) = self._lookup_index(encoded_path) |
335 | try: |
336 | - self._index_del_entry(encoded_path) |
337 | + self._index_del_entry(index, encoded_path) |
338 | except KeyError: |
339 | # A directory, perhaps? |
340 | - for p in list(self.index): |
341 | - if p.startswith(encoded_path+b"/"): |
342 | + # TODO(jelmer): Deletes that involve submodules? |
343 | + for p in list(index): |
344 | + if p.startswith(subpath+b"/"): |
345 | count += 1 |
346 | - self._index_del_entry(p) |
347 | + self._index_del_entry(index, p) |
348 | else: |
349 | count = 1 |
350 | self._versioned_dirs = None |
351 | @@ -980,9 +1085,11 @@ |
352 | def update_basis_by_delta(self, revid, delta): |
353 | # TODO(jelmer): This shouldn't be called, it's inventory specific. |
354 | for (old_path, new_path, file_id, ie) in delta: |
355 | - if old_path is not None and old_path.encode('utf-8') in self.index: |
356 | - self._index_del_entry(old_path.encode('utf-8')) |
357 | - self._versioned_dirs = None |
358 | + if old_path is not None: |
359 | + (index, old_subpath) = self._lookup_index(old_path.encode('utf-8')) |
360 | + if old_subpath in index: |
361 | + self._index_del_entry(index, old_subpath) |
362 | + self._versioned_dirs = None |
363 | if new_path is not None and ie.kind != 'directory': |
364 | self._index_add_entry(new_path, ie.kind) |
365 | self.flush() |
366 | @@ -1050,10 +1157,12 @@ |
367 | |
368 | kind = self.kind(from_rel) |
369 | |
370 | - if not after and not from_path in self.index and kind != 'directory': |
371 | - # It's not a file |
372 | - raise errors.BzrMoveFailedError(from_rel, to_rel, |
373 | - errors.NotVersionedError(path=from_rel)) |
374 | + if not after and kind != 'directory': |
375 | + (index, from_subpath) = self._lookup_index(from_path) |
376 | + if from_subpath not in index: |
377 | + # It's not a file |
378 | + raise errors.BzrMoveFailedError(from_rel, to_rel, |
379 | + errors.NotVersionedError(path=from_rel)) |
380 | |
381 | if not after: |
382 | try: |
383 | @@ -1064,17 +1173,21 @@ |
384 | errors.NoSuchFile(to_rel)) |
385 | raise |
386 | if kind != 'directory': |
387 | + (index, from_index_path) = self._lookup_index(from_path) |
388 | try: |
389 | - self._index_del_entry(from_path) |
390 | + self._index_del_entry(index, from_path) |
391 | except KeyError: |
392 | pass |
393 | self._index_add_entry(to_rel, kind) |
394 | else: |
395 | - todo = [p for p in self.index if p.startswith(from_path+'/')] |
396 | - for p in todo: |
397 | + todo = [(p, i) for (p, i) in self._recurse_index_entries() if p.startswith(from_path+'/')] |
398 | + for child_path, child_value in todo: |
399 | + (child_to_index, child_to_index_path) = self._lookup_index(posixpath.join(to_path, posixpath.relpath(child_path, from_path))) |
400 | + child_to_index[child_to_index_path] = child_value |
401 | + # TODO(jelmer): Mark individual index as dirty |
402 | self._index_dirty = True |
403 | - self.index[posixpath.join(to_path, posixpath.relpath(p, from_path))] = self.index[p] |
404 | - self._index_del_entry(p) |
405 | + (child_from_index, child_from_index_path) = self._lookup_index(child_path) |
406 | + self._index_del_entry(child_from_index, child_from_index_path) |
407 | |
408 | self._versioned_dirs = None |
409 | self.flush() |
410 | @@ -1097,3 +1210,43 @@ |
411 | raise errors.PathsNotVersionedError(unversioned) |
412 | |
413 | return filter(self.is_versioned, paths) |
414 | + |
415 | + def path_content_summary(self, path): |
416 | + """See Tree.path_content_summary.""" |
417 | + try: |
418 | + stat_result = self._lstat(path) |
419 | + except OSError as e: |
420 | + if getattr(e, 'errno', None) == errno.ENOENT: |
421 | + # no file. |
422 | + return ('missing', None, None, None) |
423 | + # propagate other errors |
424 | + raise |
425 | + kind = mode_kind(stat_result.st_mode) |
426 | + if kind == 'file': |
427 | + return self._file_content_summary(path, stat_result) |
428 | + elif kind == 'directory': |
429 | + # perhaps it looks like a plain directory, but it's really a |
430 | + # reference. |
431 | + if self._directory_is_tree_reference(path): |
432 | + kind = 'tree-reference' |
433 | + return kind, None, None, None |
434 | + elif kind == 'symlink': |
435 | + target = osutils.readlink(self.abspath(path)) |
436 | + return ('symlink', None, None, target) |
437 | + else: |
438 | + return (kind, None, None, None) |
439 | + |
440 | + def kind(self, relpath, file_id=None): |
441 | + kind = osutils.file_kind(self.abspath(relpath)) |
442 | + if kind == 'directory': |
443 | + (index, index_path) = self._lookup_index(relpath.encode('utf-8')) |
444 | + try: |
445 | + mode = index[index_path].mode |
446 | + except KeyError: |
447 | + return kind |
448 | + else: |
449 | + if S_ISGITLINK(mode): |
450 | + return 'tree-reference' |
451 | + return 'directory' |
452 | + else: |
453 | + return kind |
454 | |
455 | === modified file 'workingtree.py' |
456 | --- workingtree.py 2018-05-05 16:39:36 +0000 |
457 | +++ workingtree.py 2018-05-05 18:49:25 +0000 |
458 | @@ -25,7 +25,6 @@ |
459 | ) |
460 | from collections import defaultdict |
461 | import errno |
462 | -from dulwich.errors import NotGitRepository |
463 | from dulwich.ignore import ( |
464 | IgnoreFilterManager, |
465 | ) |
466 | @@ -42,6 +41,7 @@ |
467 | iter_fresh_entries, |
468 | blob_from_path_and_stat, |
469 | FLAG_STAGEMASK, |
470 | + read_submodule_head, |
471 | validate_path, |
472 | write_index_dict, |
473 | ) |
474 | @@ -55,7 +55,10 @@ |
475 | S_ISGITLINK, |
476 | ZERO_SHA, |
477 | ) |
478 | -from dulwich.repo import Repo |
479 | +from dulwich.repo import ( |
480 | + NotGitRepository, |
481 | + Repo as GitRepo, |
482 | + ) |
483 | import os |
484 | import posixpath |
485 | import re |
486 | @@ -84,6 +87,7 @@ |
487 | inventory, |
488 | ) |
489 | from ...mutabletree import ( |
490 | + BadReferenceTarget, |
491 | MutableTree, |
492 | ) |
493 | |
494 | @@ -127,7 +131,7 @@ |
495 | self._reset_data() |
496 | |
497 | def supports_tree_reference(self): |
498 | - return False |
499 | + return True |
500 | |
501 | def supports_rename_tracking(self): |
502 | return False |
503 | @@ -455,7 +459,8 @@ |
504 | abspath = self.abspath(filepath) |
505 | kind = osutils.file_kind(abspath) |
506 | if kind in ("file", "symlink"): |
507 | - if filepath in self.index: |
508 | + (index, subpath) = self._lookup_index(filepath.encode('utf-8')) |
509 | + if subpath in index: |
510 | # Already present |
511 | continue |
512 | call_action(filepath, kind) |
513 | @@ -463,7 +468,8 @@ |
514 | self._index_add_entry(filepath, kind) |
515 | added.append(filepath) |
516 | elif kind == "directory": |
517 | - if filepath not in self.index: |
518 | + (index, subpath) = self._lookup_index(filepath.encode('utf-8')) |
519 | + if subpath not in index: |
520 | call_action(filepath, kind) |
521 | if recurse: |
522 | user_dirs.append(filepath) |
523 | @@ -546,10 +552,27 @@ |
524 | """Yield all unversioned files in this WorkingTree. |
525 | """ |
526 | with self.lock_read(): |
527 | - for p in (set(self._iter_files_recursive(include_dirs=True)) - set([p.decode('utf-8') for p in self.index])): |
528 | + index_paths = set([p.decode('utf-8') for p, i in self._recurse_index_entries()]) |
529 | + all_paths = set(self._iter_files_recursive(include_dirs=True)) |
530 | + for p in (all_paths - index_paths): |
531 | if not self._has_dir(p): |
532 | yield p |
533 | |
534 | + def _gather_kinds(self, files, kinds): |
535 | + """See MutableTree._gather_kinds.""" |
536 | + with self.lock_tree_write(): |
537 | + for pos, f in enumerate(files): |
538 | + if kinds[pos] is None: |
539 | + fullpath = osutils.normpath(self.abspath(f)) |
540 | + try: |
541 | + kind = osutils.file_kind(fullpath) |
542 | + except OSError as e: |
543 | + if e.errno == errno.ENOENT: |
544 | + raise errors.NoSuchFile(fullpath) |
545 | + if kind == 'directory' and f != '' and os.path.exists(os.path.join(fullpath, '.git')): |
546 | + kind = 'tree-reference' |
547 | + kinds[pos] = kind |
548 | + |
549 | def flush(self): |
550 | if self._lock_mode != 'w': |
551 | raise errors.NotWriteLocked(self) |
552 | @@ -665,8 +688,9 @@ |
553 | |
554 | def get_file_verifier(self, path, file_id=None, stat_value=None): |
555 | with self.lock_read(): |
556 | + (index, subpath) = self._lookup_index(path.encode('utf-8')) |
557 | try: |
558 | - return ("GIT", self.index[path.encode('utf-8')].sha) |
559 | + return ("GIT", index[subpath].sha) |
560 | except KeyError: |
561 | if self._has_dir(path): |
562 | return ("GIT", None) |
563 | @@ -699,8 +723,9 @@ |
564 | |
565 | def stored_kind(self, path, file_id=None): |
566 | with self.lock_read(): |
567 | + (index, subpath) = self._lookup_index(path.encode('utf-8')) |
568 | try: |
569 | - return mode_kind(self.index[path.encode("utf-8")].mode) |
570 | + return mode_kind(index[subpath].mode) |
571 | except KeyError: |
572 | # Maybe it's a directory? |
573 | if self._has_dir(path): |
574 | @@ -715,8 +740,9 @@ |
575 | if getattr(self, "_supports_executable", osutils.supports_executable)(): |
576 | mode = self._lstat(path).st_mode |
577 | else: |
578 | + (index, subpath) = self._lookup_index(path.encode('utf-8')) |
579 | try: |
580 | - mode = self.index[path.encode('utf-8')].mode |
581 | + mode = index[subpath].mode |
582 | except KeyError: |
583 | mode = 0 |
584 | return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode) |
585 | @@ -733,7 +759,8 @@ |
586 | dir_ids = {} |
587 | fk_entries = {'directory': tree.TreeDirectory, |
588 | 'file': tree.TreeFile, |
589 | - 'symlink': tree.TreeLink} |
590 | + 'symlink': tree.TreeLink, |
591 | + 'tree-reference': tree.TreeReference} |
592 | with self.lock_read(): |
593 | root_ie = self._get_dir_ie(u"", None) |
594 | if include_root and not from_dir: |
595 | @@ -747,19 +774,20 @@ |
596 | and not self.mapping.is_special_file(name)]) |
597 | for path in path_iterator: |
598 | try: |
599 | - index_path = path.encode("utf-8") |
600 | + encoded_path = path.encode("utf-8") |
601 | except UnicodeEncodeError: |
602 | raise errors.BadFilenameEncoding( |
603 | path, osutils._fs_enc) |
604 | + (index, index_path) = self._lookup_index(encoded_path) |
605 | try: |
606 | - value = self.index[index_path] |
607 | + value = index[index_path] |
608 | except KeyError: |
609 | value = None |
610 | - kind = osutils.file_kind(self.abspath(path)) |
611 | + kind = self.kind(path) |
612 | parent, name = posixpath.split(path) |
613 | for dir_path, dir_ie in self._add_missing_parent_ids(parent, dir_ids): |
614 | pass |
615 | - if kind == 'directory': |
616 | + if kind in ('directory', 'tree-reference'): |
617 | if path != from_dir: |
618 | if self._has_dir(path): |
619 | ie = self._get_dir_ie(path, self.path2id(path)) |
620 | @@ -810,10 +838,6 @@ |
621 | paths.add(path) |
622 | return paths |
623 | |
624 | - def _directory_is_tree_reference(self, path): |
625 | - # FIXME: Check .gitsubmodules for path |
626 | - return False |
627 | - |
628 | def iter_child_entries(self, path, file_id=None): |
629 | encoded_path = path.encode('utf-8') |
630 | with self.lock_read(): |
631 | @@ -1040,14 +1064,21 @@ |
632 | def apply_inventory_delta(self, changes): |
633 | for (old_path, new_path, file_id, ie) in changes: |
634 | if old_path is not None: |
635 | + (index, old_subpath) = self._lookup_index(old_path.encode('utf-8')) |
636 | try: |
637 | - self._index_del_entry(old_path.encode('utf-8')) |
638 | + self._index_del_entry(index, old_subpath) |
639 | except KeyError: |
640 | pass |
641 | else: |
642 | self._versioned_dirs = None |
643 | if new_path is not None and ie.kind != 'directory': |
644 | - self._index_add_entry(new_path, ie.kind) |
645 | + if ie.kind == 'tree-reference': |
646 | + self._index_add_entry( |
647 | + new_path, ie.kind, |
648 | + reference_revision=ie.reference_revision) |
649 | + else: |
650 | + self._index_add_entry(new_path, ie.kind) |
651 | + self.flush() |
652 | |
653 | def annotate_iter(self, path, file_id=None, |
654 | default_revision=_mod_revision.CURRENT_REVISION): |
655 | @@ -1142,7 +1173,8 @@ |
656 | st = os.stat_result((entry.mode, 0, 0, 0, |
657 | 0, 0, len(obj.as_raw_string()), 0, |
658 | 0, 0)) |
659 | - self.index[entry.path] = index_entry_from_stat(st, entry.sha, 0) |
660 | + (index, subpath) = self._lookup_index(entry.path) |
661 | + index[subpath] = index_entry_from_stat(st, entry.sha, 0) |
662 | |
663 | def pull(self, source, overwrite=False, stop_revision=None, |
664 | change_reporter=None, possible_transports=None, local=False, |
665 | @@ -1166,6 +1198,77 @@ |
666 | show_base=show_base) |
667 | return count |
668 | |
669 | + def add_reference(self, sub_tree): |
670 | + """Add a TreeReference to the tree, pointing at sub_tree. |
671 | + |
672 | + :param sub_tree: subtree to add. |
673 | + """ |
674 | + with self.lock_tree_write(): |
675 | + try: |
676 | + sub_tree_path = self.relpath(sub_tree.basedir) |
677 | + except errors.PathNotChild: |
678 | + raise BadReferenceTarget( |
679 | + self, sub_tree, 'Target not inside tree.') |
680 | + |
681 | + self._add([sub_tree_path], [None], ['tree-reference']) |
682 | + |
683 | + def _read_submodule_head(self, path): |
684 | + return read_submodule_head(self.abspath(path)) |
685 | + |
686 | + def get_reference_revision(self, path, file_id=None): |
687 | + hexsha = self._read_submodule_head(path) |
688 | + if hexsha is None: |
689 | + return _mod_revision.NULL_REVISION |
690 | + return self.branch.lookup_foreign_revision_id(hexsha) |
691 | + |
692 | + def get_nested_tree(self, path, file_id=None): |
693 | + return workingtree.WorkingTree.open(self.abspath(path)) |
694 | + |
695 | + def _directory_is_tree_reference(self, relpath): |
696 | + # as a special case, if a directory contains control files then |
697 | + # it's a tree reference, except that the root of the tree is not |
698 | + return relpath and osutils.lexists(self.abspath(relpath) + u"/.git") |
699 | + |
700 | + def extract(self, sub_path, file_id=None, format=None): |
701 | + """Extract a subtree from this tree. |
702 | + |
703 | + A new branch will be created, relative to the path for this tree. |
704 | + """ |
705 | + def mkdirs(path): |
706 | + segments = osutils.splitpath(path) |
707 | + transport = self.branch.controldir.root_transport |
708 | + for name in segments: |
709 | + transport = transport.clone(name) |
710 | + transport.ensure_base() |
711 | + return transport |
712 | + |
713 | + with self.lock_tree_write(): |
714 | + self.flush() |
715 | + branch_transport = mkdirs(sub_path) |
716 | + if format is None: |
717 | + format = self.controldir.cloning_metadir() |
718 | + branch_transport.ensure_base() |
719 | + branch_bzrdir = format.initialize_on_transport(branch_transport) |
720 | + try: |
721 | + repo = branch_bzrdir.find_repository() |
722 | + except errors.NoRepositoryPresent: |
723 | + repo = branch_bzrdir.create_repository() |
724 | + if not repo.supports_rich_root(): |
725 | + raise errors.RootNotRich() |
726 | + new_branch = branch_bzrdir.create_branch() |
727 | + new_branch.pull(self.branch) |
728 | + for parent_id in self.get_parent_ids(): |
729 | + new_branch.fetch(self.branch, parent_id) |
730 | + tree_transport = self.controldir.root_transport.clone(sub_path) |
731 | + if tree_transport.base != branch_transport.base: |
732 | + tree_bzrdir = format.initialize_on_transport(tree_transport) |
733 | + tree_bzrdir.set_branch_reference(new_branch) |
734 | + else: |
735 | + tree_bzrdir = branch_bzrdir |
736 | + wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION) |
737 | + wt.set_parent_ids(self.get_parent_ids()) |
738 | + return wt |
739 | + |
740 | def _get_check_refs(self): |
741 | """Return the references needed to perform a check of this tree. |
742 | |
743 | @@ -1284,7 +1387,7 @@ |
744 | # replaced with non-empty directories if they have contents. |
745 | dirified = [] |
746 | target_root_path = target.abspath('.').encode(sys.getfilesystemencoding()) |
747 | - for path, index_entry in target.index.iteritems(): |
748 | + for path, index_entry in target._recurse_index_entries(): |
749 | try: |
750 | live_entry = index_entry_from_path( |
751 | target.abspath(path.decode('utf-8')).encode(osutils._fs_enc)) |
Running landing tests failed /ci.breezy- vcs.org/ job/brz- git-dev/ 183/
https:/