Merge lp:~jelmer/brz-git/submodules into lp:brz-git

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
Reviewer Review Type Date Requested Status
Jelmer Vernooij Approve
Review via email: mp+345129@code.launchpad.net

Commit message

Add basic support for submodules.

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))

Subscribers

People subscribed via source and target branches

to all changes: