/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.358.2 by Jelmer Vernooij
Refresh copyright headers, add my email.
1
# Copyright (C) 2009-2018 Jelmer Vernooij <jelmer@jelmer.uk>
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
0.358.1 by Jelmer Vernooij
Fix FSF address.
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
16
17
18
"""Git Trees."""
19
0.200.1594 by Jelmer Vernooij
Use absolute_import everywhere.
20
from __future__ import absolute_import
21
0.360.2 by Jelmer Vernooij
Fix test.
22
import errno
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
23
from io import BytesIO
24
import os
25
26
from dulwich.index import (
27
    index_entry_from_stat,
28
    )
0.200.1780 by Jelmer Vernooij
Fix cross-object-store tree comparison.
29
from dulwich.object_store import (
30
    tree_lookup_path,
31
    OverlayObjectStore,
32
    )
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
33
from dulwich.objects import (
34
    Blob,
35
    Tree,
36
    )
0.264.3 by Jelmer Vernooij
Make RevisionTree inventoryless.
37
import stat
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
38
import posixpath
0.264.3 by Jelmer Vernooij
Make RevisionTree inventoryless.
39
0.200.1641 by Jelmer Vernooij
Use relative imports where possible.
40
from ... import (
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
41
    delta,
42
    errors,
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
43
    lock,
44
    mutabletree,
0.264.10 by Jelmer Vernooij
Yield inventory entries.
45
    osutils,
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
46
    revisiontree,
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
47
    trace,
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
48
    tree as _mod_tree,
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
49
    workingtree,
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
50
    )
0.200.1731 by Jelmer Vernooij
Add support for checking untracked changes.
51
from ...revision import NULL_REVISION
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
52
0.200.1641 by Jelmer Vernooij
Use relative imports where possible.
53
from .mapping import (
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
54
    mode_is_executable,
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
55
    mode_kind,
0.200.1731 by Jelmer Vernooij
Add support for checking untracked changes.
56
    GitFileIdMap,
0.287.2 by Jelmer Vernooij
Support empty trees in GitRevisionTree.
57
    default_mapping,
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
58
    )
59
60
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
61
class GitTreeDirectory(_mod_tree.TreeDirectory):
62
63
    __slots__ = ['file_id', 'name', 'parent_id', 'children', 'revision']
64
65
    def __init__(self, file_id, name, parent_id, revision=None):
66
        self.file_id = file_id
67
        self.name = name
68
        self.parent_id = parent_id
69
        # TODO(jelmer)
70
        self.children = {}
71
        self.revision = revision
72
73
    @property
74
    def kind(self):
75
        return 'directory'
76
77
    @property
78
    def executable(self):
79
        return False
80
81
    def copy(self):
82
        return self.__class__(
83
            self.file_id, self.name, self.parent_id, self.revision)
84
85
    def __repr__(self):
86
        return "%s(file_id=%r, name=%r, parent_id=%r, revision=%r)" % (
87
            self.__class__.__name__, self.file_id, self.name,
88
            self.parent_id, self.revision)
89
90
    def __eq__(self, other):
91
        return (self.kind == other.kind and
92
                self.file_id == other.file_id and
93
                self.name == other.name and
94
                self.parent_id == other.parent_id and
95
                self.revision == other.revision)
96
97
98
class GitTreeFile(_mod_tree.TreeFile):
99
100
    __slots__ = ['file_id', 'name', 'parent_id', 'text_size', 'text_sha1', 'revision',
101
                 'executable']
102
103
    def __init__(self, file_id, name, parent_id, revision=None):
104
        self.file_id = file_id
105
        self.name = name
106
        self.parent_id = parent_id
107
        self.revision = revision
108
        self.text_size = None
109
        self.text_sha1 = None
110
        self.executable = None
111
112
    @property
113
    def kind(self):
114
        return 'file'
115
116
    def __eq__(self, other):
117
        return (self.kind == other.kind and
118
                self.file_id == other.file_id and
119
                self.name == other.name and
120
                self.parent_id == other.parent_id and
121
                self.revision == other.revision and
122
                self.text_sha1 == other.text_sha1 and
123
                self.text_size == other.text_size and
124
                self.executable == other.executable)
125
0.365.2 by Jelmer Vernooij
Add eq/copy.
126
    def copy(self):
127
        ret = self.__class__(
128
                self.file_id, self.name, self.parent_id,
129
                self.revision)
130
        ret.text_sha1 = self.text_sha1
131
        ret.text_size = self.text_size
132
        ret.executable = self.executable
133
        return ret
134
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
135
136
class GitTreeSymlink(_mod_tree.TreeLink):
137
138
    __slots__ = ['file_id', 'name', 'parent_id', 'symlink_target', 'revision']
139
0.365.2 by Jelmer Vernooij
Add eq/copy.
140
    def __init__(self, file_id, name, parent_id, revision=None,
141
                 symlink_target=None):
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
142
        self.file_id = file_id
143
        self.name = name
144
        self.parent_id = parent_id
0.365.2 by Jelmer Vernooij
Add eq/copy.
145
        self.revision = revision
146
        self.symlink_target = symlink_target
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
147
148
    @property
149
    def kind(self):
150
        return 'symlink'
151
152
    @property
153
    def executable(self):
154
        return False
155
156
    @property
157
    def text_size(self):
158
        return None
159
0.365.2 by Jelmer Vernooij
Add eq/copy.
160
    def __eq__(self, other):
161
        return (self.kind == other.kind and
162
                self.file_id == other.file_id and
163
                self.name == other.name and
164
                self.parent_id == other.parent_id and
165
                self.revision == other.revision and
166
                self.symlink_target == other.symlink_target)
167
168
    def copy(self):
169
        return self.__class__(
170
                self.file_id, self.name, self.parent_id,
171
                self.revision, self.symlink_target)
172
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
173
174
entry_factory = {
175
    'directory': GitTreeDirectory,
176
    'file': GitTreeFile,
177
    'symlink': GitTreeSymlink,
178
    }
179
180
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
181
def ensure_normalized_path(path):
182
    """Check whether path is normalized.
183
184
    :raises InvalidNormalization: When path is not normalized, and cannot be
185
        accessed on this platform by the normalized path.
186
    :return: The NFC normalised version of path.
187
    """
188
    norm_path, can_access = osutils.normalized_filename(path)
189
    if norm_path != path:
190
        if can_access:
191
            return norm_path
192
        else:
193
            raise errors.InvalidNormalization(path)
194
    return path
195
196
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
197
class GitRevisionTree(revisiontree.RevisionTree):
0.200.959 by Jelmer Vernooij
Improve docstrings.
198
    """Revision tree implementation based on Git objects."""
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
199
200
    def __init__(self, repository, revision_id):
201
        self._revision_id = revision_id
202
        self._repository = repository
0.264.3 by Jelmer Vernooij
Make RevisionTree inventoryless.
203
        self.store = repository._git.object_store
0.361.1 by Jelmer Vernooij
Don't use assert.
204
        if type(revision_id) is not str:
205
            raise TypeError(revision_id)
0.200.1283 by Jelmer Vernooij
Provide Repository.get_file_graph() and Tree.get_file_revision().
206
        self.commit_id, self.mapping = repository.lookup_bzr_revision_id(revision_id)
0.200.1731 by Jelmer Vernooij
Add support for checking untracked changes.
207
        if revision_id == NULL_REVISION:
208
            self.tree = None
0.287.2 by Jelmer Vernooij
Support empty trees in GitRevisionTree.
209
            self.mapping = default_mapping
210
            self._fileid_map = GitFileIdMap(
0.287.4 by Jelmer Vernooij
Fix GitRevisionTree behaviour for null:
211
                {},
0.287.2 by Jelmer Vernooij
Support empty trees in GitRevisionTree.
212
                default_mapping)
0.200.1731 by Jelmer Vernooij
Add support for checking untracked changes.
213
        else:
214
            try:
215
                commit = self.store[self.commit_id]
216
            except KeyError, r:
217
                raise errors.NoSuchRevision(repository, revision_id)
218
            self.tree = commit.tree
219
            self._fileid_map = self.mapping.get_fileid_map(self.store.__getitem__, self.tree)
0.264.3 by Jelmer Vernooij
Make RevisionTree inventoryless.
220
0.349.1 by Jelmer Vernooij
Support supports_rename_tracking method.
221
    def supports_rename_tracking(self):
222
        return False
223
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
224
    def get_file_revision(self, path, file_id=None):
0.200.1283 by Jelmer Vernooij
Provide Repository.get_file_graph() and Tree.get_file_revision().
225
        change_scanner = self._repository._file_change_scanner
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
226
        (path, commit_id) = change_scanner.find_last_change_revision(
227
            path.encode('utf-8'), self.commit_id)
0.200.1283 by Jelmer Vernooij
Provide Repository.get_file_graph() and Tree.get_file_revision().
228
        return self._repository.lookup_foreign_revision_id(commit_id, self.mapping)
229
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
230
    def get_file_mtime(self, path, file_id=None):
231
        revid = self.get_file_revision(path, file_id)
0.200.1283 by Jelmer Vernooij
Provide Repository.get_file_graph() and Tree.get_file_revision().
232
        try:
233
            rev = self._repository.get_revision(revid)
234
        except errors.NoSuchRevision:
235
            raise errors.FileTimestampUnavailable(path)
236
        return rev.timestamp
237
0.264.3 by Jelmer Vernooij
Make RevisionTree inventoryless.
238
    def id2path(self, file_id):
0.200.1712 by Jelmer Vernooij
Add file_id prefix.
239
        try:
0.329.1 by Jelmer Vernooij
Check that path actually exists in Tree.id2path.
240
            path = self._fileid_map.lookup_path(file_id)
0.200.1712 by Jelmer Vernooij
Add file_id prefix.
241
        except ValueError:
0.200.1714 by Jelmer Vernooij
Fix NoSuchId raising.
242
            raise errors.NoSuchId(self, file_id)
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
243
        path = path.decode('utf-8')
0.329.1 by Jelmer Vernooij
Check that path actually exists in Tree.id2path.
244
        if self.has_filename(path):
245
            return path
246
        raise errors.NoSuchId(self, file_id)
247
248
    def is_versioned(self, path):
249
        return self.has_filename(path)
0.264.3 by Jelmer Vernooij
Make RevisionTree inventoryless.
250
251
    def path2id(self, path):
0.200.1328 by Jelmer Vernooij
More test fixes.
252
        if self.mapping.is_special_file(path):
253
            return None
254
        return self._fileid_map.lookup_file_id(path.encode('utf-8'))
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
255
0.200.1569 by Jelmer Vernooij
Implement GitRevisionTree.all_file_ids().
256
    def all_file_ids(self):
257
        return set(self._fileid_map.all_file_ids())
258
0.200.1724 by Jelmer Vernooij
Add GitRevisionTree.all_versioned_paths implementation.
259
    def all_versioned_paths(self):
260
        ret = set()
261
        todo = set([('', self.tree)])
262
        while todo:
263
            (path, tree_id) = todo.pop()
0.287.2 by Jelmer Vernooij
Support empty trees in GitRevisionTree.
264
            if tree_id is None:
265
                continue
0.200.1724 by Jelmer Vernooij
Add GitRevisionTree.all_versioned_paths implementation.
266
            tree = self.store[tree_id]
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
267
            for name, mode, hexsha in tree.items():
0.200.1724 by Jelmer Vernooij
Add GitRevisionTree.all_versioned_paths implementation.
268
                subpath = posixpath.join(path, name)
269
                if stat.S_ISDIR(mode):
270
                    todo.add((subpath, hexsha))
271
                else:
272
                    ret.add(subpath)
273
        return ret
274
0.200.1204 by Jelmer Vernooij
Implement GitRevisionTree.get_root_id().
275
    def get_root_id(self):
276
        return self.path2id("")
277
0.200.1208 by Jelmer Vernooij
Add GitWorkingTree.has_id and GitWorkingTree.has_or_had_id.
278
    def has_or_had_id(self, file_id):
279
        return self.has_id(file_id)
280
281
    def has_id(self, file_id):
282
        try:
283
            path = self.id2path(file_id)
284
        except errors.NoSuchId:
285
            return False
286
        return self.has_filename(path)
287
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
288
    def is_executable(self, path, file_id=None):
0.200.1615 by Jelmer Vernooij
Implement GitRevisionTree.is_executable.:
289
        try:
290
            (mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
291
                path)
292
        except KeyError:
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
293
            raise errors.NoSuchId(self, path)
0.200.1615 by Jelmer Vernooij
Implement GitRevisionTree.is_executable.:
294
        if mode is None:
295
            # the tree root is a directory
296
            return False
297
        return mode_is_executable(mode)
298
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
299
    def kind(self, path, file_id=None):
0.287.4 by Jelmer Vernooij
Fix GitRevisionTree behaviour for null:
300
        if self.tree is None:
0.287.5 by Jelmer Vernooij
Fix root handling.
301
            raise errors.NoSuchFile(path)
0.200.1283 by Jelmer Vernooij
Provide Repository.get_file_graph() and Tree.get_file_revision().
302
        try:
303
            (mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
304
                path)
305
        except KeyError:
0.287.5 by Jelmer Vernooij
Fix root handling.
306
            raise errors.NoSuchFile(path)
0.200.1253 by Jelmer Vernooij
Fix Tree.kind(TREE_ROOT).
307
        if mode is None:
308
            # the tree root is a directory
309
            return "directory"
0.200.1241 by Jelmer Vernooij
Implement GitRevisionTree.kind.
310
        return mode_kind(mode)
311
0.200.1197 by Jelmer Vernooij
Implement GitRevisionTree.has_filename.
312
    def has_filename(self, path):
0.287.4 by Jelmer Vernooij
Fix GitRevisionTree behaviour for null:
313
        if self.tree is None:
314
            return False
0.200.1197 by Jelmer Vernooij
Implement GitRevisionTree.has_filename.
315
        try:
316
            tree_lookup_path(self.store.__getitem__, self.tree,
317
                path.encode("utf-8"))
318
        except KeyError:
319
            return False
320
        else:
321
            return True
322
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
323
    def list_files(self, include_root=False, from_dir=None, recursive=True):
324
        if from_dir is None:
0.200.1197 by Jelmer Vernooij
Implement GitRevisionTree.has_filename.
325
            from_dir = u""
0.287.2 by Jelmer Vernooij
Support empty trees in GitRevisionTree.
326
        if self.tree is None:
327
            return
0.200.1197 by Jelmer Vernooij
Implement GitRevisionTree.has_filename.
328
        (mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
329
            from_dir.encode("utf-8"))
0.264.9 by Jelmer Vernooij
Implement basic GitWorkingTree.iter_entries_by_dir.
330
        if mode is None: # Root
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
331
            root_ie = self._get_dir_ie(b"", None)
0.264.9 by Jelmer Vernooij
Implement basic GitWorkingTree.iter_entries_by_dir.
332
        else:
0.264.10 by Jelmer Vernooij
Yield inventory entries.
333
            parent_path = posixpath.dirname(from_dir.encode("utf-8"))
0.200.1328 by Jelmer Vernooij
More test fixes.
334
            parent_id = self._fileid_map.lookup_file_id(parent_path)
0.264.10 by Jelmer Vernooij
Yield inventory entries.
335
            if mode_kind(mode) == 'directory':
336
                root_ie = self._get_dir_ie(from_dir.encode("utf-8"), parent_id)
337
            else:
338
                root_ie = self._get_file_ie(from_dir.encode("utf-8"),
339
                    posixpath.basename(from_dir), mode, hexsha)
340
        if from_dir != "" or include_root:
341
            yield (from_dir, "V", root_ie.kind, root_ie.file_id, root_ie)
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
342
        todo = set()
0.264.10 by Jelmer Vernooij
Yield inventory entries.
343
        if root_ie.kind == 'directory':
344
            todo.add((from_dir.encode("utf-8"), hexsha, root_ie.file_id))
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
345
        while todo:
0.264.10 by Jelmer Vernooij
Yield inventory entries.
346
            (path, hexsha, parent_id) = todo.pop()
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
347
            tree = self.store[hexsha]
348
            for name, mode, hexsha in tree.iteritems():
0.200.1328 by Jelmer Vernooij
More test fixes.
349
                if self.mapping.is_special_file(name):
350
                    continue
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
351
                child_path = posixpath.join(path, name)
0.264.10 by Jelmer Vernooij
Yield inventory entries.
352
                if stat.S_ISDIR(mode):
353
                    ie = self._get_dir_ie(child_path, parent_id)
354
                    if recursive:
355
                        todo.add((child_path, hexsha, ie.file_id))
356
                else:
357
                    ie = self._get_file_ie(child_path, name, mode, hexsha, parent_id)
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
358
                yield child_path.decode('utf-8'), "V", ie.kind, ie.file_id, ie
0.264.10 by Jelmer Vernooij
Yield inventory entries.
359
360
    def _get_file_ie(self, path, name, mode, hexsha, parent_id):
0.361.1 by Jelmer Vernooij
Don't use assert.
361
        if type(path) is not bytes:
362
            raise TypeError(path)
363
        if type(name) is not bytes:
364
            raise TypeError(name)
0.264.10 by Jelmer Vernooij
Yield inventory entries.
365
        kind = mode_kind(mode)
0.200.1328 by Jelmer Vernooij
More test fixes.
366
        file_id = self._fileid_map.lookup_file_id(path)
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
367
        ie = entry_factory[kind](file_id, name.decode("utf-8"), parent_id)
0.264.10 by Jelmer Vernooij
Yield inventory entries.
368
        if kind == 'symlink':
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
369
            ie.symlink_target = self.store[hexsha].data.decode('utf-8')
0.200.1401 by Jelmer Vernooij
Cope with submodules in working trees.
370
        elif kind == 'tree-reference':
371
            ie.reference_revision = self.mapping.revision_id_foreign_to_bzr(hexsha)
0.264.10 by Jelmer Vernooij
Yield inventory entries.
372
        else:
373
            data = self.store[hexsha].data
374
            ie.text_sha1 = osutils.sha_string(data)
375
            ie.text_size = len(data)
376
            ie.executable = mode_is_executable(mode)
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
377
        ie.revision = self.get_file_revision(path.decode('utf-8'))
0.264.10 by Jelmer Vernooij
Yield inventory entries.
378
        return ie
379
380
    def _get_dir_ie(self, path, parent_id):
0.200.1328 by Jelmer Vernooij
More test fixes.
381
        file_id = self._fileid_map.lookup_file_id(path)
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
382
        ie = GitTreeDirectory(file_id,
0.264.10 by Jelmer Vernooij
Yield inventory entries.
383
            posixpath.basename(path).decode("utf-8"), parent_id)
0.360.4 by Jelmer Vernooij
Implement MemoryTree.rename_one, MemoryTree.mkdir.
384
        ie.revision = self.get_file_revision(path.decode('utf-8'))
0.287.6 by Jelmer Vernooij
Fix some more tests.
385
        return ie
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
386
0.200.1744 by Jelmer Vernooij
Implement GitRevisionTree.iter_children.
387
    def iter_children(self, file_id):
388
        path = self._fileid_map.lookup_path(file_id)
389
        mode, tree_sha = tree_lookup_path(self.store.__getitem__, self.tree, path)
390
        if stat.S_ISDIR(mode):
391
            for name, mode, hexsha  in self.store[tree_sha].iteritems():
392
                yield self._fileid_map.lookup_file_id(posixpath.join(path, name))
393
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
394
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
0.287.4 by Jelmer Vernooij
Fix GitRevisionTree behaviour for null:
395
        if self.tree is None:
396
            return
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
397
        if yield_parents:
398
            # TODO(jelmer): Support yield parents
399
            raise NotImplementedError
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
400
        if specific_file_ids is not None:
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
401
            specific_paths = [self.id2path(file_id).encode('utf-8') for file_id in specific_file_ids]
402
            if specific_paths in ([""], []):
0.200.1285 by Jelmer Vernooij
Support specific_file_ids argument to Tree.iter_entries_by_dir.
403
                specific_paths = None
404
            else:
405
                specific_paths = set(specific_paths)
406
        else:
407
            specific_paths = None
0.264.10 by Jelmer Vernooij
Yield inventory entries.
408
        todo = set([("", self.tree, None)])
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
409
        while todo:
0.264.10 by Jelmer Vernooij
Yield inventory entries.
410
            path, tree_sha, parent_id = todo.pop()
411
            ie = self._get_dir_ie(path, parent_id)
0.200.1285 by Jelmer Vernooij
Support specific_file_ids argument to Tree.iter_entries_by_dir.
412
            if specific_paths is None or path in specific_paths:
0.200.1715 by Jelmer Vernooij
Fix some more tests.
413
                yield path.decode("utf-8"), ie
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
414
            tree = self.store[tree_sha]
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
415
            for name, mode, hexsha in tree.iteritems():
0.200.1328 by Jelmer Vernooij
More test fixes.
416
                if self.mapping.is_special_file(name):
417
                    continue
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
418
                child_path = posixpath.join(path, name)
419
                if stat.S_ISDIR(mode):
0.200.1285 by Jelmer Vernooij
Support specific_file_ids argument to Tree.iter_entries_by_dir.
420
                    if (specific_paths is None or
421
                        any(filter(lambda p: p.startswith(child_path), specific_paths))):
422
                        todo.add((child_path, hexsha, ie.file_id))
423
                elif specific_paths is None or child_path in specific_paths:
0.200.1715 by Jelmer Vernooij
Fix some more tests.
424
                    yield (child_path.decode("utf-8"),
0.200.1307 by Jelmer Vernooij
Formatting fixes, specify path to a couple more functions.
425
                            self._get_file_ie(child_path, name, mode, hexsha,
0.200.1285 by Jelmer Vernooij
Support specific_file_ids argument to Tree.iter_entries_by_dir.
426
                           ie.file_id))
0.264.6 by Jelmer Vernooij
Implement custom GitRevisionTree.iter_entries_by_dir, GitRevisionTree.list_files.
427
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
428
    def get_revision_id(self):
0.200.959 by Jelmer Vernooij
Improve docstrings.
429
        """See RevisionTree.get_revision_id."""
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
430
        return self._revision_id
431
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
432
    def get_file_sha1(self, path, file_id=None, stat_value=None):
0.287.6 by Jelmer Vernooij
Fix some more tests.
433
        if self.tree is None:
434
            raise errors.NoSuchFile(path)
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
435
        return osutils.sha_string(self.get_file_text(path, file_id))
0.200.1255 by Jelmer Vernooij
Implement GitRevisionTree.get_file_sha1.
436
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
437
    def get_file_verifier(self, path, file_id=None, stat_value=None):
0.200.1302 by Jelmer Vernooij
Significantly improve performance of WorkingTree.extras().
438
        (mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
439
            path.encode('utf-8'))
0.200.1302 by Jelmer Vernooij
Significantly improve performance of WorkingTree.extras().
440
        return ("GIT", hexsha)
441
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
442
    def get_file_text(self, path, file_id=None):
0.200.959 by Jelmer Vernooij
Improve docstrings.
443
        """See RevisionTree.get_file_text."""
0.287.5 by Jelmer Vernooij
Fix root handling.
444
        if self.tree is None:
445
            raise errors.NoSuchFile(path)
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
446
        (mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
447
                path.encode('utf-8'))
0.264.3 by Jelmer Vernooij
Make RevisionTree inventoryless.
448
        if stat.S_ISREG(mode):
449
            return self.store[hexsha].data
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
450
        else:
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
451
            return b""
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
452
0.285.1 by Jelmer Vernooij
Swap arguments for tree methods.
453
    def get_symlink_target(self, path, file_id=None):
0.200.1466 by Jelmer Vernooij
Implement GitRevisionTree.get_symlink_target.
454
        """See RevisionTree.get_symlink_target."""
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
455
        (mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree,
456
                path.encode('utf-8'))
0.200.1466 by Jelmer Vernooij
Implement GitRevisionTree.get_symlink_target.
457
        if stat.S_ISLNK(mode):
0.200.1701 by Jelmer Vernooij
Fix a few tests.
458
            return self.store[hexsha].data.decode('utf-8')
0.200.1466 by Jelmer Vernooij
Implement GitRevisionTree.get_symlink_target.
459
        else:
460
            return None
461
0.264.10 by Jelmer Vernooij
Yield inventory entries.
462
    def _comparison_data(self, entry, path):
463
        if entry is None:
464
            return None, False, None
465
        return entry.kind, entry.executable, None
466
0.200.1568 by Jelmer Vernooij
Implement GitRevisionTree.path_content_summary.
467
    def path_content_summary(self, path):
468
        """See Tree.path_content_summary."""
469
        try:
0.335.1 by Jelmer Vernooij
Be a bit stricter about encodings.
470
            (mode, hexsha) = tree_lookup_path(self.store.__getitem__, self.tree, path.encode('utf-8'))
0.200.1568 by Jelmer Vernooij
Implement GitRevisionTree.path_content_summary.
471
        except KeyError:
472
            return ('missing', None, None, None)
473
        kind = mode_kind(mode)
474
        if kind == 'file':
475
            executable = mode_is_executable(mode)
476
            contents = self.store[hexsha].data
477
            return (kind, len(contents), executable, osutils.sha_string(contents))
478
        elif kind == 'symlink':
479
            return (kind, None, None, self.store[hexsha].data)
480
        else:
481
            return (kind, None, None, None)
482
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
483
0.252.43 by Jelmer Vernooij
Some refactoring, support proper file ids in revision deltas.
484
def tree_delta_from_git_changes(changes, mapping,
0.200.1743 by Jelmer Vernooij
Fix some revision delta filtering.
485
        (old_fileid_map, new_fileid_map), specific_files=None,
0.287.5 by Jelmer Vernooij
Fix root handling.
486
        require_versioned=False, include_root=False):
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
487
    """Create a TreeDelta from two git trees.
0.200.959 by Jelmer Vernooij
Improve docstrings.
488
489
    source and target are iterators over tuples with:
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
490
        (filename, sha, mode)
491
    """
492
    ret = delta.TreeDelta()
493
    for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
0.287.5 by Jelmer Vernooij
Fix root handling.
494
        if newpath == u'' and not include_root:
495
            continue
0.200.1743 by Jelmer Vernooij
Fix some revision delta filtering.
496
        if not (specific_files is None or
0.287.5 by Jelmer Vernooij
Fix root handling.
497
                (oldpath is not None and osutils.is_inside_or_parent_of_any(specific_files, oldpath)) or
498
                (newpath is not None and osutils.is_inside_or_parent_of_any(specific_files, newpath))):
0.200.1743 by Jelmer Vernooij
Fix some revision delta filtering.
499
            continue
0.200.1752 by Jelmer Vernooij
Don't traverse nested trees in WorkingTree.smart_add.
500
        if mapping.is_special_file(oldpath):
0.252.43 by Jelmer Vernooij
Some refactoring, support proper file ids in revision deltas.
501
            oldpath = None
0.200.1752 by Jelmer Vernooij
Don't traverse nested trees in WorkingTree.smart_add.
502
        if mapping.is_special_file(newpath):
0.252.43 by Jelmer Vernooij
Some refactoring, support proper file ids in revision deltas.
503
            newpath = None
504
        if oldpath is None and newpath is None:
505
            continue
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
506
        if oldpath is None:
0.200.1613 by Jelmer Vernooij
Handle encoding better in working tree iter changes.
507
            file_id = new_fileid_map.lookup_file_id(newpath)
508
            ret.added.append((newpath.decode('utf-8'), file_id, mode_kind(newmode)))
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
509
        elif newpath is None:
0.200.1613 by Jelmer Vernooij
Handle encoding better in working tree iter changes.
510
            file_id = old_fileid_map.lookup_file_id(oldpath)
511
            ret.removed.append((oldpath.decode('utf-8'), file_id, mode_kind(oldmode)))
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
512
        elif oldpath != newpath:
0.200.1613 by Jelmer Vernooij
Handle encoding better in working tree iter changes.
513
            file_id = old_fileid_map.lookup_file_id(oldpath)
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
514
            ret.renamed.append(
515
                (oldpath.decode('utf-8'), newpath.decode('utf-8'), file_id,
516
                mode_kind(newmode), (oldsha != newsha),
517
                (oldmode != newmode)))
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
518
        elif mode_kind(oldmode) != mode_kind(newmode):
0.200.1613 by Jelmer Vernooij
Handle encoding better in working tree iter changes.
519
            file_id = new_fileid_map.lookup_file_id(newpath)
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
520
            ret.kind_changed.append(
521
                (newpath.decode('utf-8'), file_id, mode_kind(oldmode),
522
                mode_kind(newmode)))
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
523
        elif oldsha != newsha or oldmode != newmode:
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
524
            if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
525
                continue
0.200.1613 by Jelmer Vernooij
Handle encoding better in working tree iter changes.
526
            file_id = new_fileid_map.lookup_file_id(newpath)
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
527
            ret.modified.append(
528
                (newpath.decode('utf-8'), file_id, mode_kind(newmode),
529
                (oldsha != newsha), (oldmode != newmode)))
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
530
        else:
0.200.1613 by Jelmer Vernooij
Handle encoding better in working tree iter changes.
531
            file_id = new_fileid_map.lookup_file_id(newpath)
532
            ret.unchanged.append((newpath.decode('utf-8'), file_id, mode_kind(newmode)))
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
533
    return ret
534
535
0.357.3 by Jelmer Vernooij
More remove fixes.
536
def changes_from_git_changes(changes, mapping, specific_files=None, include_unchanged=False):
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
537
    """Create a iter_changes-like generator from a git stream.
0.200.959 by Jelmer Vernooij
Improve docstrings.
538
539
    source and target are iterators over tuples with:
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
540
        (filename, sha, mode)
541
    """
542
    for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
0.200.1743 by Jelmer Vernooij
Fix some revision delta filtering.
543
        if not (specific_files is None or
0.287.5 by Jelmer Vernooij
Fix root handling.
544
                (oldpath is not None and osutils.is_inside_or_parent_of_any(specific_files, oldpath)) or
545
                (newpath is not None and osutils.is_inside_or_parent_of_any(specific_files, newpath))):
0.200.1743 by Jelmer Vernooij
Fix some revision delta filtering.
546
            continue
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
547
        path = (oldpath, newpath)
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
548
        if oldpath is not None and mapping.is_special_file(oldpath):
549
            continue
550
        if newpath is not None and mapping.is_special_file(newpath):
0.200.1328 by Jelmer Vernooij
More test fixes.
551
            continue
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
552
        if oldpath is None:
553
            fileid = mapping.generate_file_id(newpath)
554
            oldexe = None
555
            oldkind = None
556
            oldname = None
557
            oldparent = None
558
        else:
0.200.1345 by Jelmer Vernooij
paths should be unicode when yielded by iter_changes.
559
            oldpath = oldpath.decode("utf-8")
0.361.1 by Jelmer Vernooij
Don't use assert.
560
            if oldmode is None:
561
                raise ValueError
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
562
            oldexe = mode_is_executable(oldmode)
563
            oldkind = mode_kind(oldmode)
0.316.1 by Jelmer Vernooij
Fix iter_changes behaviour for trees in the tree root.
564
            if oldpath == u'':
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
565
                oldparent = None
0.316.1 by Jelmer Vernooij
Fix iter_changes behaviour for trees in the tree root.
566
                oldname = ''
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
567
            else:
0.316.1 by Jelmer Vernooij
Fix iter_changes behaviour for trees in the tree root.
568
                (oldparentpath, oldname) = osutils.split(oldpath)
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
569
                oldparent = mapping.generate_file_id(oldparentpath)
570
            fileid = mapping.generate_file_id(oldpath)
571
        if newpath is None:
572
            newexe = None
573
            newkind = None
574
            newname = None
575
            newparent = None
576
        else:
0.200.1345 by Jelmer Vernooij
paths should be unicode when yielded by iter_changes.
577
            newpath = newpath.decode("utf-8")
0.200.1576 by Jelmer Vernooij
Merge a bunch of fixes from store-roundtrip-info.
578
            if newmode is not None:
579
                newexe = mode_is_executable(newmode)
580
                newkind = mode_kind(newmode)
581
            else:
582
                newexe = False
583
                newkind = None
0.316.1 by Jelmer Vernooij
Fix iter_changes behaviour for trees in the tree root.
584
            if newpath == u'':
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
585
                newparent = None
0.316.1 by Jelmer Vernooij
Fix iter_changes behaviour for trees in the tree root.
586
                newname = u''
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
587
            else:
0.316.1 by Jelmer Vernooij
Fix iter_changes behaviour for trees in the tree root.
588
                newparentpath, newname = osutils.split(newpath)
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
589
                newparent = mapping.generate_file_id(newparentpath)
0.357.3 by Jelmer Vernooij
More remove fixes.
590
        if (not include_unchanged and
591
            oldkind == 'directory' and newkind == 'directory' and
592
            oldpath == newpath):
593
            continue
0.200.959 by Jelmer Vernooij
Improve docstrings.
594
        yield (fileid, (oldpath, newpath), (oldsha != newsha),
595
             (oldpath is not None, newpath is not None),
596
             (oldparent, newparent), (oldname, newname),
597
             (oldkind, newkind), (oldexe, newexe))
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
598
599
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
600
class InterGitTrees(_mod_tree.InterTree):
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
601
    """InterTree that works between two git trees."""
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
602
0.200.659 by Jelmer Vernooij
Prevent tests using InterGitRevisionTrees.
603
    _matching_from_tree_format = None
604
    _matching_to_tree_format = None
605
    _test_mutable_trees_to_test_trees = None
606
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
607
    @classmethod
608
    def is_compatible(cls, source, target):
0.200.959 by Jelmer Vernooij
Improve docstrings.
609
        return (isinstance(source, GitRevisionTree) and
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
610
                isinstance(target, GitRevisionTree))
611
612
    def compare(self, want_unchanged=False, specific_files=None,
613
                extra_trees=None, require_versioned=False, include_root=False,
614
                want_unversioned=False):
0.357.1 by Jelmer Vernooij
Fix some remove tests.
615
        changes = self._iter_git_changes(want_unchanged=want_unchanged,
616
                require_versioned=require_versioned,
617
                specific_files=specific_files)
0.200.1731 by Jelmer Vernooij
Add support for checking untracked changes.
618
        source_fileid_map = self.source._fileid_map
619
        target_fileid_map = self.target._fileid_map
0.200.959 by Jelmer Vernooij
Improve docstrings.
620
        return tree_delta_from_git_changes(changes, self.target.mapping,
0.252.43 by Jelmer Vernooij
Some refactoring, support proper file ids in revision deltas.
621
            (source_fileid_map, target_fileid_map),
0.287.5 by Jelmer Vernooij
Fix root handling.
622
            specific_files=specific_files, include_root=include_root)
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
623
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
624
    def iter_changes(self, include_unchanged=False, specific_files=None,
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
625
                     pb=None, extra_trees=[], require_versioned=True,
626
                     want_unversioned=False):
0.357.1 by Jelmer Vernooij
Fix some remove tests.
627
        changes = self._iter_git_changes(want_unchanged=include_unchanged,
628
                require_versioned=require_versioned,
629
                specific_files=specific_files)
0.200.959 by Jelmer Vernooij
Improve docstrings.
630
        return changes_from_git_changes(changes, self.target.mapping,
0.357.3 by Jelmer Vernooij
More remove fixes.
631
            specific_files=specific_files, include_unchanged=include_unchanged)
0.200.622 by Jelmer Vernooij
Implement InterTree.iter_changes() as well.
632
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
633
    def _iter_git_changes(self, want_unchanged=False):
634
        raise NotImplementedError(self._iter_git_changes)
635
636
637
class InterGitRevisionTrees(InterGitTrees):
638
    """InterTree that works between two git revision trees."""
639
640
    _matching_from_tree_format = None
641
    _matching_to_tree_format = None
642
    _test_mutable_trees_to_test_trees = None
643
644
    @classmethod
645
    def is_compatible(cls, source, target):
646
        return (isinstance(source, GitRevisionTree) and
647
                isinstance(target, GitRevisionTree))
648
0.357.1 by Jelmer Vernooij
Fix some remove tests.
649
    def _iter_git_changes(self, want_unchanged=False, require_versioned=False,
650
            specific_files=None):
651
        if require_versioned and specific_files:
652
            for path in specific_files:
653
                if (not self.source.is_versioned(path) and
654
                    not self.target.is_versioned(path)):
655
                    raise errors.PathsNotVersionedError(path)
656
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
657
        if self.source._repository._git.object_store != self.target._repository._git.object_store:
0.200.1780 by Jelmer Vernooij
Fix cross-object-store tree comparison.
658
            store = OverlayObjectStore([self.source._repository._git.object_store,
659
                                        self.target._repository._git.object_store])
660
        else:
661
            store = self.source._repository._git.object_store
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
662
        return self.source._repository._git.object_store.tree_changes(
663
            self.source.tree, self.target.tree, want_unchanged=want_unchanged,
0.339.1 by Jelmer Vernooij
Use change_type_same.
664
            include_trees=True, change_type_same=True)
0.287.3 by Jelmer Vernooij
Some improvements to changes iterator.
665
0.200.617 by Jelmer Vernooij
Add custom InterTree for use between git revision trees.
666
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
667
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
668
669
670
class MutableGitIndexTree(mutabletree.MutableTree):
671
672
    def __init__(self):
673
        self._lock_mode = None
674
        self._lock_count = 0
675
        self._versioned_dirs = None
676
677
    def is_versioned(self, path):
678
        with self.lock_read():
679
            path = path.rstrip('/').encode('utf-8')
680
            return (path in self.index or self._has_dir(path))
681
682
    def _has_dir(self, path):
683
        if path == "":
684
            return True
685
        if self._versioned_dirs is None:
686
            self._load_dirs()
687
        return path in self._versioned_dirs
688
689
    def _load_dirs(self):
0.361.3 by Jelmer Vernooij
Merge trunk,
690
        if self._lock_mode is None:
691
            raise errors.ObjectNotLocked(self)
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
692
        self._versioned_dirs = set()
693
        for p in self.index:
694
            self._ensure_versioned_dir(posixpath.dirname(p))
695
696
    def _ensure_versioned_dir(self, dirname):
697
        if dirname in self._versioned_dirs:
698
            return
699
        if dirname != "":
700
            self._ensure_versioned_dir(posixpath.dirname(dirname))
701
        self._versioned_dirs.add(dirname)
702
703
    def path2id(self, path):
704
        with self.lock_read():
705
            path = path.rstrip('/')
706
            if self.is_versioned(path.rstrip('/')):
707
                return self._fileid_map.lookup_file_id(path.encode("utf-8"))
708
            return None
709
710
    def has_id(self, file_id):
711
        try:
712
            self.id2path(file_id)
713
        except errors.NoSuchId:
714
            return False
715
        else:
716
            return True
717
718
    def id2path(self, file_id):
719
        if file_id is None:
720
            return ''
0.361.3 by Jelmer Vernooij
Merge trunk,
721
        if type(file_id) is not bytes:
722
            raise TypeError(file_id)
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
723
        with self.lock_read():
724
            try:
725
                path = self._fileid_map.lookup_path(file_id)
726
            except ValueError:
727
                raise errors.NoSuchId(self, file_id)
728
            path = path.decode('utf-8')
729
            if self.is_versioned(path):
730
                return path
731
            raise errors.NoSuchId(self, file_id)
732
733
    def _set_root_id(self, file_id):
734
        self._fileid_map.set_file_id("", file_id)
735
736
    def get_root_id(self):
737
        return self.path2id("")
738
739
    def _add(self, files, ids, kinds):
740
        for (path, file_id, kind) in zip(files, ids, kinds):
741
            if file_id is not None:
742
                raise workingtree.SettingFileIdUnsupported()
0.366.1 by Jelmer Vernooij
Fix normalized filename checking in add.
743
            path, can_access = osutils.normalized_filename(path)
744
            if not can_access:
745
                raise errors.InvalidNormalization(path)
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
746
            self._index_add_entry(path, kind)
747
748
    def _index_add_entry(self, path, kind, flags=0):
0.361.4 by Jelmer Vernooij
Fix assert.
749
        if not isinstance(path, basestring):
0.361.3 by Jelmer Vernooij
Merge trunk,
750
            raise TypeError(path)
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
751
        if kind == "directory":
752
            # Git indexes don't contain directories
753
            return
754
        if kind == "file":
755
            blob = Blob()
756
            try:
757
                file, stat_val = self.get_file_with_stat(path)
758
            except (errors.NoSuchFile, IOError):
759
                # TODO: Rather than come up with something here, use the old index
760
                file = BytesIO()
761
                stat_val = os.stat_result(
762
                    (stat.S_IFREG | 0644, 0, 0, 0, 0, 0, 0, 0, 0, 0))
763
            blob.set_raw_string(file.read())
764
        elif kind == "symlink":
765
            blob = Blob()
766
            try:
767
                stat_val = self._lstat(path)
768
            except (errors.NoSuchFile, OSError):
769
                # TODO: Rather than come up with something here, use the
770
                # old index
771
                stat_val = os.stat_result(
772
                    (stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
773
            blob.set_raw_string(
774
                self.get_symlink_target(path).encode("utf-8"))
775
        else:
776
            raise AssertionError("unknown kind '%s'" % kind)
777
        # Add object to the repository if it didn't exist yet
778
        if not blob.id in self.store:
779
            self.store.add_object(blob)
780
        # Add an entry to the index or update the existing entry
781
        ensure_normalized_path(path)
782
        encoded_path = path.encode("utf-8")
783
        if b'\r' in encoded_path or b'\n' in encoded_path:
784
            # TODO(jelmer): Why do we need to do this?
785
            trace.mutter('ignoring path with invalid newline in it: %r', path)
786
            return
787
        self.index[encoded_path] = index_entry_from_stat(
788
            stat_val, blob.id, flags)
789
        if self._versioned_dirs is not None:
790
            self._ensure_versioned_dir(encoded_path)
791
792
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
793
        if yield_parents:
794
            raise NotImplementedError(self.iter_entries_by_dir)
795
        with self.lock_read():
796
            if specific_file_ids is not None:
797
                specific_paths = []
798
                for file_id in specific_file_ids:
799
                    if file_id is None:
800
                        continue
801
                    try:
802
                        specific_paths.append(self.id2path(file_id))
803
                    except errors.NoSuchId:
804
                        pass
805
                if specific_paths in ([u""], []):
806
                    specific_paths = None
807
                else:
808
                    specific_paths = set(specific_paths)
809
            else:
810
                specific_paths = None
811
            root_ie = self._get_dir_ie(u"", None)
812
            ret = {}
813
            if specific_paths is None:
814
                ret[(None, u"")] = root_ie
815
            dir_ids = {u"": root_ie.file_id}
816
            for path, value in self.index.iteritems():
817
                if self.mapping.is_special_file(path):
818
                    continue
819
                path = path.decode("utf-8")
820
                if specific_paths is not None and not path in specific_paths:
821
                    continue
822
                (parent, name) = posixpath.split(path)
823
                try:
824
                    file_ie = self._get_file_ie(name, path, value, None)
825
                except errors.NoSuchFile:
826
                    continue
827
                if yield_parents or specific_file_ids is None:
828
                    for (dir_path, dir_ie) in self._add_missing_parent_ids(parent,
829
                            dir_ids):
830
                        ret[(posixpath.dirname(dir_path), dir_path)] = dir_ie
831
                file_ie.parent_id = self.path2id(parent)
832
                ret[(posixpath.dirname(path), path)] = file_ie
833
            return ((path, ie) for ((_, path), ie) in sorted(ret.items()))
834
835
836
    def _get_dir_ie(self, path, parent_id):
837
        file_id = self.path2id(path)
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
838
        return GitTreeDirectory(file_id,
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
839
            posixpath.basename(path).strip("/"), parent_id)
840
841
    def _get_file_ie(self, name, path, value, parent_id):
0.361.3 by Jelmer Vernooij
Merge trunk,
842
        if type(name) is not unicode:
843
            raise TypeError(name)
844
        if type(path) is not unicode:
845
            raise TypeError(path)
846
        if not isinstance(value, tuple) or len(value) != 10:
847
            raise TypeError(value)
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
848
        (ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value
849
        file_id = self.path2id(path)
850
        if type(file_id) != str:
851
            raise AssertionError
852
        kind = mode_kind(mode)
0.365.1 by Jelmer Vernooij
Add custom GitTree{Directory,File,Symlink}.
853
        ie = entry_factory[kind](file_id, name, parent_id)
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
854
        if kind == 'symlink':
855
            ie.symlink_target = self.get_symlink_target(path, file_id)
856
        else:
857
            try:
858
                data = self.get_file_text(path, file_id)
859
            except errors.NoSuchFile:
860
                data = None
861
            except IOError as e:
862
                if e.errno != errno.ENOENT:
863
                    raise
864
                data = None
865
            if data is None:
866
                data = self.branch.repository._git.object_store[sha].data
867
            ie.text_sha1 = osutils.sha_string(data)
868
            ie.text_size = len(data)
869
            ie.executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
870
        ie.revision = None
871
        return ie
872
873
    def _add_missing_parent_ids(self, path, dir_ids):
874
        if path in dir_ids:
875
            return []
876
        parent = posixpath.dirname(path).strip("/")
877
        ret = self._add_missing_parent_ids(parent, dir_ids)
878
        parent_id = dir_ids[parent]
879
        ie = self._get_dir_ie(path, parent_id)
880
        dir_ids[path] = ie.file_id
881
        ret.append((path, ie))
882
        return ret
883
884
    def _comparison_data(self, entry, path):
885
        if entry is None:
886
            return None, False, None
887
        return entry.kind, entry.executable, None
888
889
    def _unversion_path(self, path):
0.361.3 by Jelmer Vernooij
Merge trunk,
890
        if self._lock_mode is None:
891
            raise errors.ObjectNotLocked(self)
0.360.1 by Jelmer Vernooij
Implement GitMemoryTree.
892
        encoded_path = path.encode("utf-8")
893
        count = 0
894
        try:
895
            del self.index[encoded_path]
896
        except KeyError:
897
            # A directory, perhaps?
898
            for p in list(self.index):
899
                if p.startswith(encoded_path+b"/"):
900
                    count += 1
901
                    del self.index[p]
902
        else:
903
            count = 1
904
        self._versioned_dirs = None
905
        return count
906
907
    def unversion(self, paths, file_ids=None):
908
        with self.lock_tree_write():
909
            for path in paths:
910
                if self._unversion_path(path) == 0:
911
                    raise errors.NoSuchFile(path)
912
            self._versioned_dirs = None
913
            self.flush()
914
915
    def flush(self):
916
        pass
917
918
    def update_basis_by_delta(self, revid, delta):
919
        # TODO(jelmer): This shouldn't be called, it's inventory specific.
920
        for (old_path, new_path, file_id, ie) in delta:
921
            if old_path is not None and old_path.encode('utf-8') in self.index:
922
                del self.index[old_path.encode('utf-8')]
923
                self._versioned_dirs = None
924
            if new_path is not None and ie.kind != 'directory':
925
                self._index_add_entry(new_path, ie.kind)
926
        self.flush()
927
        self._set_merges_from_parent_ids([])
0.360.4 by Jelmer Vernooij
Implement MemoryTree.rename_one, MemoryTree.mkdir.
928
929
    def move(self, from_paths, to_dir=None, after=None):
930
        rename_tuples = []
931
        with self.lock_tree_write():
932
            to_abs = self.abspath(to_dir)
933
            if not os.path.isdir(to_abs):
934
                raise errors.BzrMoveFailedError('', to_dir,
935
                    errors.NotADirectory(to_abs))
936
937
            for from_rel in from_paths:
938
                from_tail = os.path.split(from_rel)[-1]
939
                to_rel = os.path.join(to_dir, from_tail)
940
                self.rename_one(from_rel, to_rel, after=after)
941
                rename_tuples.append((from_rel, to_rel))
942
            self.flush()
943
            return rename_tuples
944
945
    def rename_one(self, from_rel, to_rel, after=None):
946
        from_path = from_rel.encode("utf-8")
947
        to_rel, can_access = osutils.normalized_filename(to_rel)
948
        if not can_access:
949
            raise errors.InvalidNormalization(to_rel)
950
        to_path = to_rel.encode("utf-8")
951
        with self.lock_tree_write():
952
            if not after:
953
                # Perhaps it's already moved?
954
                after = (
955
                    not self.has_filename(from_rel) and
956
                    self.has_filename(to_rel) and
957
                    not self.is_versioned(to_rel))
958
            if after:
959
                if not self.has_filename(to_rel):
960
                    raise errors.BzrMoveFailedError(from_rel, to_rel,
961
                        errors.NoSuchFile(to_rel))
962
                if self.basis_tree().is_versioned(to_rel):
963
                    raise errors.BzrMoveFailedError(from_rel, to_rel,
964
                        errors.AlreadyVersionedError(to_rel))
965
966
                kind = self.kind(to_rel)
967
            else:
968
                try:
969
                    to_kind = self.kind(to_rel)
970
                except errors.NoSuchFile:
971
                    exc_type = errors.BzrRenameFailedError
972
                    to_kind = None
973
                else:
974
                    exc_type = errors.BzrMoveFailedError
975
                if self.is_versioned(to_rel):
976
                    raise exc_type(from_rel, to_rel,
977
                        errors.AlreadyVersionedError(to_rel))
978
                if not self.has_filename(from_rel):
979
                    raise errors.BzrMoveFailedError(from_rel, to_rel,
980
                        errors.NoSuchFile(from_rel))
981
                if not self.is_versioned(from_rel):
982
                    raise exc_type(from_rel, to_rel,
983
                        errors.NotVersionedError(from_rel))
984
                if self.has_filename(to_rel):
985
                    raise errors.RenameFailedFilesExist(
986
                        from_rel, to_rel, errors.FileExists(to_rel))
987
988
                kind = self.kind(from_rel)
989
990
            if not after and not from_path in self.index and kind != 'directory':
991
                # It's not a file
992
                raise errors.BzrMoveFailedError(from_rel, to_rel,
993
                    errors.NotVersionedError(path=from_rel))
994
995
            if not after:
996
                try:
997
                    self._rename_one(from_rel, to_rel)
998
                except OSError as e:
999
                    if e.errno == errno.ENOENT:
1000
                        raise errors.BzrMoveFailedError(from_rel, to_rel,
1001
                            errors.NoSuchFile(to_rel))
1002
                    raise
1003
            if kind != 'directory':
1004
                try:
1005
                    del self.index[from_path]
1006
                except KeyError:
1007
                    pass
1008
                self._index_add_entry(to_rel, kind)
1009
            else:
1010
                todo = [p for p in self.index if p.startswith(from_path+'/')]
1011
                for p in todo:
1012
                    self.index[posixpath.join(to_path, posixpath.relpath(p, from_path))] = self.index[p]
1013
                    del self.index[p]
1014
1015
            self._versioned_dirs = None
1016
            self.flush()