Merge lp:~jelmer/brz/per-tree-memorytree into lp:brz

Proposed by Jelmer Vernooij on 2018-09-16
Status: Needs review
Proposed branch: lp:~jelmer/brz/per-tree-memorytree
Merge into: lp:brz
Prerequisite: lp:~jelmer/brz/memorytransport-links
Diff against target: 766 lines (+345/-117)
7 files modified
breezy/git/memorytree.py (+73/-11)
breezy/git/tree.py (+111/-0)
breezy/git/workingtree.py (+0/-96)
breezy/memorytree.py (+142/-7)
breezy/tests/per_tree/__init__.py (+13/-2)
breezy/tests/per_tree/test_path_content_summary.py (+2/-1)
breezy/tests/per_tree/test_tree.py (+4/-0)
To merge this branch: bzr merge lp:~jelmer/brz/per-tree-memorytree
Reviewer Review Type Date Requested Status
Martin Packman 2018-09-16 Approve on 2019-06-14
Review via email: mp+355055@code.launchpad.net

Commit message

Run per_tree tests against MemoryTree and GitMemoryTree.

To post a comment you must log in.
Martin Packman (gz) wrote :

All looks good, thanks!

review: Approve

Unmerged revisions

7104. By Jelmer Vernooij on 2018-09-16

Merge memorytransport-links.

7103. By Jelmer Vernooij on 2018-09-16

More memorytree fixes.

7102. By Jelmer Vernooij on 2018-09-16

Initial work running tests against GitMemoryTree.

7101. By Jelmer Vernooij on 2018-09-15

Add symlink support to MemoryTree.

7100. By Jelmer Vernooij on 2018-09-15

Merge trunk.

7099. By Jelmer Vernooij on 2018-09-15

Fix some memorytree tests.

7098. By Jelmer Vernooij on 2018-09-14

Implement MemoryTree.list_files.

7097. By Jelmer Vernooij on 2018-09-08

Run tests against MemoryTree.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'breezy/git/memorytree.py'
2--- breezy/git/memorytree.py 2018-07-22 21:59:42 +0000
3+++ breezy/git/memorytree.py 2018-09-16 21:29:34 +0000
4@@ -22,12 +22,14 @@
5 import os
6 import posixpath
7 import stat
8+import time
9
10 from dulwich.index import (
11 index_entry_from_stat,
12 )
13 from dulwich.objects import (
14 Blob,
15+ S_ISGITLINK,
16 Tree,
17 )
18
19@@ -41,10 +43,14 @@
20 )
21 from breezy.transport.memory import MemoryTransport
22
23-from .mapping import GitFileIdMap
24+from .mapping import (
25+ GitFileIdMap,
26+ mode_is_executable,
27+ mode_kind,
28+ )
29 from .tree import MutableGitIndexTree
30
31-class GitMemoryTree(MutableGitIndexTree,_mod_tree.Tree):
32+class GitMemoryTree(MutableGitIndexTree):
33 """A Git memory tree."""
34
35 def __init__(self, branch, store, head):
36@@ -75,6 +81,7 @@
37 def put_file_bytes_non_atomic(self, path, bytes, file_id=None):
38 """See MutableTree.put_file_bytes_non_atomic."""
39 self._file_transport.put_bytes(path, bytes)
40+ self._file_mtimes[path] = time.time()
41
42 def _populate_from_branch(self):
43 """Populate the in-tree state from the branch."""
44@@ -91,21 +98,32 @@
45 self._basis_fileid_map = self.mapping.get_fileid_map(
46 self.store.__getitem__, tree_id)
47 tree = self.store[tree_id]
48- self._fileid_map = self._basis_fileid_map.copy()
49+ self._fileid_map= self._basis_fileid_map.copy()
50+ self._file_mtimes = {u'': time.time()}
51
52 trees = [("", tree)]
53 while trees:
54 (path, tree) = trees.pop()
55 for name, mode, sha in tree.iteritems():
56 subpath = posixpath.join(path, name.decode('utf-8'))
57+ escaped_subpath = urlutils.escape(subpath)
58+ self._file_mtimes[subpath] = time.time()
59 if stat.S_ISDIR(mode):
60- self._file_transport.mkdir(subpath)
61+ self._file_transport.mkdir(escaped_subpath)
62 trees.append((subpath, self.store[sha]))
63 elif stat.S_ISREG(mode):
64- self._file_transport.put_bytes(subpath, self.store[sha].data)
65+ self._file_transport.put_bytes(escaped_subpath, self.store[sha].data)
66 self._index_add_entry(subpath, 'file')
67+ elif stat.S_ISLNK(mode):
68+ self._file_transport.symlink(
69+ self.store[sha].data.decode('utf-8'),
70+ escaped_subpath)
71+ elif S_ISGITLINK(mode):
72+ self._file_transport.mkdir(escaped_subpath)
73 else:
74 raise NotImplementedError(self._populate_from_branch)
75+ if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
76+ self.index[subpath.encode('utf-8')] = self._live_entry(subpath.encode('utf-8'))
77
78 def lock_read(self):
79 """Lock the memory tree for reading.
80@@ -123,6 +141,9 @@
81 self._locks -= 1
82 raise
83
84+ def is_locked(self):
85+ return self._locks > 0
86+
87 def lock_tree_write(self):
88 """See MutableTree.lock_tree_write()."""
89 self._locks += 1
90@@ -171,14 +192,14 @@
91 self._locks -= 1
92
93 def _lstat(self, path):
94- mem_stat = self._file_transport.stat(path)
95+ mem_stat = self._file_transport.stat(urlutils.escape(path))
96 stat_val = os.stat_result(
97 (mem_stat.st_mode, 0, 0, 0, 0, 0, mem_stat.st_size, 0, 0, 0))
98 return stat_val
99
100 def _live_entry(self, path):
101+ stat_val = self._lstat(path)
102 path = urlutils.quote_from_bytes(path)
103- stat_val = self._lstat(path)
104 if stat.S_ISDIR(stat_val.st_mode):
105 return None
106 elif stat.S_ISLNK(stat_val.st_mode):
107@@ -190,17 +211,24 @@
108 return index_entry_from_stat(stat_val, blob.id, 0)
109
110 def get_file_with_stat(self, path, file_id=None):
111- return (self.get_file(path, file_id), self._lstat(path))
112+ return (self.get_file(path, file_id), None)
113
114 def get_file(self, path, file_id=None):
115 """See Tree.get_file."""
116- return self._file_transport.get(path)
117+ return self._file_transport.get(urlutils.escape(path))
118
119 def get_file_sha1(self, path, file_id=None, stat_value=None):
120 """See Tree.get_file_sha1()."""
121- stream = self._file_transport.get(path)
122+ stream = self._file_transport.get(urlutils.escape(path))
123 return osutils.sha_file(stream)
124
125+ def get_file_mtime(self, path, file_id=None, stat_value=None):
126+ """See Tree.get_file_sha1()."""
127+ try:
128+ return self._file_mtimes[path]
129+ except KeyError:
130+ raise errors.NoSuchFile(path)
131+
132 def get_parent_ids(self):
133 """See Tree.get_parent_ids.
134
135@@ -246,10 +274,44 @@
136 """See MutableTree.mkdir()."""
137 self.add(path, None, 'directory')
138 self._file_transport.mkdir(path)
139+ self._file_mtimes[path] = time.time()
140
141 def _rename_one(self, from_rel, to_rel):
142 self._file_transport.rename(from_rel, to_rel)
143
144- def kind(self, p):
145+ def kind(self, p, file_id=None):
146 stat_value = self._file_transport.stat(p)
147 return osutils.file_kind_from_stat_mode(stat_value.st_mode)
148+
149+ def is_executable(self, path, file_id=None):
150+ return mode_is_executable(self._lstat(path).st_mode)
151+
152+ def get_file_size(self, path, file_id=None):
153+ stat_value = self._file_transport.stat(urlutils.escape(path))
154+ if mode_kind(stat_value.st_mode) == 'file':
155+ return stat_value.st_size
156+ return None
157+
158+ def get_symlink_target(self, path, file_id=None):
159+ return self._file_transport.readlink(path)
160+
161+ def path_content_summary(self, path):
162+ """See Tree.path_content_summary."""
163+ try:
164+ entry = self.index[path.encode('utf-8')]
165+ except KeyError:
166+ return 'missing', None, None, None
167+ kind = mode_kind(entry.mode)
168+ if kind == 'file':
169+ executable = mode_is_executable(entry.mode)
170+ sha1 = None # no stat cache
171+ return (kind, entry.size, executable, sha1)
172+ elif kind == 'directory':
173+ # memory tree does not support nested trees yet.
174+ return kind, None, None, None
175+ elif kind == 'symlink':
176+ return kind, None, None, self.store[entry.sha].data.decode('utf-8')
177+ else:
178+ return kind, None, None, None
179+
180+
181
182=== modified file 'breezy/git/tree.py'
183--- breezy/git/tree.py 2018-09-14 16:53:25 +0000
184+++ breezy/git/tree.py 2018-09-16 21:29:34 +0000
185@@ -997,6 +997,9 @@
186 file = BytesIO()
187 stat_val = os.stat_result(
188 (stat.S_IFREG | 0o644, 0, 0, 0, 0, 0, 0, 0, 0, 0))
189+ else:
190+ if stat_val is None:
191+ stat_val = self._lstat(path)
192 with file:
193 blob.set_raw_string(file.read())
194 # Add object to the repository if it didn't exist yet
195@@ -1354,8 +1357,116 @@
196 return kind
197
198 def _live_entry(self, relpath):
199+ """Return the live IndexEntry for a path.
200+
201+ :param relpath: A Git-encoded path
202+ :return: An IndexEntry
203+ """
204 raise NotImplementedError(self._live_entry)
205
206+ def _lstat(self, relpath):
207+ """Return the stat entry for the specified path.
208+
209+ :param relpath: A tree path
210+ """
211+ raise NotImplemented(self._lstat)
212+
213+ def iter_child_entries(self, path, file_id=None):
214+ encoded_path = path.encode('utf-8')
215+ with self.lock_read():
216+ parent_id = self.path2id(path)
217+ found_any = False
218+ seen_children = set()
219+ for item_path, value in self.index.iteritems():
220+ decoded_item_path = item_path.decode('utf-8')
221+ if self.mapping.is_special_file(item_path):
222+ continue
223+ if not osutils.is_inside(path, decoded_item_path):
224+ continue
225+ found_any = True
226+ subpath = posixpath.relpath(decoded_item_path, path)
227+ if '/' in subpath:
228+ dirname = subpath.split('/', 1)[0]
229+ file_ie = self._get_dir_ie(posixpath.join(path, dirname), parent_id)
230+ else:
231+ (unused_parent, name) = posixpath.split(decoded_item_path)
232+ file_ie = self._get_file_ie(
233+ name, decoded_item_path, value, parent_id)
234+ yield file_ie
235+ if not found_any and path != u'':
236+ raise errors.NoSuchFile(path)
237+
238+ def all_file_ids(self):
239+ with self.lock_read():
240+ ids = {u"": self.path2id("")}
241+ for path in self.index:
242+ if self.mapping.is_special_file(path):
243+ continue
244+ path = path.decode("utf-8")
245+ parent = posixpath.dirname(path).strip("/")
246+ for e in self._add_missing_parent_ids(parent, ids):
247+ pass
248+ ids[path] = self.path2id(path)
249+ return set(ids.values())
250+
251+ def annotate_iter(self, path, file_id=None,
252+ default_revision=CURRENT_REVISION):
253+ """See Tree.annotate_iter
254+
255+ This implementation will use the basis tree implementation if possible.
256+ Lines not in the basis are attributed to CURRENT_REVISION
257+
258+ If there are pending merges, lines added by those merges will be
259+ incorrectly attributed to CURRENT_REVISION (but after committing, the
260+ attribution will be correct).
261+ """
262+ with self.lock_read():
263+ maybe_file_parent_keys = []
264+ for parent_id in self.get_parent_ids():
265+ try:
266+ parent_tree = self.revision_tree(parent_id)
267+ except errors.NoSuchRevisionInTree:
268+ parent_tree = self.branch.repository.revision_tree(
269+ parent_id)
270+ with parent_tree.lock_read():
271+ # TODO(jelmer): Use rename/copy tracker to find path name in parent
272+ parent_path = path
273+ try:
274+ kind = parent_tree.kind(parent_path)
275+ except errors.NoSuchFile:
276+ continue
277+ if kind != 'file':
278+ # Note: this is slightly unnecessary, because symlinks and
279+ # directories have a "text" which is the empty text, and we
280+ # know that won't mess up annotations. But it seems cleaner
281+ continue
282+ parent_text_key = (
283+ parent_path,
284+ parent_tree.get_file_revision(parent_path))
285+ if parent_text_key not in maybe_file_parent_keys:
286+ maybe_file_parent_keys.append(parent_text_key)
287+ # Now we have the parents of this content
288+ from breezy.annotate import Annotator
289+ from .annotate import AnnotateProvider
290+ annotate_provider = AnnotateProvider(
291+ self.branch.repository._file_change_scanner)
292+ annotator = Annotator(annotate_provider)
293+
294+ from breezy.graph import Graph
295+ graph = Graph(annotate_provider)
296+ heads = graph.heads(maybe_file_parent_keys)
297+ file_parent_keys = []
298+ for key in maybe_file_parent_keys:
299+ if key in heads:
300+ file_parent_keys.append(key)
301+
302+ text = self.get_file_text(path)
303+ this_key = (path, default_revision)
304+ annotator.add_special_text(this_key, file_parent_keys, text)
305+ annotations = [(key[-1], line)
306+ for key, line in annotator.annotate_flat(this_key)]
307+ return annotations
308+
309
310 class InterIndexGitTree(InterGitTrees):
311 """InterTree that works between a Git revision tree and an index."""
312
313=== modified file 'breezy/git/workingtree.py'
314--- breezy/git/workingtree.py 2018-09-01 19:51:29 +0000
315+++ breezy/git/workingtree.py 2018-09-16 21:29:34 +0000
316@@ -803,19 +803,6 @@
317 ie = fk_entries[kind]()
318 yield posixpath.relpath(path, from_dir), ("I" if self.is_ignored(path) else "?"), kind, None, ie
319
320- def all_file_ids(self):
321- with self.lock_read():
322- ids = {u"": self.path2id("")}
323- for path in self.index:
324- if self.mapping.is_special_file(path):
325- continue
326- path = path.decode("utf-8")
327- parent = posixpath.dirname(path).strip("/")
328- for e in self._add_missing_parent_ids(parent, ids):
329- pass
330- ids[path] = self.path2id(path)
331- return set(ids.values())
332-
333 def all_versioned_paths(self):
334 with self.lock_read():
335 paths = {u""}
336@@ -831,31 +818,6 @@
337 paths.add(path)
338 return paths
339
340- def iter_child_entries(self, path, file_id=None):
341- encoded_path = path.encode('utf-8')
342- with self.lock_read():
343- parent_id = self.path2id(path)
344- found_any = False
345- seen_children = set()
346- for item_path, value in self.index.iteritems():
347- decoded_item_path = item_path.decode('utf-8')
348- if self.mapping.is_special_file(item_path):
349- continue
350- if not osutils.is_inside(path, decoded_item_path):
351- continue
352- found_any = True
353- subpath = posixpath.relpath(decoded_item_path, path)
354- if '/' in subpath:
355- dirname = subpath.split('/', 1)[0]
356- file_ie = self._get_dir_ie(posixpath.join(path, dirname), parent_id)
357- else:
358- (unused_parent, name) = posixpath.split(decoded_item_path)
359- file_ie = self._get_file_ie(
360- name, decoded_item_path, value, parent_id)
361- yield file_ie
362- if not found_any and path != u'':
363- raise errors.NoSuchFile(path)
364-
365 def conflicts(self):
366 with self.lock_read():
367 conflicts = _mod_conflicts.ConflictList()
368@@ -1074,64 +1036,6 @@
369 self._index_add_entry(new_path, ie.kind)
370 self.flush()
371
372- def annotate_iter(self, path, file_id=None,
373- default_revision=_mod_revision.CURRENT_REVISION):
374- """See Tree.annotate_iter
375-
376- This implementation will use the basis tree implementation if possible.
377- Lines not in the basis are attributed to CURRENT_REVISION
378-
379- If there are pending merges, lines added by those merges will be
380- incorrectly attributed to CURRENT_REVISION (but after committing, the
381- attribution will be correct).
382- """
383- with self.lock_read():
384- maybe_file_parent_keys = []
385- for parent_id in self.get_parent_ids():
386- try:
387- parent_tree = self.revision_tree(parent_id)
388- except errors.NoSuchRevisionInTree:
389- parent_tree = self.branch.repository.revision_tree(
390- parent_id)
391- with parent_tree.lock_read():
392- # TODO(jelmer): Use rename/copy tracker to find path name in parent
393- parent_path = path
394- try:
395- kind = parent_tree.kind(parent_path)
396- except errors.NoSuchFile:
397- continue
398- if kind != 'file':
399- # Note: this is slightly unnecessary, because symlinks and
400- # directories have a "text" which is the empty text, and we
401- # know that won't mess up annotations. But it seems cleaner
402- continue
403- parent_text_key = (
404- parent_path,
405- parent_tree.get_file_revision(parent_path))
406- if parent_text_key not in maybe_file_parent_keys:
407- maybe_file_parent_keys.append(parent_text_key)
408- # Now we have the parents of this content
409- from breezy.annotate import Annotator
410- from .annotate import AnnotateProvider
411- annotate_provider = AnnotateProvider(
412- self.branch.repository._file_change_scanner)
413- annotator = Annotator(annotate_provider)
414-
415- from breezy.graph import Graph
416- graph = Graph(annotate_provider)
417- heads = graph.heads(maybe_file_parent_keys)
418- file_parent_keys = []
419- for key in maybe_file_parent_keys:
420- if key in heads:
421- file_parent_keys.append(key)
422-
423- text = self.get_file_text(path)
424- this_key = (path, default_revision)
425- annotator.add_special_text(this_key, file_parent_keys, text)
426- annotations = [(key[-1], line)
427- for key, line in annotator.annotate_flat(this_key)]
428- return annotations
429-
430 def _rename_one(self, from_rel, to_rel):
431 os.rename(self.abspath(from_rel), self.abspath(to_rel))
432
433
434=== modified file 'breezy/memorytree.py'
435--- breezy/memorytree.py 2018-07-03 01:41:55 +0000
436+++ breezy/memorytree.py 2018-09-16 21:29:34 +0000
437@@ -22,11 +22,13 @@
438 from __future__ import absolute_import
439
440 import os
441+import time
442
443 from . import (
444 errors,
445 lock,
446 revision as _mod_revision,
447+ urlutils,
448 )
449 from .bzr.inventory import Inventory
450 from .bzr.inventorytree import MutableInventoryTree
451@@ -45,6 +47,7 @@
452 def __init__(self, branch, revision_id):
453 """Construct a MemoryTree for branch using revision_id."""
454 self.branch = branch
455+ self.repository = branch.repository
456 self.controldir = branch.controldir
457 self._branch_revision_id = revision_id
458 self._locks = 0
459@@ -94,6 +97,21 @@
460 stream = self._file_transport.get(path)
461 return sha_file(stream)
462
463+ def get_file_mtime(self, path, file_id=None, stat_value=None):
464+ """See Tree.get_file_sha1()."""
465+ try:
466+ return self._file_mtimes[path]
467+ except KeyError:
468+ raise errors.NoSuchFile(path)
469+
470+ def get_file_size(self, path, file_id=None):
471+ """See Tree.get_file_size()."""
472+ return self._file_transport.stat(path).st_size
473+
474+ def get_symlink_target(self, path, file_id=None):
475+ """See Tree.get_symlink_target()."""
476+ return self._file_transport.readlink(path)
477+
478 def get_root_id(self):
479 return self.path2id('')
480
481@@ -120,16 +138,16 @@
482 if kind == 'file':
483 bytes = self._file_transport.get_bytes(path)
484 size = len(bytes)
485- executable = self._inventory[id].executable
486+ executable = self._inventory.get_entry(id).executable
487 sha1 = None # no stat cache
488 return (kind, size, executable, sha1)
489 elif kind == 'directory':
490 # memory tree does not support nested trees yet.
491 return kind, None, None, None
492 elif kind == 'symlink':
493- raise NotImplementedError('symlink support')
494+ return kind, None, None, self._file_transport.readlink(path)
495 else:
496- raise NotImplementedError('unknown kind')
497+ return kind, None, None, None
498
499 def get_parent_ids(self):
500 """See Tree.get_parent_ids.
501@@ -145,12 +163,14 @@
502 return self._file_transport.has(filename)
503
504 def is_executable(self, path, file_id=None):
505- return self._inventory[file_id].executable
506+ if file_id is None:
507+ file_id = self.path2id(path)
508+ return self._inventory.get_entry(file_id).executable
509
510 def kind(self, path, file_id=None):
511 if file_id is None:
512 file_id = self.path2id(path)
513- return self._inventory[file_id].kind
514+ return self._inventory.get_entry(file_id).kind
515
516 def mkdir(self, path, file_id=None):
517 """See MutableTree.mkdir()."""
518@@ -158,6 +178,7 @@
519 if file_id is None:
520 file_id = self.path2id(path)
521 self._file_transport.mkdir(path)
522+ self._file_mtimes[path] = time.time()
523 return file_id
524
525 def last_revision(self):
526@@ -211,6 +232,9 @@
527 self._locks -= 1
528 raise
529
530+ def is_locked(self):
531+ return self._locks > 0
532+
533 def _populate_from_branch(self):
534 """Populate the in-tree state from the branch."""
535 self._set_basis()
536@@ -220,23 +244,33 @@
537 self._parent_ids = [self._branch_revision_id]
538 self._inventory = Inventory(None, self._basis_tree.get_revision_id())
539 self._file_transport = MemoryTransport()
540+ self._file_mtimes = {}
541 # TODO copy the revision trees content, or do it lazy, or something.
542 inventory_entries = self._basis_tree.iter_entries_by_dir()
543 for path, entry in inventory_entries:
544 self._inventory.add(entry.copy())
545+ self._file_mtimes[path] = time.time()
546 if path == '':
547 continue
548+ escaped_path = urlutils.escape(path)
549 if entry.kind == 'directory':
550- self._file_transport.mkdir(path)
551+ self._file_transport.mkdir(escaped_path)
552 elif entry.kind == 'file':
553- self._file_transport.put_file(path,
554+ self._file_transport.put_file(escaped_path,
555 self._basis_tree.get_file(path, entry.file_id))
556+ elif entry.kind == 'symlink':
557+ self._file_transport.symlink(
558+ self._basis_tree.get_symlink_target(path, entry.file_id),
559+ escaped_path)
560+ elif entry.kind == 'tree-reference':
561+ self._file_transport.mkdir(escaped_path)
562 else:
563 raise NotImplementedError(self._populate_from_branch)
564
565 def put_file_bytes_non_atomic(self, path, bytes, file_id=None):
566 """See MutableTree.put_file_bytes_non_atomic."""
567 self._file_transport.put_bytes(path, bytes)
568+ self._file_mtimes[path] = time.time()
569
570 def unlock(self):
571 """Release a lock.
572@@ -325,3 +359,104 @@
573 else:
574 self._basis_tree = parents_list[0][1]
575 self._branch_revision_id = parents_list[0][0]
576+
577+ def annotate_iter(self, path, file_id=None,
578+ default_revision=_mod_revision.CURRENT_REVISION):
579+ """See Tree.annotate_iter
580+
581+ This implementation will use the basis tree implementation if possible.
582+ Lines not in the basis are attributed to CURRENT_REVISION
583+
584+ If there are pending merges, lines added by those merges will be
585+ incorrectly attributed to CURRENT_REVISION (but after committing, the
586+ attribution will be correct).
587+ """
588+ with self.lock_read():
589+ if file_id is None:
590+ file_id = self.path2id(path)
591+ if file_id is None:
592+ raise errors.NoSuchFile(path)
593+ maybe_file_parent_keys = []
594+ for parent_id in self.get_parent_ids():
595+ try:
596+ parent_tree = self.revision_tree(parent_id)
597+ except errors.NoSuchRevisionInTree:
598+ parent_tree = self.branch.repository.revision_tree(
599+ parent_id)
600+ with parent_tree.lock_read():
601+
602+ try:
603+ kind = parent_tree.kind(path, file_id)
604+ except errors.NoSuchFile:
605+ continue
606+ if kind != 'file':
607+ # Note: this is slightly unnecessary, because symlinks and
608+ # directories have a "text" which is the empty text, and we
609+ # know that won't mess up annotations. But it seems cleaner
610+ continue
611+ parent_path = parent_tree.id2path(file_id)
612+ parent_text_key = (
613+ file_id,
614+ parent_tree.get_file_revision(parent_path, file_id))
615+ if parent_text_key not in maybe_file_parent_keys:
616+ maybe_file_parent_keys.append(parent_text_key)
617+ graph = self.branch.repository.get_file_graph()
618+ heads = graph.heads(maybe_file_parent_keys)
619+ file_parent_keys = []
620+ for key in maybe_file_parent_keys:
621+ if key in heads:
622+ file_parent_keys.append(key)
623+
624+ # Now we have the parents of this content
625+ annotator = self.branch.repository.texts.get_annotator()
626+ text = self.get_file_text(path, file_id)
627+ this_key = (file_id, default_revision)
628+ annotator.add_special_text(this_key, file_parent_keys, text)
629+ annotations = [(key[-1], line)
630+ for key, line in annotator.annotate_flat(this_key)]
631+ return annotations
632+
633+ def list_files(self, include_root=False, from_dir=None, recursive=True):
634+ # The only files returned by this are those from the version
635+ if from_dir is None:
636+ from_dir_id = None
637+ inv = self.root_inventory
638+ else:
639+ inv, from_dir_id = self._path2inv_file_id(from_dir)
640+ if from_dir_id is None:
641+ # Directory not versioned
642+ return
643+ entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
644+ if inv.root is not None and not include_root and from_dir is None:
645+ # skip the root for compatability with the current apis.
646+ next(entries)
647+ for path, entry in entries:
648+ yield path, 'V', entry.kind, entry.file_id, entry
649+
650+ def walkdirs(self, prefix=""):
651+ _directory = 'directory'
652+ inv, top_id = self._path2inv_file_id(prefix)
653+ if top_id is None:
654+ pending = []
655+ else:
656+ pending = [(prefix, '', _directory, None, top_id, None)]
657+ while pending:
658+ dirblock = []
659+ currentdir = pending.pop()
660+ # 0 - relpath, 1- basename, 2- kind, 3- stat, id, v-kind
661+ if currentdir[0]:
662+ relroot = currentdir[0] + '/'
663+ else:
664+ relroot = ""
665+ # FIXME: stash the node in pending
666+ entry = inv.get_entry(currentdir[4])
667+ for name, child in entry.sorted_children():
668+ toppath = relroot + name
669+ dirblock.append((toppath, name, child.kind, None,
670+ child.file_id, child.kind
671+ ))
672+ yield (currentdir[0], entry.file_id), dirblock
673+ # push the user specified dirs from dirblock
674+ for dir in reversed(dirblock):
675+ if dir[2] == _directory:
676+ pending.append(dir)
677
678=== modified file 'breezy/tests/per_tree/__init__.py'
679--- breezy/tests/per_tree/__init__.py 2018-09-15 00:32:05 +0000
680+++ breezy/tests/per_tree/__init__.py 2018-09-16 21:29:34 +0000
681@@ -38,6 +38,7 @@
682 make_scenarios as wt_make_scenarios,
683 make_scenario as wt_make_scenario,
684 )
685+from breezy.memorytree import MemoryTree
686 from breezy.revisiontree import RevisionTree
687 from breezy.transform import TransformPreview
688 from breezy.tests import (
689@@ -64,6 +65,12 @@
690 return tree.branch.repository.revision_tree(revid)
691
692
693+def memory_tree_from_workingtree(testcase, tree):
694+ """Create a memory tree from a working tree."""
695+ revid = tree.commit('save tree', allow_pointless=True, recursive=None)
696+ return tree.branch.create_memorytree()
697+
698+
699 def _dirstate_tree_from_workingtree(testcase, tree):
700 revid = tree.commit('save tree', allow_pointless=True, recursive=None)
701 return tree.basis_tree()
702@@ -286,8 +293,6 @@
703 DirStateRevisionTree by committing a working tree to create the revision
704 tree.
705 """
706- # TODO(jelmer): Test MemoryTree here
707- # TODO(jelmer): Test GitMemoryTree here
708 scenarios = wt_make_scenarios(transport_server, transport_readonly_server,
709 formats)
710 # now adjust the scenarios and add the non-working-tree tree scenarios.
711@@ -312,6 +317,12 @@
712 scenarios.append((DirStateRevisionTree.__name__ + ",WT5",
713 create_tree_scenario(transport_server, transport_readonly_server,
714 WorkingTreeFormat5(), _dirstate_tree_from_workingtree)))
715+ scenarios.append(("InventoryMemoryTree",
716+ create_tree_scenario(transport_server, transport_readonly_server,
717+ WorkingTreeFormat5(), memory_tree_from_workingtree)))
718+ scenarios.append(("GitMemoryTree",
719+ create_tree_scenario(transport_server, transport_readonly_server,
720+ GitWorkingTreeFormat(), memory_tree_from_workingtree)))
721 scenarios.append(("PreviewTree", create_tree_scenario(transport_server,
722 transport_readonly_server, workingtree_format, preview_tree_pre)))
723 scenarios.append(("PreviewTreePost", create_tree_scenario(transport_server,
724
725=== modified file 'breezy/tests/per_tree/test_path_content_summary.py'
726--- breezy/tests/per_tree/test_path_content_summary.py 2018-09-09 02:40:56 +0000
727+++ breezy/tests/per_tree/test_path_content_summary.py 2018-09-16 21:29:34 +0000
728@@ -109,7 +109,8 @@
729 summary = tree.path_content_summary('path')
730 self.assertEqual(4, len(summary))
731 if isinstance(tree, (per_tree.DirStateRevisionTree,
732- per_tree.RevisionTree)):
733+ per_tree.RevisionTree,
734+ per_tree.MemoryTree)):
735 self.assertEqual('missing', summary[0])
736 self.assertIs(None, summary[2])
737 self.assertIs(None, summary[3])
738
739=== modified file 'breezy/tests/per_tree/test_tree.py'
740--- breezy/tests/per_tree/test_tree.py 2018-07-27 18:57:21 +0000
741+++ breezy/tests/per_tree/test_tree.py 2018-09-16 21:29:34 +0000
742@@ -17,6 +17,7 @@
743 from breezy import (
744 errors,
745 conflicts,
746+ memorytree,
747 osutils,
748 revisiontree,
749 tests,
750@@ -24,6 +25,7 @@
751 from breezy.bzr import (
752 workingtree_4,
753 )
754+from breezy.git import memorytree as git_memorytree
755 from breezy.tests import TestSkipped
756 from breezy.tests.per_tree import TestCaseWithTree
757
758@@ -320,6 +322,8 @@
759 tree = self._convert_tree(work_tree)
760 if isinstance(tree,
761 (revisiontree.RevisionTree,
762+ git_memorytree.GitMemoryTree,
763+ memorytree.MemoryTree,
764 workingtree_4.DirStateRevisionTree)):
765 expected = []
766 else:

Subscribers

People subscribed via source and target branches