/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to workingtree.py

  • Committer: John Arbash Meinel
  • Date: 2010-01-12 22:51:31 UTC
  • mto: This revision was merged to the branch mainline in revision 4955.
  • Revision ID: john@arbash-meinel.com-20100112225131-he8h411p6aeeb947
Delay grabbing an output stream until we actually go to show a diff.

This makes the test suite happy, but it also seems to be reasonable.
If we aren't going to write anything, we don't need to hold an
output stream open.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2008-2011 Jelmer Vernooij <jelmer@samba.org>
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
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
"""An adapter between a Git index and a Bazaar Working Tree"""
19
 
 
20
 
 
21
 
from cStringIO import (
22
 
    StringIO,
23
 
    )
24
 
from collections import defaultdict
25
 
import errno
26
 
from dulwich.index import (
27
 
    Index,
28
 
    )
29
 
from dulwich.object_store import (
30
 
    tree_lookup_path,
31
 
    )
32
 
from dulwich.objects import (
33
 
    Blob,
34
 
    ZERO_SHA,
35
 
    )
36
 
import os
37
 
import posix
38
 
import posixpath
39
 
import stat
40
 
import sys
41
 
 
42
 
from bzrlib import (
43
 
    errors,
44
 
    conflicts as _mod_conflicts,
45
 
    ignores,
46
 
    inventory,
47
 
    lockable_files,
48
 
    lockdir,
49
 
    osutils,
50
 
    trace,
51
 
    transport,
52
 
    tree,
53
 
    workingtree,
54
 
    )
55
 
from bzrlib.decorators import (
56
 
    needs_read_lock,
57
 
    )
58
 
from bzrlib.mutabletree import needs_tree_write_lock
59
 
 
60
 
 
61
 
from bzrlib.plugins.git.dir import (
62
 
    LocalGitDir,
63
 
    )
64
 
from bzrlib.plugins.git.tree import (
65
 
    changes_from_git_changes,
66
 
    tree_delta_from_git_changes,
67
 
    )
68
 
from bzrlib.plugins.git.mapping import (
69
 
    GitFileIdMap,
70
 
    mode_kind,
71
 
    )
72
 
 
73
 
IGNORE_FILENAME = ".gitignore"
74
 
 
75
 
 
76
 
class GitWorkingTree(workingtree.WorkingTree):
77
 
    """A Git working tree."""
78
 
 
79
 
    def __init__(self, bzrdir, repo, branch, index):
80
 
        self.basedir = bzrdir.root_transport.local_abspath('.')
81
 
        self.bzrdir = bzrdir
82
 
        self.repository = repo
83
 
        self.store = self.repository._git.object_store
84
 
        self.mapping = self.repository.get_mapping()
85
 
        self._branch = branch
86
 
        self._transport = bzrdir.transport
87
 
 
88
 
        self.controldir = self.bzrdir.transport.local_abspath('bzr')
89
 
 
90
 
        try:
91
 
            os.makedirs(self.controldir)
92
 
            os.makedirs(os.path.join(self.controldir, 'lock'))
93
 
        except OSError:
94
 
            pass
95
 
 
96
 
        self._control_files = lockable_files.LockableFiles(
97
 
            transport.get_transport(self.controldir), 'lock', lockdir.LockDir)
98
 
        self._format = GitWorkingTreeFormat()
99
 
        self.index = index
100
 
        self._versioned_dirs = None
101
 
        self.views = self._make_views()
102
 
        self._rules_searcher = None
103
 
        self._detect_case_handling()
104
 
        self._reset_data()
105
 
        self._fileid_map = self._basis_fileid_map.copy()
106
 
 
107
 
    def _detect_case_handling(self):
108
 
        try:
109
 
            self._transport.stat(".git/cOnFiG")
110
 
        except errors.NoSuchFile:
111
 
            self.case_sensitive = True
112
 
        else:
113
 
            self.case_sensitive = False
114
 
 
115
 
    def merge_modified(self):
116
 
        return {}
117
 
 
118
 
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
119
 
        self.set_parent_ids([p for p, t in parents_list])
120
 
 
121
 
    def _index_add_entry(self, path, file_id, kind):
122
 
        assert isinstance(path, basestring)
123
 
        assert type(file_id) == str or file_id is None
124
 
        if kind == "directory":
125
 
            # Git indexes don't contain directories
126
 
            return
127
 
        if kind == "file":
128
 
            blob = Blob()
129
 
            try:
130
 
                file, stat_val = self.get_file_with_stat(file_id, path)
131
 
            except (errors.NoSuchFile, IOError):
132
 
                # TODO: Rather than come up with something here, use the old index
133
 
                file = StringIO()
134
 
                from posix import stat_result
135
 
                stat_val = stat_result((stat.S_IFREG | 0644, 0, 0, 0, 0, 0, 0, 0, 0, 0))
136
 
            blob.set_raw_string(file.read())
137
 
        elif kind == "symlink":
138
 
            blob = Blob()
139
 
            try:
140
 
                stat_val = os.lstat(self.abspath(path))
141
 
            except (errors.NoSuchFile, OSError):
142
 
                # TODO: Rather than come up with something here, use the 
143
 
                # old index
144
 
                from posix import stat_result
145
 
                stat_val = stat_result((stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
146
 
            blob.set_raw_string(
147
 
                self.get_symlink_target(file_id, path).encode("utf-8"))
148
 
        else:
149
 
            raise AssertionError("unknown kind '%s'" % kind)
150
 
        # Add object to the repository if it didn't exist yet
151
 
        if not blob.id in self.store:
152
 
            self.store.add_object(blob)
153
 
        # Add an entry to the index or update the existing entry
154
 
        flags = 0 # FIXME
155
 
        encoded_path = path.encode("utf-8")
156
 
        self.index[encoded_path] = (stat_val.st_ctime,
157
 
                stat_val.st_mtime, stat_val.st_dev, stat_val.st_ino,
158
 
                stat_val.st_mode, stat_val.st_uid, stat_val.st_gid,
159
 
                stat_val.st_size, blob.id, flags)
160
 
        if self._versioned_dirs is not None:
161
 
            self._ensure_versioned_dir(encoded_path)
162
 
 
163
 
    def _ensure_versioned_dir(self, dirname):
164
 
        if dirname in self._versioned_dirs:
165
 
            return
166
 
        if dirname != "":
167
 
            self._ensure_versioned_dir(posixpath.dirname(dirname))
168
 
        self._versioned_dirs.add(dirname)
169
 
 
170
 
    def _load_dirs(self):
171
 
        self._versioned_dirs = set()
172
 
        for p in self.index:
173
 
            self._ensure_versioned_dir(posixpath.dirname(p))
174
 
 
175
 
    def _unversion_path(self, path):
176
 
        encoded_path = path.encode("utf-8")
177
 
        try:
178
 
            del self.index[encoded_path]
179
 
        except KeyError:
180
 
            # A directory, perhaps?
181
 
            for p in list(self.index):
182
 
                if p.startswith(encoded_path+"/"):
183
 
                    del self.index[p]
184
 
        # FIXME: remove empty directories
185
 
 
186
 
    @needs_tree_write_lock
187
 
    def unversion(self, file_ids):
188
 
        for file_id in file_ids:
189
 
            path = self.id2path(file_id)
190
 
            self._unversion_path(path)
191
 
        self.flush()
192
 
 
193
 
    def check_state(self):
194
 
        """Check that the working state is/isn't valid."""
195
 
        pass
196
 
 
197
 
    @needs_tree_write_lock
198
 
    def remove(self, files, verbose=False, to_file=None, keep_files=True,
199
 
        force=False):
200
 
        """Remove nominated files from the working tree metadata.
201
 
 
202
 
        :param files: File paths relative to the basedir.
203
 
        :param keep_files: If true, the files will also be kept.
204
 
        :param force: Delete files and directories, even if they are changed
205
 
            and even if the directories are not empty.
206
 
        """
207
 
        all_files = set() # specified and nested files 
208
 
 
209
 
        if isinstance(files, basestring):
210
 
            files = [files]
211
 
 
212
 
        if to_file is None:
213
 
            to_file = sys.stdout
214
 
 
215
 
        files = list(all_files)
216
 
 
217
 
        if len(files) == 0:
218
 
            return # nothing to do
219
 
 
220
 
        # Sort needed to first handle directory content before the directory
221
 
        files.sort(reverse=True)
222
 
 
223
 
        def backup(file_to_backup):
224
 
            abs_path = self.abspath(file_to_backup)
225
 
            backup_name = self.bzrdir._available_backup_name(file_to_backup)
226
 
            osutils.rename(abs_path, self.abspath(backup_name))
227
 
            return "removed %s (but kept a copy: %s)" % (
228
 
                file_to_backup, backup_name)
229
 
 
230
 
        for f in files:
231
 
            fid = self.path2id(f)
232
 
            if not fid:
233
 
                message = "%s is not versioned." % (f,)
234
 
            else:
235
 
                abs_path = self.abspath(f)
236
 
                if verbose:
237
 
                    # having removed it, it must be either ignored or unknown
238
 
                    if self.is_ignored(f):
239
 
                        new_status = 'I'
240
 
                    else:
241
 
                        new_status = '?'
242
 
                    # XXX: Really should be a more abstract reporter interface
243
 
                    kind_ch = osutils.kind_marker(self.kind(fid))
244
 
                    to_file.write(new_status + '       ' + f + kind_ch + '\n')
245
 
                # Unversion file
246
 
                # FIXME: _unversion_path() is O(size-of-index) for directories
247
 
                self._unversion_path(f)
248
 
                message = "removed %s" % (f,)
249
 
                if osutils.lexists(abs_path):
250
 
                    if (osutils.isdir(abs_path) and
251
 
                        len(os.listdir(abs_path)) > 0):
252
 
                        if force:
253
 
                            osutils.rmtree(abs_path)
254
 
                            message = "deleted %s" % (f,)
255
 
                        else:
256
 
                            message = backup(f)
257
 
                    else:
258
 
                        if not keep_files:
259
 
                            osutils.delete_any(abs_path)
260
 
                            message = "deleted %s" % (f,)
261
 
 
262
 
            # print only one message (if any) per file.
263
 
            if message is not None:
264
 
                trace.note(message)
265
 
        self.flush()
266
 
 
267
 
    def _add(self, files, ids, kinds):
268
 
        for (path, file_id, kind) in zip(files, ids, kinds):
269
 
            if file_id is not None:
270
 
                self._fileid_map.set_file_id(path.encode("utf-8"), file_id)
271
 
            else:
272
 
                file_id = self._fileid_map.lookup_file_id(path.encode("utf-8"))
273
 
            self._index_add_entry(path, file_id, kind)
274
 
 
275
 
    @needs_tree_write_lock
276
 
    def smart_add(self, file_list, recurse=True, action=None, save=True):
277
 
        added = []
278
 
        ignored = {}
279
 
        user_dirs = []
280
 
        for filepath in osutils.canonical_relpaths(self.basedir, file_list):
281
 
            abspath = self.abspath(filepath)
282
 
            kind = osutils.file_kind(abspath)
283
 
            if action is not None:
284
 
                file_id = action(self, None, filepath, kind)
285
 
            else:
286
 
                file_id = None
287
 
            if kind in ("file", "symlink"):
288
 
                if save:
289
 
                    self._index_add_entry(filepath, file_id, kind)
290
 
                added.append(filepath)
291
 
            elif kind == "directory":
292
 
                if recurse:
293
 
                    user_dirs.append(filepath)
294
 
            else:
295
 
                raise errors.BadFileKindError(filename=abspath, kind=kind)
296
 
        for user_dir in user_dirs:
297
 
            abs_user_dir = self.abspath(user_dir)
298
 
            for name in os.listdir(abs_user_dir):
299
 
                subp = os.path.join(user_dir, name)
300
 
                if self.is_control_filename(subp) or self.mapping.is_special_file(subp):
301
 
                    continue
302
 
                ignore_glob = self.is_ignored(subp)
303
 
                if ignore_glob is not None:
304
 
                    ignored.setdefault(ignore_glob, []).append(subp)
305
 
                    continue
306
 
                abspath = self.abspath(subp)
307
 
                kind = osutils.file_kind(abspath)
308
 
                if kind == "directory":
309
 
                    user_dirs.append(subp)
310
 
                else:
311
 
                    if action is not None:
312
 
                        file_id = action()
313
 
                    else:
314
 
                        file_id = None
315
 
                    if save:
316
 
                        self._index_add_entry(subp, file_id, kind)
317
 
        if added and save:
318
 
            self.flush()
319
 
        return added, ignored
320
 
 
321
 
    def _set_root_id(self, file_id):
322
 
        self._fileid_map.set_file_id("", file_id)
323
 
 
324
 
    @needs_tree_write_lock
325
 
    def move(self, from_paths, to_dir=None, after=False):
326
 
        rename_tuples = []
327
 
        to_abs = self.abspath(to_dir)
328
 
        if not os.path.isdir(to_abs):
329
 
            raise errors.BzrMoveFailedError('', to_dir,
330
 
                errors.NotADirectory(to_abs))
331
 
 
332
 
        for from_rel in from_paths:
333
 
            from_tail = os.path.split(from_rel)[-1]
334
 
            to_rel = os.path.join(to_dir, from_tail)
335
 
            self.rename_one(from_rel, to_rel, after=after)
336
 
            rename_tuples.append((from_rel, to_rel))
337
 
        self.flush()
338
 
        return rename_tuples
339
 
 
340
 
    @needs_tree_write_lock
341
 
    def rename_one(self, from_rel, to_rel, after=False):
342
 
        if not after:
343
 
            os.rename(self.abspath(from_rel), self.abspath(to_rel))
344
 
        from_path = from_rel.encode("utf-8")
345
 
        to_path = to_rel.encode("utf-8")
346
 
        if not self.has_filename(to_rel):
347
 
            raise errors.BzrMoveFailedError(from_rel, to_rel,
348
 
                errors.NoSuchFile(to_rel))
349
 
        if not from_path in self.index:
350
 
            raise errors.BzrMoveFailedError(from_rel, to_rel,
351
 
                errors.NotVersionedError(path=from_rel))
352
 
        self.index[to_path] = self.index[from_path]
353
 
        del self.index[from_path]
354
 
        self.flush()
355
 
 
356
 
    def get_root_id(self):
357
 
        return self.path2id("")
358
 
 
359
 
    def _has_dir(self, path):
360
 
        if self._versioned_dirs is None:
361
 
            self._load_dirs()
362
 
        return path in self._versioned_dirs
363
 
 
364
 
    @needs_read_lock
365
 
    def path2id(self, path):
366
 
        encoded_path = path.encode("utf-8")
367
 
        if self._is_versioned(encoded_path):
368
 
            return self._fileid_map.lookup_file_id(encoded_path)
369
 
        return None
370
 
 
371
 
    def _iter_files_recursive(self, from_dir=None):
372
 
        if from_dir is None:
373
 
            from_dir = ""
374
 
        for (dirpath, dirnames, filenames) in os.walk(self.abspath(from_dir)):
375
 
            dir_relpath = dirpath[len(self.basedir):].strip("/")
376
 
            if self.bzrdir.is_control_filename(dir_relpath):
377
 
                continue
378
 
            for filename in filenames:
379
 
                if not self.mapping.is_special_file(filename):
380
 
                    yield os.path.join(dir_relpath, filename)
381
 
 
382
 
    def extras(self):
383
 
        """Yield all unversioned files in this WorkingTree.
384
 
        """
385
 
        return set(self._iter_files_recursive()) - set(self.index)
386
 
 
387
 
    def unlock(self):
388
 
        # non-implementation specific cleanup
389
 
        self._cleanup()
390
 
 
391
 
        # reverse order of locking.
392
 
        try:
393
 
            return self._control_files.unlock()
394
 
        finally:
395
 
            self.branch.unlock()
396
 
 
397
 
    def flush(self):
398
 
        # TODO: Maybe this should only write on dirty ?
399
 
        if self._control_files._lock_mode != 'w':
400
 
            raise errors.NotWriteLocked(self)
401
 
        self.index.write()
402
 
 
403
 
    def __iter__(self):
404
 
        for path in self.index:
405
 
            yield self.path2id(path)
406
 
        self._load_dirs()
407
 
        for path in self._versioned_dirs:
408
 
            yield self.path2id(path)
409
 
 
410
 
    def has_or_had_id(self, file_id):
411
 
        if self.has_id(file_id):
412
 
            return True
413
 
        if self.had_id(file_id):
414
 
            return True
415
 
        return False
416
 
 
417
 
    def had_id(self, file_id):
418
 
        path = self._basis_fileid_map.lookup_file_id(file_id)
419
 
        try:
420
 
            head = self.repository._git.head()
421
 
        except KeyError:
422
 
            # Assume no if basis is not accessible
423
 
            return False
424
 
        if head == ZERO_SHA:
425
 
            return False
426
 
        root_tree = self.store[head].tree
427
 
        try:
428
 
            tree_lookup_path(self.store.__getitem__, root_tree, path)
429
 
        except KeyError:
430
 
            return False
431
 
        else:
432
 
            return True
433
 
 
434
 
    def has_id(self, file_id):
435
 
        try:
436
 
            self.id2path(file_id)
437
 
        except errors.NoSuchId:
438
 
            return False
439
 
        else:
440
 
            return True
441
 
 
442
 
    def id2path(self, file_id):
443
 
        if type(file_id) != str:
444
 
            raise AssertionError
445
 
        path = self._fileid_map.lookup_path(file_id)
446
 
        # FIXME: What about directories?
447
 
        if self._is_versioned(path):
448
 
            return path.decode("utf-8")
449
 
        raise errors.NoSuchId(self, file_id)
450
 
 
451
 
    def get_file_mtime(self, file_id, path=None):
452
 
        """See Tree.get_file_mtime."""
453
 
        if not path:
454
 
            path = self.id2path(file_id)
455
 
        return os.lstat(self.abspath(path)).st_mtime
456
 
 
457
 
    def get_ignore_list(self):
458
 
        ignoreset = getattr(self, '_ignoreset', None)
459
 
        if ignoreset is not None:
460
 
            return ignoreset
461
 
 
462
 
        ignore_globs = set()
463
 
        ignore_globs.update(ignores.get_runtime_ignores())
464
 
        ignore_globs.update(ignores.get_user_ignores())
465
 
        if self.has_filename(IGNORE_FILENAME):
466
 
            f = self.get_file_byname(IGNORE_FILENAME)
467
 
            try:
468
 
                # FIXME: Parse git file format, rather than assuming it's
469
 
                # the same as for bzr's native formats.
470
 
                ignore_globs.update(ignores.parse_ignore_file(f))
471
 
            finally:
472
 
                f.close()
473
 
        self._ignoreset = ignore_globs
474
 
        return ignore_globs
475
 
 
476
 
    def set_last_revision(self, revid):
477
 
        self._change_last_revision(revid)
478
 
 
479
 
    def _reset_data(self):
480
 
        try:
481
 
            head = self.repository._git.head()
482
 
        except KeyError, name:
483
 
            raise errors.NotBranchError("branch %s at %s" % (name,
484
 
                self.repository.base))
485
 
        if head == ZERO_SHA:
486
 
            self._basis_fileid_map = GitFileIdMap({}, self.mapping)
487
 
        else:
488
 
            self._basis_fileid_map = self.mapping.get_fileid_map(
489
 
                self.store.__getitem__, self.store[head].tree)
490
 
 
491
 
    @needs_read_lock
492
 
    def get_file_verifier(self, file_id, path=None, stat_value=None):
493
 
        if path is None:
494
 
            path = self.id2path(file_id)
495
 
        return ("GIT", self.index[path][-2])
496
 
 
497
 
    @needs_read_lock
498
 
    def get_file_sha1(self, file_id, path=None, stat_value=None):
499
 
        if not path:
500
 
            path = self.id2path(file_id)
501
 
        abspath = self.abspath(path).encode(osutils._fs_enc)
502
 
        try:
503
 
            return osutils.sha_file_by_name(abspath)
504
 
        except OSError, (num, msg):
505
 
            if num in (errno.EISDIR, errno.ENOENT):
506
 
                return None
507
 
            raise
508
 
 
509
 
    def revision_tree(self, revid):
510
 
        return self.repository.revision_tree(revid)
511
 
 
512
 
    def _is_versioned(self, path):
513
 
        return (path in self.index or self._has_dir(path))
514
 
 
515
 
    def filter_unversioned_files(self, files):
516
 
        return set([p for p in files if not self._is_versioned(p.encode("utf-8"))])
517
 
 
518
 
    def _get_dir_ie(self, path, parent_id):
519
 
        file_id = self.path2id(path)
520
 
        return inventory.InventoryDirectory(file_id,
521
 
            posixpath.basename(path).strip("/"), parent_id)
522
 
 
523
 
    def _add_missing_parent_ids(self, path, dir_ids):
524
 
        if path in dir_ids:
525
 
            return []
526
 
        parent = posixpath.dirname(path).strip("/")
527
 
        ret = self._add_missing_parent_ids(parent, dir_ids)
528
 
        parent_id = dir_ids[parent]
529
 
        ie = self._get_dir_ie(path, parent_id)
530
 
        dir_ids[path] = ie.file_id
531
 
        ret.append((path, ie))
532
 
        return ret
533
 
 
534
 
    def _get_file_ie(self, name, path, value, parent_id):
535
 
        assert isinstance(name, unicode)
536
 
        assert isinstance(path, unicode)
537
 
        assert isinstance(value, tuple) and len(value) == 10
538
 
        (ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value
539
 
        file_id = self.path2id(path)
540
 
        if type(file_id) != str:
541
 
            raise AssertionError
542
 
        kind = mode_kind(mode)
543
 
        ie = inventory.entry_factory[kind](file_id, name, parent_id)
544
 
        if kind == 'symlink':
545
 
            ie.symlink_target = self.get_symlink_target(file_id)
546
 
        else:
547
 
            data = self.get_file_text(file_id, path)
548
 
            ie.text_sha1 = osutils.sha_string(data)
549
 
            ie.text_size = len(data)
550
 
            ie.executable = self.is_executable(file_id, path)
551
 
        ie.revision = None
552
 
        return ie
553
 
 
554
 
    def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
555
 
        mode = stat_result.st_mode
556
 
        return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
557
 
 
558
 
    def stored_kind(self, file_id, path=None):
559
 
        if path is None:
560
 
            path = self.id2path(file_id)
561
 
        try:
562
 
            return mode_kind(self.index[path.encode("utf-8")][4])
563
 
        except KeyError:
564
 
            # Maybe it's a directory?
565
 
            if self._has_dir(path):
566
 
                return "directory"
567
 
            raise errors.NoSuchId(self, file_id)
568
 
 
569
 
    if not osutils.supports_executable():
570
 
        def is_executable(self, file_id, path=None):
571
 
            basis_tree = self.basis_tree()
572
 
            if file_id in basis_tree:
573
 
                return basis_tree.is_executable(file_id)
574
 
            # Default to not executable
575
 
            return False
576
 
    else:
577
 
        def is_executable(self, file_id, path=None):
578
 
            if not path:
579
 
                path = self.id2path(file_id)
580
 
            mode = os.lstat(self.abspath(path)).st_mode
581
 
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
582
 
 
583
 
        _is_executable_from_path_and_stat = \
584
 
            _is_executable_from_path_and_stat_from_stat
585
 
 
586
 
    def list_files(self, include_root=False, from_dir=None, recursive=True):
587
 
        # FIXME: Yield non-versioned files
588
 
        if from_dir is None:
589
 
            from_dir = ""
590
 
        dir_ids = {}
591
 
        fk_entries = {'directory': workingtree.TreeDirectory, 'file': workingtree.TreeFile, 'symlink': workingtree.TreeLink}
592
 
        root_ie = self._get_dir_ie(u"", None)
593
 
        if include_root and not from_dir:
594
 
            yield "", "V", root_ie.kind, root_ie.file_id, root_ie
595
 
        dir_ids[u""] = root_ie.file_id
596
 
        if recursive:
597
 
            path_iterator = self._iter_files_recursive(from_dir)
598
 
        else:
599
 
            if from_dir is None:
600
 
                start = self.basedir
601
 
            else:
602
 
                start = os.path.join(self.basedir, from_dir)
603
 
            path_iterator = sorted([os.path.join(from_dir, name) for name in
604
 
                os.listdir(start) if not self.bzrdir.is_control_filename(name)
605
 
                and not self.mapping.is_special_file(name)])
606
 
        for path in path_iterator:
607
 
            try:
608
 
                value = self.index[path]
609
 
            except KeyError:
610
 
                value = None
611
 
            path = path.decode("utf-8")
612
 
            parent, name = posixpath.split(path)
613
 
            for dir_path, dir_ie in self._add_missing_parent_ids(parent, dir_ids):
614
 
                yield dir_path, "V", dir_ie.kind, dir_ie.file_id, dir_ie
615
 
            if value is not None:
616
 
                ie = self._get_file_ie(name, path, value, dir_ids[parent])
617
 
                yield path, "V", ie.kind, ie.file_id, ie
618
 
            else:
619
 
                kind = osutils.file_kind(self.abspath(path))
620
 
                ie = fk_entries[kind]()
621
 
                yield path, "?", kind, None, ie
622
 
 
623
 
    def all_file_ids(self):
624
 
        ids = {u"": self.path2id("")}
625
 
        for path in self.index:
626
 
            if self.mapping.is_special_file(path):
627
 
                continue
628
 
            path = path.decode("utf-8")
629
 
            parent = posixpath.dirname(path).strip("/")
630
 
            for e in self._add_missing_parent_ids(parent, ids):
631
 
                pass
632
 
            ids[path] = self.path2id(path)
633
 
        return set(ids.values())
634
 
 
635
 
    def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
636
 
        # FIXME: Is return order correct?
637
 
        if yield_parents:
638
 
            raise NotImplementedError(self.iter_entries_by_dir)
639
 
        if specific_file_ids is not None:
640
 
            specific_paths = [self.id2path(file_id) for file_id in specific_file_ids]
641
 
            if specific_paths in ([u""], []):
642
 
                specific_paths = None
643
 
            else:
644
 
                specific_paths = set(specific_paths)
645
 
        else:
646
 
            specific_paths = None
647
 
        root_ie = self._get_dir_ie(u"", None)
648
 
        if specific_paths is None:
649
 
            yield u"", root_ie
650
 
        dir_ids = {u"": root_ie.file_id}
651
 
        for path, value in self.index.iteritems():
652
 
            if self.mapping.is_special_file(path):
653
 
                continue
654
 
            path = path.decode("utf-8")
655
 
            if specific_paths is not None and not path in specific_paths:
656
 
                continue
657
 
            (parent, name) = posixpath.split(path)
658
 
            try:
659
 
                file_ie = self._get_file_ie(name, path, value, None)
660
 
            except IOError:
661
 
                continue
662
 
            for (dir_path, dir_ie) in self._add_missing_parent_ids(parent,
663
 
                    dir_ids):
664
 
                yield dir_path, dir_ie
665
 
            file_ie.parent_id = self.path2id(parent)
666
 
            yield path, file_ie
667
 
 
668
 
    @needs_read_lock
669
 
    def conflicts(self):
670
 
        # FIXME:
671
 
        return _mod_conflicts.ConflictList()
672
 
 
673
 
    def update_basis_by_delta(self, new_revid, delta):
674
 
        # The index just contains content, which won't have changed.
675
 
        self._reset_data()
676
 
 
677
 
    def get_canonical_inventory_path(self, path):
678
 
        for p in self.index:
679
 
            if p.lower() == path.lower():
680
 
                return p
681
 
        else:
682
 
            return path
683
 
 
684
 
    def _walkdirs(self, prefix=""):
685
 
        if prefix != "":
686
 
            prefix += "/"
687
 
        per_dir = defaultdict(list)
688
 
        for path, value in self.index.iteritems():
689
 
            if self.mapping.is_special_file(path):
690
 
                continue
691
 
            if not path.startswith(prefix):
692
 
                continue
693
 
            (dirname, child_name) = posixpath.split(path)
694
 
            dirname = dirname.decode("utf-8")
695
 
            dir_file_id = self.path2id(dirname)
696
 
            assert isinstance(value, tuple) and len(value) == 10
697
 
            (ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value
698
 
            stat_result = posix.stat_result((mode, ino,
699
 
                    dev, 1, uid, gid, size,
700
 
                    0, mtime, ctime))
701
 
            per_dir[(dirname, dir_file_id)].append(
702
 
                (path.decode("utf-8"), child_name.decode("utf-8"),
703
 
                mode_kind(mode), stat_result,
704
 
                self.path2id(path.decode("utf-8")),
705
 
                mode_kind(mode)))
706
 
        return per_dir.iteritems()
707
 
 
708
 
 
709
 
class GitWorkingTreeFormat(workingtree.WorkingTreeFormat):
710
 
 
711
 
    _tree_class = GitWorkingTree
712
 
 
713
 
    supports_versioned_directories = False
714
 
 
715
 
    @property
716
 
    def _matchingbzrdir(self):
717
 
        from bzrlib.plugins.git.dir import LocalGitControlDirFormat
718
 
        return LocalGitControlDirFormat()
719
 
 
720
 
    def get_format_description(self):
721
 
        return "Git Working Tree"
722
 
 
723
 
    def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
724
 
                   accelerator_tree=None, hardlink=False):
725
 
        """See WorkingTreeFormat.initialize()."""
726
 
        if not isinstance(a_bzrdir, LocalGitDir):
727
 
            raise errors.IncompatibleFormat(self, a_bzrdir)
728
 
        index = Index(a_bzrdir.root_transport.local_abspath(".git/index"))
729
 
        index.write()
730
 
        return GitWorkingTree(a_bzrdir, a_bzrdir.open_repository(),
731
 
            a_bzrdir.open_branch(), index)
732
 
 
733
 
 
734
 
class InterIndexGitTree(tree.InterTree):
735
 
    """InterTree that works between a Git revision tree and an index."""
736
 
 
737
 
    def __init__(self, source, target):
738
 
        super(InterIndexGitTree, self).__init__(source, target)
739
 
        self._index = target.index
740
 
 
741
 
    @classmethod
742
 
    def is_compatible(cls, source, target):
743
 
        from bzrlib.plugins.git.repository import GitRevisionTree
744
 
        return (isinstance(source, GitRevisionTree) and 
745
 
                isinstance(target, GitWorkingTree))
746
 
 
747
 
    def compare(self, want_unchanged=False, specific_files=None,
748
 
                extra_trees=None, require_versioned=False, include_root=False,
749
 
                want_unversioned=False):
750
 
        changes = self._index.changes_from_tree(
751
 
            self.source.store, self.source.tree, 
752
 
            want_unchanged=want_unchanged)
753
 
        source_fileid_map = self.source.mapping.get_fileid_map(
754
 
            self.source.store.__getitem__,
755
 
            self.source.tree)
756
 
        if self.target.mapping.BZR_FILE_IDS_FILE is not None:
757
 
            file_id = self.target.path2id(
758
 
                self.target.mapping.BZR_FILE_IDS_FILE)
759
 
            if file_id is None:
760
 
                target_fileid_map = {}
761
 
            else:
762
 
                target_fileid_map = self.target.mapping.import_fileid_map(
763
 
                    Blob.from_string(self.target.get_file_text(file_id)))
764
 
        else:
765
 
            target_fileid_map = {}
766
 
        target_fileid_map = GitFileIdMap(target_fileid_map,
767
 
                self.target.mapping)
768
 
        ret = tree_delta_from_git_changes(changes, self.target.mapping,
769
 
            (source_fileid_map, target_fileid_map),
770
 
            specific_file=specific_files, require_versioned=require_versioned)
771
 
        if want_unversioned:
772
 
            for e in self.target.extras():
773
 
                ret.unversioned.append((e, None,
774
 
                    osutils.file_kind(self.target.abspath(e))))
775
 
        return ret
776
 
 
777
 
    def iter_changes(self, include_unchanged=False, specific_files=None,
778
 
        pb=None, extra_trees=[], require_versioned=True,
779
 
        want_unversioned=False):
780
 
        changes = self._index.changes_from_tree(
781
 
            self.source.store, self.source.tree,
782
 
            want_unchanged=include_unchanged)
783
 
        # FIXME: Handle want_unversioned
784
 
        return changes_from_git_changes(changes, self.target.mapping,
785
 
            specific_file=specific_files)
786
 
 
787
 
 
788
 
tree.InterTree.register_optimiser(InterIndexGitTree)