/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 breezy/git/workingtree.py

  • Committer: Jelmer Vernooij
  • Date: 2019-10-28 01:38:39 UTC
  • mto: This revision was merged to the branch mainline in revision 7412.
  • Revision ID: jelmer@jelmer.uk-20191028013839-q63zzm4yr0id9b3o
Allow unknown extras in git commits when just inspecting revisions, rather than importing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008-2018 Jelmer Vernooij <jelmer@jelmer.uk>
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
"""An adapter between a Git index and a Bazaar Working Tree"""
 
19
 
 
20
from __future__ import absolute_import
 
21
 
 
22
import itertools
 
23
from collections import defaultdict
 
24
import errno
 
25
from dulwich.ignore import (
 
26
    IgnoreFilterManager,
 
27
    )
 
28
from dulwich.file import GitFile, FileLocked
 
29
from dulwich.index import (
 
30
    Index,
 
31
    SHA1Writer,
 
32
    build_index_from_tree,
 
33
    index_entry_from_path,
 
34
    index_entry_from_stat,
 
35
    FLAG_STAGEMASK,
 
36
    read_submodule_head,
 
37
    validate_path,
 
38
    write_index_dict,
 
39
    )
 
40
from dulwich.object_store import (
 
41
    tree_lookup_path,
 
42
    )
 
43
from dulwich.objects import (
 
44
    S_ISGITLINK,
 
45
    )
 
46
import os
 
47
import posixpath
 
48
import stat
 
49
import sys
 
50
 
 
51
from .. import (
 
52
    conflicts as _mod_conflicts,
 
53
    errors,
 
54
    controldir as _mod_controldir,
 
55
    globbing,
 
56
    ignores,
 
57
    lock,
 
58
    merge,
 
59
    osutils,
 
60
    revision as _mod_revision,
 
61
    trace,
 
62
    transport as _mod_transport,
 
63
    tree,
 
64
    workingtree,
 
65
    )
 
66
from ..decorators import (
 
67
    only_raises,
 
68
    )
 
69
from ..mutabletree import (
 
70
    BadReferenceTarget,
 
71
    MutableTree,
 
72
    )
 
73
 
 
74
 
 
75
from .dir import (
 
76
    LocalGitDir,
 
77
    )
 
78
from .tree import (
 
79
    MutableGitIndexTree,
 
80
    )
 
81
from .mapping import (
 
82
    mode_kind,
 
83
    )
 
84
 
 
85
 
 
86
class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree):
 
87
    """A Git working tree."""
 
88
 
 
89
    def __init__(self, controldir, repo, branch):
 
90
        MutableGitIndexTree.__init__(self)
 
91
        basedir = controldir.root_transport.local_abspath('.')
 
92
        self.basedir = osutils.realpath(basedir)
 
93
        self.controldir = controldir
 
94
        self.repository = repo
 
95
        self.store = self.repository._git.object_store
 
96
        self.mapping = self.repository.get_mapping()
 
97
        self._branch = branch
 
98
        self._transport = self.repository._git._controltransport
 
99
        self._format = GitWorkingTreeFormat()
 
100
        self.index = None
 
101
        self._index_file = None
 
102
        self.views = self._make_views()
 
103
        self._rules_searcher = None
 
104
        self._detect_case_handling()
 
105
        self._reset_data()
 
106
 
 
107
    def supports_tree_reference(self):
 
108
        return True
 
109
 
 
110
    def supports_rename_tracking(self):
 
111
        return False
 
112
 
 
113
    def _read_index(self):
 
114
        self.index = Index(self.control_transport.local_abspath('index'))
 
115
        self._index_dirty = False
 
116
 
 
117
    def lock_read(self):
 
118
        """Lock the repository for read operations.
 
119
 
 
120
        :return: A breezy.lock.LogicalLockResult.
 
121
        """
 
122
        if not self._lock_mode:
 
123
            self._lock_mode = 'r'
 
124
            self._lock_count = 1
 
125
            self._read_index()
 
126
        else:
 
127
            self._lock_count += 1
 
128
        self.branch.lock_read()
 
129
        return lock.LogicalLockResult(self.unlock)
 
130
 
 
131
    def _lock_write_tree(self):
 
132
        if not self._lock_mode:
 
133
            self._lock_mode = 'w'
 
134
            self._lock_count = 1
 
135
            try:
 
136
                self._index_file = GitFile(
 
137
                    self.control_transport.local_abspath('index'), 'wb')
 
138
            except FileLocked:
 
139
                raise errors.LockContention('index')
 
140
            self._read_index()
 
141
        elif self._lock_mode == 'r':
 
142
            raise errors.ReadOnlyError(self)
 
143
        else:
 
144
            self._lock_count += 1
 
145
 
 
146
    def lock_tree_write(self):
 
147
        self.branch.lock_read()
 
148
        try:
 
149
            self._lock_write_tree()
 
150
            return lock.LogicalLockResult(self.unlock)
 
151
        except BaseException:
 
152
            self.branch.unlock()
 
153
            raise
 
154
 
 
155
    def lock_write(self, token=None):
 
156
        self.branch.lock_write()
 
157
        try:
 
158
            self._lock_write_tree()
 
159
            return lock.LogicalLockResult(self.unlock)
 
160
        except BaseException:
 
161
            self.branch.unlock()
 
162
            raise
 
163
 
 
164
    def is_locked(self):
 
165
        return self._lock_count >= 1
 
166
 
 
167
    def get_physical_lock_status(self):
 
168
        return False
 
169
 
 
170
    def break_lock(self):
 
171
        try:
 
172
            self.control_transport.delete('index.lock')
 
173
        except errors.NoSuchFile:
 
174
            pass
 
175
        self.branch.break_lock()
 
176
 
 
177
    @only_raises(errors.LockNotHeld, errors.LockBroken)
 
178
    def unlock(self):
 
179
        if not self._lock_count:
 
180
            return lock.cant_unlock_not_held(self)
 
181
        try:
 
182
            self._cleanup()
 
183
            self._lock_count -= 1
 
184
            if self._lock_count > 0:
 
185
                return
 
186
            if self._index_file is not None:
 
187
                if self._index_dirty:
 
188
                    self._flush(self._index_file)
 
189
                    self._index_file.close()
 
190
                else:
 
191
                    # Something else already triggered a write of the index
 
192
                    # file by calling .flush()
 
193
                    self._index_file.abort()
 
194
                self._index_file = None
 
195
            self._lock_mode = None
 
196
            self.index = None
 
197
        finally:
 
198
            self.branch.unlock()
 
199
 
 
200
    def _cleanup(self):
 
201
        pass
 
202
 
 
203
    def _detect_case_handling(self):
 
204
        try:
 
205
            self._transport.stat(".git/cOnFiG")
 
206
        except errors.NoSuchFile:
 
207
            self.case_sensitive = True
 
208
        else:
 
209
            self.case_sensitive = False
 
210
 
 
211
    def get_transform(self, pb=None):
 
212
        from ..transform import TreeTransform
 
213
        return TreeTransform(self, pb=pb)
 
214
 
 
215
    def merge_modified(self):
 
216
        return {}
 
217
 
 
218
    def set_merge_modified(self, modified_hashes):
 
219
        raise errors.UnsupportedOperation(self.set_merge_modified, self)
 
220
 
 
221
    def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
 
222
        self.set_parent_ids([p for p, t in parents_list])
 
223
 
 
224
    def _set_merges_from_parent_ids(self, rhs_parent_ids):
 
225
        try:
 
226
            merges = [self.branch.lookup_bzr_revision_id(
 
227
                revid)[0] for revid in rhs_parent_ids]
 
228
        except errors.NoSuchRevision as e:
 
229
            raise errors.GhostRevisionUnusableHere(e.revision)
 
230
        if merges:
 
231
            self.control_transport.put_bytes(
 
232
                'MERGE_HEAD', b'\n'.join(merges),
 
233
                mode=self.controldir._get_file_mode())
 
234
        else:
 
235
            try:
 
236
                self.control_transport.delete('MERGE_HEAD')
 
237
            except errors.NoSuchFile:
 
238
                pass
 
239
 
 
240
    def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
 
241
        """Set the parent ids to revision_ids.
 
242
 
 
243
        See also set_parent_trees. This api will try to retrieve the tree data
 
244
        for each element of revision_ids from the trees repository. If you have
 
245
        tree data already available, it is more efficient to use
 
246
        set_parent_trees rather than set_parent_ids. set_parent_ids is however
 
247
        an easier API to use.
 
248
 
 
249
        :param revision_ids: The revision_ids to set as the parent ids of this
 
250
            working tree. Any of these may be ghosts.
 
251
        """
 
252
        with self.lock_tree_write():
 
253
            self._check_parents_for_ghosts(
 
254
                revision_ids, allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
255
            for revision_id in revision_ids:
 
256
                _mod_revision.check_not_reserved_id(revision_id)
 
257
 
 
258
            revision_ids = self._filter_parent_ids_by_ancestry(revision_ids)
 
259
 
 
260
            if len(revision_ids) > 0:
 
261
                self.set_last_revision(revision_ids[0])
 
262
            else:
 
263
                self.set_last_revision(_mod_revision.NULL_REVISION)
 
264
 
 
265
            self._set_merges_from_parent_ids(revision_ids[1:])
 
266
 
 
267
    def get_parent_ids(self):
 
268
        """See Tree.get_parent_ids.
 
269
 
 
270
        This implementation reads the pending merges list and last_revision
 
271
        value and uses that to decide what the parents list should be.
 
272
        """
 
273
        last_rev = _mod_revision.ensure_null(self._last_revision())
 
274
        if _mod_revision.NULL_REVISION == last_rev:
 
275
            parents = []
 
276
        else:
 
277
            parents = [last_rev]
 
278
        try:
 
279
            merges_bytes = self.control_transport.get_bytes('MERGE_HEAD')
 
280
        except errors.NoSuchFile:
 
281
            pass
 
282
        else:
 
283
            for l in osutils.split_lines(merges_bytes):
 
284
                revision_id = l.rstrip(b'\n')
 
285
                parents.append(
 
286
                    self.branch.lookup_foreign_revision_id(revision_id))
 
287
        return parents
 
288
 
 
289
    def check_state(self):
 
290
        """Check that the working state is/isn't valid."""
 
291
        pass
 
292
 
 
293
    def remove(self, files, verbose=False, to_file=None, keep_files=True,
 
294
               force=False):
 
295
        """Remove nominated files from the working tree metadata.
 
296
 
 
297
        :param files: File paths relative to the basedir.
 
298
        :param keep_files: If true, the files will also be kept.
 
299
        :param force: Delete files and directories, even if they are changed
 
300
            and even if the directories are not empty.
 
301
        """
 
302
        if not isinstance(files, list):
 
303
            files = [files]
 
304
 
 
305
        if to_file is None:
 
306
            to_file = sys.stdout
 
307
 
 
308
        def backup(file_to_backup):
 
309
            abs_path = self.abspath(file_to_backup)
 
310
            backup_name = self.controldir._available_backup_name(
 
311
                file_to_backup)
 
312
            osutils.rename(abs_path, self.abspath(backup_name))
 
313
            return "removed %s (but kept a copy: %s)" % (
 
314
                file_to_backup, backup_name)
 
315
 
 
316
        # Sort needed to first handle directory content before the directory
 
317
        files_to_backup = []
 
318
 
 
319
        all_files = set()
 
320
 
 
321
        def recurse_directory_to_add_files(directory):
 
322
            # Recurse directory and add all files
 
323
            # so we can check if they have changed.
 
324
            for parent_info, file_infos in self.walkdirs(directory):
 
325
                for relpath, basename, kind, lstat, fileid, kind in file_infos:
 
326
                    # Is it versioned or ignored?
 
327
                    if self.is_versioned(relpath):
 
328
                        # Add nested content for deletion.
 
329
                        all_files.add(relpath)
 
330
                    else:
 
331
                        # Files which are not versioned
 
332
                        # should be treated as unknown.
 
333
                        files_to_backup.append(relpath)
 
334
 
 
335
        with self.lock_tree_write():
 
336
            for filepath in files:
 
337
                # Get file name into canonical form.
 
338
                abspath = self.abspath(filepath)
 
339
                filepath = self.relpath(abspath)
 
340
 
 
341
                if filepath:
 
342
                    all_files.add(filepath)
 
343
                    recurse_directory_to_add_files(filepath)
 
344
 
 
345
            files = list(all_files)
 
346
 
 
347
            if len(files) == 0:
 
348
                return  # nothing to do
 
349
 
 
350
            # Sort needed to first handle directory content before the
 
351
            # directory
 
352
            files.sort(reverse=True)
 
353
 
 
354
            # Bail out if we are going to delete files we shouldn't
 
355
            if not keep_files and not force:
 
356
                for change in self.iter_changes(
 
357
                        self.basis_tree(), include_unchanged=True,
 
358
                        require_versioned=False, want_unversioned=True,
 
359
                        specific_files=files):
 
360
                    if change.versioned[0] is False:
 
361
                        # The record is unknown or newly added
 
362
                        files_to_backup.append(change.path[1])
 
363
                        files_to_backup.extend(
 
364
                            osutils.parent_directories(change.path[1]))
 
365
                    elif (change.changed_content and (change.kind[1] is not None)
 
366
                            and osutils.is_inside_any(files, change.path[1])):
 
367
                        # Versioned and changed, but not deleted, and still
 
368
                        # in one of the dirs to be deleted.
 
369
                        files_to_backup.append(change.path[1])
 
370
                        files_to_backup.extend(
 
371
                            osutils.parent_directories(change.path[1]))
 
372
 
 
373
            for f in files:
 
374
                if f == '':
 
375
                    continue
 
376
 
 
377
                try:
 
378
                    kind = self.kind(f)
 
379
                except errors.NoSuchFile:
 
380
                    kind = None
 
381
 
 
382
                abs_path = self.abspath(f)
 
383
                if verbose:
 
384
                    # having removed it, it must be either ignored or unknown
 
385
                    if self.is_ignored(f):
 
386
                        new_status = 'I'
 
387
                    else:
 
388
                        new_status = '?'
 
389
                    kind_ch = osutils.kind_marker(kind)
 
390
                    to_file.write(new_status + '       ' + f + kind_ch + '\n')
 
391
                if kind is None:
 
392
                    message = "%s does not exist" % (f, )
 
393
                else:
 
394
                    if not keep_files:
 
395
                        if f in files_to_backup and not force:
 
396
                            message = backup(f)
 
397
                        else:
 
398
                            if kind == 'directory':
 
399
                                osutils.rmtree(abs_path)
 
400
                            else:
 
401
                                osutils.delete_any(abs_path)
 
402
                            message = "deleted %s" % (f,)
 
403
                    else:
 
404
                        message = "removed %s" % (f,)
 
405
                self._unversion_path(f)
 
406
 
 
407
                # print only one message (if any) per file.
 
408
                if message is not None:
 
409
                    trace.note(message)
 
410
            self._versioned_dirs = None
 
411
 
 
412
    def smart_add(self, file_list, recurse=True, action=None, save=True):
 
413
        if not file_list:
 
414
            file_list = [u'.']
 
415
 
 
416
        # expand any symlinks in the directory part, while leaving the
 
417
        # filename alone
 
418
        # only expanding if symlinks are supported avoids windows path bugs
 
419
        if self.supports_symlinks():
 
420
            file_list = list(map(osutils.normalizepath, file_list))
 
421
 
 
422
        conflicts_related = set()
 
423
        for c in self.conflicts():
 
424
            conflicts_related.update(c.associated_filenames())
 
425
 
 
426
        added = []
 
427
        ignored = {}
 
428
        user_dirs = []
 
429
 
 
430
        def call_action(filepath, kind):
 
431
            if filepath == '':
 
432
                return
 
433
            if action is not None:
 
434
                parent_path = posixpath.dirname(filepath)
 
435
                parent_id = self.path2id(parent_path)
 
436
                parent_ie = self._get_dir_ie(parent_path, parent_id)
 
437
                file_id = action(self, parent_ie, filepath, kind)
 
438
                if file_id is not None:
 
439
                    raise workingtree.SettingFileIdUnsupported()
 
440
 
 
441
        with self.lock_tree_write():
 
442
            for filepath in osutils.canonical_relpaths(
 
443
                    self.basedir, file_list):
 
444
                filepath, can_access = osutils.normalized_filename(filepath)
 
445
                if not can_access:
 
446
                    raise errors.InvalidNormalization(filepath)
 
447
 
 
448
                abspath = self.abspath(filepath)
 
449
                kind = osutils.file_kind(abspath)
 
450
                if kind in ("file", "symlink"):
 
451
                    (index, subpath) = self._lookup_index(
 
452
                        filepath.encode('utf-8'))
 
453
                    if subpath in index:
 
454
                        # Already present
 
455
                        continue
 
456
                    call_action(filepath, kind)
 
457
                    if save:
 
458
                        self._index_add_entry(filepath, kind)
 
459
                    added.append(filepath)
 
460
                elif kind == "directory":
 
461
                    (index, subpath) = self._lookup_index(
 
462
                        filepath.encode('utf-8'))
 
463
                    if subpath not in index:
 
464
                        call_action(filepath, kind)
 
465
                    if recurse:
 
466
                        user_dirs.append(filepath)
 
467
                else:
 
468
                    raise errors.BadFileKindError(filename=abspath, kind=kind)
 
469
            for user_dir in user_dirs:
 
470
                abs_user_dir = self.abspath(user_dir)
 
471
                if user_dir != '':
 
472
                    try:
 
473
                        transport = _mod_transport.get_transport_from_path(
 
474
                            abs_user_dir)
 
475
                        _mod_controldir.ControlDirFormat.find_format(transport)
 
476
                        subtree = True
 
477
                    except errors.NotBranchError:
 
478
                        subtree = False
 
479
                    except errors.UnsupportedFormatError:
 
480
                        subtree = False
 
481
                else:
 
482
                    subtree = False
 
483
                if subtree:
 
484
                    trace.warning('skipping nested tree %r', abs_user_dir)
 
485
                    continue
 
486
 
 
487
                for name in os.listdir(abs_user_dir):
 
488
                    subp = os.path.join(user_dir, name)
 
489
                    if (self.is_control_filename(subp) or
 
490
                            self.mapping.is_special_file(subp)):
 
491
                        continue
 
492
                    ignore_glob = self.is_ignored(subp)
 
493
                    if ignore_glob is not None:
 
494
                        ignored.setdefault(ignore_glob, []).append(subp)
 
495
                        continue
 
496
                    abspath = self.abspath(subp)
 
497
                    kind = osutils.file_kind(abspath)
 
498
                    if kind == "directory":
 
499
                        user_dirs.append(subp)
 
500
                    else:
 
501
                        (index, subpath) = self._lookup_index(
 
502
                            subp.encode('utf-8'))
 
503
                        if subpath in index:
 
504
                            # Already present
 
505
                            continue
 
506
                        if subp in conflicts_related:
 
507
                            continue
 
508
                        call_action(subp, kind)
 
509
                        if save:
 
510
                            self._index_add_entry(subp, kind)
 
511
                        added.append(subp)
 
512
            return added, ignored
 
513
 
 
514
    def has_filename(self, filename):
 
515
        return osutils.lexists(self.abspath(filename))
 
516
 
 
517
    def _iter_files_recursive(self, from_dir=None, include_dirs=False):
 
518
        if from_dir is None:
 
519
            from_dir = u""
 
520
        encoded_from_dir = self.abspath(from_dir).encode(osutils._fs_enc)
 
521
        for (dirpath, dirnames, filenames) in os.walk(encoded_from_dir):
 
522
            dir_relpath = dirpath[len(self.basedir):].strip(b"/")
 
523
            if self.controldir.is_control_filename(
 
524
                    dir_relpath.decode(osutils._fs_enc)):
 
525
                continue
 
526
            for name in list(dirnames):
 
527
                if self.controldir.is_control_filename(
 
528
                        name.decode(osutils._fs_enc)):
 
529
                    dirnames.remove(name)
 
530
                    continue
 
531
                relpath = os.path.join(dir_relpath, name)
 
532
                if include_dirs:
 
533
                    try:
 
534
                        yield relpath.decode(osutils._fs_enc)
 
535
                    except UnicodeDecodeError:
 
536
                        raise errors.BadFilenameEncoding(
 
537
                            relpath, osutils._fs_enc)
 
538
                    if not self._has_dir(relpath):
 
539
                        dirnames.remove(name)
 
540
            for name in filenames:
 
541
                if self.mapping.is_special_file(name):
 
542
                    continue
 
543
                if self.controldir.is_control_filename(
 
544
                        name.decode(osutils._fs_enc, 'replace')):
 
545
                    continue
 
546
                yp = os.path.join(dir_relpath, name)
 
547
                try:
 
548
                    yield yp.decode(osutils._fs_enc)
 
549
                except UnicodeDecodeError:
 
550
                    raise errors.BadFilenameEncoding(
 
551
                        yp, osutils._fs_enc)
 
552
 
 
553
    def extras(self):
 
554
        """Yield all unversioned files in this WorkingTree.
 
555
        """
 
556
        with self.lock_read():
 
557
            index_paths = set(
 
558
                [p.decode('utf-8') for p, i in self._recurse_index_entries()])
 
559
            all_paths = set(self._iter_files_recursive(include_dirs=False))
 
560
            return iter(all_paths - index_paths)
 
561
 
 
562
    def _gather_kinds(self, files, kinds):
 
563
        """See MutableTree._gather_kinds."""
 
564
        with self.lock_tree_write():
 
565
            for pos, f in enumerate(files):
 
566
                if kinds[pos] is None:
 
567
                    fullpath = osutils.normpath(self.abspath(f))
 
568
                    try:
 
569
                        kind = osutils.file_kind(fullpath)
 
570
                    except OSError as e:
 
571
                        if e.errno == errno.ENOENT:
 
572
                            raise errors.NoSuchFile(fullpath)
 
573
                    if f != '' and self._directory_is_tree_reference(f):
 
574
                        kind = 'tree-reference'
 
575
                    kinds[pos] = kind
 
576
 
 
577
    def flush(self):
 
578
        if self._lock_mode != 'w':
 
579
            raise errors.NotWriteLocked(self)
 
580
        # TODO(jelmer): This shouldn't be writing in-place, but index.lock is
 
581
        # already in use and GitFile doesn't allow overriding the lock file
 
582
        # name :(
 
583
        f = open(self.control_transport.local_abspath('index'), 'wb')
 
584
        # Note that _flush will close the file
 
585
        self._flush(f)
 
586
 
 
587
    def _flush(self, f):
 
588
        try:
 
589
            shaf = SHA1Writer(f)
 
590
            write_index_dict(shaf, self.index)
 
591
            shaf.close()
 
592
        except BaseException:
 
593
            f.abort()
 
594
            raise
 
595
        self._index_dirty = False
 
596
 
 
597
    def get_file_mtime(self, path):
 
598
        """See Tree.get_file_mtime."""
 
599
        try:
 
600
            return self._lstat(path).st_mtime
 
601
        except OSError as e:
 
602
            if e.errno == errno.ENOENT:
 
603
                raise errors.NoSuchFile(path)
 
604
            raise
 
605
 
 
606
    def is_ignored(self, filename):
 
607
        r"""Check whether the filename matches an ignore pattern.
 
608
 
 
609
        If the file is ignored, returns the pattern which caused it to
 
610
        be ignored, otherwise None.  So this can simply be used as a
 
611
        boolean if desired."""
 
612
        if getattr(self, '_global_ignoreglobster', None) is None:
 
613
            ignore_globs = set()
 
614
            ignore_globs.update(ignores.get_runtime_ignores())
 
615
            ignore_globs.update(ignores.get_user_ignores())
 
616
            self._global_ignoreglobster = globbing.ExceptionGlobster(
 
617
                ignore_globs)
 
618
        match = self._global_ignoreglobster.match(filename)
 
619
        if match is not None:
 
620
            return match
 
621
        try:
 
622
            if self.kind(filename) == 'directory':
 
623
                filename += '/'
 
624
        except errors.NoSuchFile:
 
625
            pass
 
626
        filename = filename.lstrip('/')
 
627
        ignore_manager = self._get_ignore_manager()
 
628
        ps = list(ignore_manager.find_matching(filename))
 
629
        if not ps:
 
630
            return None
 
631
        if not ps[-1].is_exclude:
 
632
            return None
 
633
        return bytes(ps[-1])
 
634
 
 
635
    def _get_ignore_manager(self):
 
636
        ignoremanager = getattr(self, '_ignoremanager', None)
 
637
        if ignoremanager is not None:
 
638
            return ignoremanager
 
639
 
 
640
        ignore_manager = IgnoreFilterManager.from_repo(self.repository._git)
 
641
        self._ignoremanager = ignore_manager
 
642
        return ignore_manager
 
643
 
 
644
    def _flush_ignore_list_cache(self):
 
645
        self._ignoremanager = None
 
646
 
 
647
    def set_last_revision(self, revid):
 
648
        if _mod_revision.is_null(revid):
 
649
            self.branch.set_last_revision_info(0, revid)
 
650
            return False
 
651
        _mod_revision.check_not_reserved_id(revid)
 
652
        try:
 
653
            self.branch.generate_revision_history(revid)
 
654
        except errors.NoSuchRevision:
 
655
            raise errors.GhostRevisionUnusableHere(revid)
 
656
 
 
657
    def _reset_data(self):
 
658
        pass
 
659
 
 
660
    def get_file_verifier(self, path, stat_value=None):
 
661
        with self.lock_read():
 
662
            (index, subpath) = self._lookup_index(path.encode('utf-8'))
 
663
            try:
 
664
                return ("GIT", index[subpath].sha)
 
665
            except KeyError:
 
666
                if self._has_dir(path):
 
667
                    return ("GIT", None)
 
668
                raise errors.NoSuchFile(path)
 
669
 
 
670
    def get_file_sha1(self, path, stat_value=None):
 
671
        with self.lock_read():
 
672
            if not self.is_versioned(path):
 
673
                raise errors.NoSuchFile(path)
 
674
            abspath = self.abspath(path)
 
675
            try:
 
676
                return osutils.sha_file_by_name(abspath)
 
677
            except OSError as e:
 
678
                if e.errno in (errno.EISDIR, errno.ENOENT):
 
679
                    return None
 
680
                raise
 
681
 
 
682
    def revision_tree(self, revid):
 
683
        return self.repository.revision_tree(revid)
 
684
 
 
685
    def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
 
686
        mode = stat_result.st_mode
 
687
        return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
688
 
 
689
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
 
690
        return self.basis_tree().is_executable(path)
 
691
 
 
692
    def stored_kind(self, path):
 
693
        with self.lock_read():
 
694
            encoded_path = path.encode('utf-8')
 
695
            (index, subpath) = self._lookup_index(encoded_path)
 
696
            try:
 
697
                return mode_kind(index[subpath].mode)
 
698
            except KeyError:
 
699
                # Maybe it's a directory?
 
700
                if self._has_dir(encoded_path):
 
701
                    return "directory"
 
702
                raise errors.NoSuchFile(path)
 
703
 
 
704
    def _lstat(self, path):
 
705
        return os.lstat(self.abspath(path))
 
706
 
 
707
    def _live_entry(self, path):
 
708
        encoded_path = self.abspath(path.decode('utf-8')).encode(
 
709
            osutils._fs_enc)
 
710
        return index_entry_from_path(encoded_path)
 
711
 
 
712
    def is_executable(self, path):
 
713
        with self.lock_read():
 
714
            if self._supports_executable():
 
715
                mode = self._lstat(path).st_mode
 
716
            else:
 
717
                (index, subpath) = self._lookup_index(path.encode('utf-8'))
 
718
                try:
 
719
                    mode = index[subpath].mode
 
720
                except KeyError:
 
721
                    mode = 0
 
722
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
 
723
 
 
724
    def _is_executable_from_path_and_stat(self, path, stat_result):
 
725
        if self._supports_executable():
 
726
            return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
 
727
        else:
 
728
            return self._is_executable_from_path_and_stat_from_basis(
 
729
                path, stat_result)
 
730
 
 
731
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
732
        if from_dir is None or from_dir == '.':
 
733
            from_dir = u""
 
734
        dir_ids = {}
 
735
        fk_entries = {'directory': tree.TreeDirectory,
 
736
                      'file': tree.TreeFile,
 
737
                      'symlink': tree.TreeLink,
 
738
                      'tree-reference': tree.TreeReference}
 
739
        with self.lock_read():
 
740
            root_ie = self._get_dir_ie(u"", None)
 
741
            if include_root and not from_dir:
 
742
                yield "", "V", root_ie.kind, root_ie
 
743
            dir_ids[u""] = root_ie.file_id
 
744
            if recursive:
 
745
                path_iterator = sorted(
 
746
                    self._iter_files_recursive(from_dir, include_dirs=True))
 
747
            else:
 
748
                encoded_from_dir = self.abspath(from_dir).encode(
 
749
                    osutils._fs_enc)
 
750
                path_iterator = sorted(
 
751
                    [os.path.join(from_dir, name.decode(osutils._fs_enc))
 
752
                     for name in os.listdir(encoded_from_dir)
 
753
                     if not self.controldir.is_control_filename(
 
754
                         name.decode(osutils._fs_enc)) and
 
755
                     not self.mapping.is_special_file(
 
756
                         name.decode(osutils._fs_enc))])
 
757
            for path in path_iterator:
 
758
                try:
 
759
                    encoded_path = path.encode("utf-8")
 
760
                except UnicodeEncodeError:
 
761
                    raise errors.BadFilenameEncoding(
 
762
                        path, osutils._fs_enc)
 
763
                (index, index_path) = self._lookup_index(encoded_path)
 
764
                try:
 
765
                    value = index[index_path]
 
766
                except KeyError:
 
767
                    value = None
 
768
                kind = self.kind(path)
 
769
                parent, name = posixpath.split(path)
 
770
                for dir_path, dir_ie in self._add_missing_parent_ids(
 
771
                        parent, dir_ids):
 
772
                    pass
 
773
                if kind in ('directory', 'tree-reference'):
 
774
                    if path != from_dir:
 
775
                        if self._has_dir(encoded_path):
 
776
                            ie = self._get_dir_ie(path, self.path2id(path))
 
777
                            status = "V"
 
778
                        elif self.is_ignored(path):
 
779
                            status = "I"
 
780
                            ie = fk_entries[kind]()
 
781
                        else:
 
782
                            status = "?"
 
783
                            ie = fk_entries[kind]()
 
784
                        yield (posixpath.relpath(path, from_dir), status, kind,
 
785
                               ie)
 
786
                    continue
 
787
                if value is not None:
 
788
                    ie = self._get_file_ie(name, path, value, dir_ids[parent])
 
789
                    yield (posixpath.relpath(path, from_dir), "V", ie.kind, ie)
 
790
                else:
 
791
                    ie = fk_entries[kind]()
 
792
                    yield (posixpath.relpath(path, from_dir),
 
793
                           ("I" if self.is_ignored(path) else "?"), kind, ie)
 
794
 
 
795
    def all_file_ids(self):
 
796
        raise errors.UnsupportedOperation(self.all_file_ids, self)
 
797
 
 
798
    def all_versioned_paths(self):
 
799
        with self.lock_read():
 
800
            paths = {u""}
 
801
            for path in self.index:
 
802
                if self.mapping.is_special_file(path):
 
803
                    continue
 
804
                path = path.decode("utf-8")
 
805
                paths.add(path)
 
806
                while path != "":
 
807
                    path = posixpath.dirname(path).strip("/")
 
808
                    if path in paths:
 
809
                        break
 
810
                    paths.add(path)
 
811
            return paths
 
812
 
 
813
    def iter_child_entries(self, path):
 
814
        encoded_path = path.encode('utf-8')
 
815
        with self.lock_read():
 
816
            parent_id = self.path2id(path)
 
817
            found_any = False
 
818
            for item_path, value in self.index.iteritems():
 
819
                decoded_item_path = item_path.decode('utf-8')
 
820
                if self.mapping.is_special_file(item_path):
 
821
                    continue
 
822
                if not osutils.is_inside(path, decoded_item_path):
 
823
                    continue
 
824
                found_any = True
 
825
                subpath = posixpath.relpath(decoded_item_path, path)
 
826
                if '/' in subpath:
 
827
                    dirname = subpath.split('/', 1)[0]
 
828
                    file_ie = self._get_dir_ie(
 
829
                        posixpath.join(path, dirname), parent_id)
 
830
                else:
 
831
                    (unused_parent, name) = posixpath.split(decoded_item_path)
 
832
                    file_ie = self._get_file_ie(
 
833
                        name, decoded_item_path, value, parent_id)
 
834
                yield file_ie
 
835
            if not found_any and path != u'':
 
836
                raise errors.NoSuchFile(path)
 
837
 
 
838
    def conflicts(self):
 
839
        with self.lock_read():
 
840
            conflicts = _mod_conflicts.ConflictList()
 
841
            for item_path, value in self.index.iteritems():
 
842
                if value.flags & FLAG_STAGEMASK:
 
843
                    conflicts.append(_mod_conflicts.TextConflict(
 
844
                        item_path.decode('utf-8')))
 
845
            return conflicts
 
846
 
 
847
    def set_conflicts(self, conflicts):
 
848
        by_path = set()
 
849
        for conflict in conflicts:
 
850
            if conflict.typestring in ('text conflict', 'contents conflict'):
 
851
                by_path.add(conflict.path.encode('utf-8'))
 
852
            else:
 
853
                raise errors.UnsupportedOperation(self.set_conflicts, self)
 
854
        with self.lock_tree_write():
 
855
            for path in self.index:
 
856
                self._set_conflicted(path, path in by_path)
 
857
 
 
858
    def _set_conflicted(self, path, conflicted):
 
859
        trace.mutter('change conflict: %r -> %r', path, conflicted)
 
860
        value = self.index[path]
 
861
        self._index_dirty = True
 
862
        if conflicted:
 
863
            self.index[path] = (value[:9] + (value[9] | FLAG_STAGEMASK, ))
 
864
        else:
 
865
            self.index[path] = (value[:9] + (value[9] & ~ FLAG_STAGEMASK, ))
 
866
 
 
867
    def add_conflicts(self, new_conflicts):
 
868
        with self.lock_tree_write():
 
869
            for conflict in new_conflicts:
 
870
                if conflict.typestring in ('text conflict',
 
871
                                           'contents conflict'):
 
872
                    try:
 
873
                        self._set_conflicted(
 
874
                            conflict.path.encode('utf-8'), True)
 
875
                    except KeyError:
 
876
                        raise errors.UnsupportedOperation(
 
877
                            self.add_conflicts, self)
 
878
                else:
 
879
                    raise errors.UnsupportedOperation(self.add_conflicts, self)
 
880
 
 
881
    def walkdirs(self, prefix=""):
 
882
        """Walk the directories of this tree.
 
883
 
 
884
        returns a generator which yields items in the form:
 
885
                ((curren_directory_path, fileid),
 
886
                 [(file1_path, file1_name, file1_kind, (lstat), file1_id,
 
887
                   file1_kind), ... ])
 
888
 
 
889
        This API returns a generator, which is only valid during the current
 
890
        tree transaction - within a single lock_read or lock_write duration.
 
891
 
 
892
        If the tree is not locked, it may cause an error to be raised,
 
893
        depending on the tree implementation.
 
894
        """
 
895
        from bisect import bisect_left
 
896
        import operator
 
897
        disk_top = self.abspath(prefix)
 
898
        if disk_top.endswith('/'):
 
899
            disk_top = disk_top[:-1]
 
900
        top_strip_len = len(disk_top) + 1
 
901
        inventory_iterator = self._walkdirs(prefix)
 
902
        disk_iterator = osutils.walkdirs(disk_top, prefix)
 
903
        try:
 
904
            current_disk = next(disk_iterator)
 
905
            disk_finished = False
 
906
        except OSError as e:
 
907
            if not (e.errno == errno.ENOENT
 
908
                    or (sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
 
909
                raise
 
910
            current_disk = None
 
911
            disk_finished = True
 
912
        try:
 
913
            current_inv = next(inventory_iterator)
 
914
            inv_finished = False
 
915
        except StopIteration:
 
916
            current_inv = None
 
917
            inv_finished = True
 
918
        while not inv_finished or not disk_finished:
 
919
            if current_disk:
 
920
                ((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
 
921
                    cur_disk_dir_content) = current_disk
 
922
            else:
 
923
                ((cur_disk_dir_relpath, cur_disk_dir_path_from_top),
 
924
                    cur_disk_dir_content) = ((None, None), None)
 
925
            if not disk_finished:
 
926
                # strip out .bzr dirs
 
927
                if (cur_disk_dir_path_from_top[top_strip_len:] == ''
 
928
                        and len(cur_disk_dir_content) > 0):
 
929
                    # osutils.walkdirs can be made nicer -
 
930
                    # yield the path-from-prefix rather than the pathjoined
 
931
                    # value.
 
932
                    bzrdir_loc = bisect_left(cur_disk_dir_content,
 
933
                                             ('.git', '.git'))
 
934
                    if (bzrdir_loc < len(cur_disk_dir_content) and
 
935
                        self.controldir.is_control_filename(
 
936
                            cur_disk_dir_content[bzrdir_loc][0])):
 
937
                        # we dont yield the contents of, or, .bzr itself.
 
938
                        del cur_disk_dir_content[bzrdir_loc]
 
939
            if inv_finished:
 
940
                # everything is unknown
 
941
                direction = 1
 
942
            elif disk_finished:
 
943
                # everything is missing
 
944
                direction = -1
 
945
            else:
 
946
                direction = ((current_inv[0][0] > cur_disk_dir_relpath)
 
947
                             - (current_inv[0][0] < cur_disk_dir_relpath))
 
948
            if direction > 0:
 
949
                # disk is before inventory - unknown
 
950
                dirblock = [(relpath, basename, kind, stat, None, None) for
 
951
                            relpath, basename, kind, stat, top_path in
 
952
                            cur_disk_dir_content]
 
953
                yield (cur_disk_dir_relpath, None), dirblock
 
954
                try:
 
955
                    current_disk = next(disk_iterator)
 
956
                except StopIteration:
 
957
                    disk_finished = True
 
958
            elif direction < 0:
 
959
                # inventory is before disk - missing.
 
960
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
 
961
                            for relpath, basename, dkind, stat, fileid, kind in
 
962
                            current_inv[1]]
 
963
                yield (current_inv[0][0], current_inv[0][1]), dirblock
 
964
                try:
 
965
                    current_inv = next(inventory_iterator)
 
966
                except StopIteration:
 
967
                    inv_finished = True
 
968
            else:
 
969
                # versioned present directory
 
970
                # merge the inventory and disk data together
 
971
                dirblock = []
 
972
                for relpath, subiterator in itertools.groupby(sorted(
 
973
                        current_inv[1] + cur_disk_dir_content,
 
974
                        key=operator.itemgetter(0)), operator.itemgetter(1)):
 
975
                    path_elements = list(subiterator)
 
976
                    if len(path_elements) == 2:
 
977
                        inv_row, disk_row = path_elements
 
978
                        # versioned, present file
 
979
                        dirblock.append((inv_row[0],
 
980
                                         inv_row[1], disk_row[2],
 
981
                                         disk_row[3], inv_row[4],
 
982
                                         inv_row[5]))
 
983
                    elif len(path_elements[0]) == 5:
 
984
                        # unknown disk file
 
985
                        dirblock.append(
 
986
                            (path_elements[0][0], path_elements[0][1],
 
987
                                path_elements[0][2], path_elements[0][3],
 
988
                                None, None))
 
989
                    elif len(path_elements[0]) == 6:
 
990
                        # versioned, absent file.
 
991
                        dirblock.append(
 
992
                            (path_elements[0][0], path_elements[0][1],
 
993
                                'unknown', None, path_elements[0][4],
 
994
                                path_elements[0][5]))
 
995
                    else:
 
996
                        raise NotImplementedError('unreachable code')
 
997
                yield current_inv[0], dirblock
 
998
                try:
 
999
                    current_inv = next(inventory_iterator)
 
1000
                except StopIteration:
 
1001
                    inv_finished = True
 
1002
                try:
 
1003
                    current_disk = next(disk_iterator)
 
1004
                except StopIteration:
 
1005
                    disk_finished = True
 
1006
 
 
1007
    def _walkdirs(self, prefix=u""):
 
1008
        if prefix != u"":
 
1009
            prefix += u"/"
 
1010
        prefix = prefix.encode('utf-8')
 
1011
        per_dir = defaultdict(set)
 
1012
        if prefix == b"":
 
1013
            per_dir[(u'', self.path2id(''))] = set()
 
1014
 
 
1015
        def add_entry(path, kind):
 
1016
            if path == b'' or not path.startswith(prefix):
 
1017
                return
 
1018
            (dirname, child_name) = posixpath.split(path)
 
1019
            add_entry(dirname, 'directory')
 
1020
            dirname = dirname.decode("utf-8")
 
1021
            dir_file_id = self.path2id(dirname)
 
1022
            if not isinstance(value, tuple) or len(value) != 10:
 
1023
                raise ValueError(value)
 
1024
            per_dir[(dirname, dir_file_id)].add(
 
1025
                (path.decode("utf-8"), child_name.decode("utf-8"),
 
1026
                 kind, None,
 
1027
                 self.path2id(path.decode("utf-8")),
 
1028
                 kind))
 
1029
        with self.lock_read():
 
1030
            for path, value in self.index.iteritems():
 
1031
                if self.mapping.is_special_file(path):
 
1032
                    continue
 
1033
                if not path.startswith(prefix):
 
1034
                    continue
 
1035
                add_entry(path, mode_kind(value.mode))
 
1036
        return ((k, sorted(v)) for (k, v) in sorted(per_dir.items()))
 
1037
 
 
1038
    def get_shelf_manager(self):
 
1039
        raise workingtree.ShelvingUnsupported()
 
1040
 
 
1041
    def store_uncommitted(self):
 
1042
        raise errors.StoringUncommittedNotSupported(self)
 
1043
 
 
1044
    def apply_inventory_delta(self, changes):
 
1045
        for (old_path, new_path, file_id, ie) in changes:
 
1046
            if old_path is not None:
 
1047
                (index, old_subpath) = self._lookup_index(
 
1048
                    old_path.encode('utf-8'))
 
1049
                try:
 
1050
                    self._index_del_entry(index, old_subpath)
 
1051
                except KeyError:
 
1052
                    pass
 
1053
                else:
 
1054
                    self._versioned_dirs = None
 
1055
            if new_path is not None and ie.kind != 'directory':
 
1056
                if ie.kind == 'tree-reference':
 
1057
                    self._index_add_entry(
 
1058
                        new_path, ie.kind,
 
1059
                        reference_revision=ie.reference_revision)
 
1060
                else:
 
1061
                    self._index_add_entry(new_path, ie.kind)
 
1062
        self.flush()
 
1063
 
 
1064
    def annotate_iter(self, path,
 
1065
                      default_revision=_mod_revision.CURRENT_REVISION):
 
1066
        """See Tree.annotate_iter
 
1067
 
 
1068
        This implementation will use the basis tree implementation if possible.
 
1069
        Lines not in the basis are attributed to CURRENT_REVISION
 
1070
 
 
1071
        If there are pending merges, lines added by those merges will be
 
1072
        incorrectly attributed to CURRENT_REVISION (but after committing, the
 
1073
        attribution will be correct).
 
1074
        """
 
1075
        with self.lock_read():
 
1076
            maybe_file_parent_keys = []
 
1077
            for parent_id in self.get_parent_ids():
 
1078
                try:
 
1079
                    parent_tree = self.revision_tree(parent_id)
 
1080
                except errors.NoSuchRevisionInTree:
 
1081
                    parent_tree = self.branch.repository.revision_tree(
 
1082
                        parent_id)
 
1083
                with parent_tree.lock_read():
 
1084
                    # TODO(jelmer): Use rename/copy tracker to find path name
 
1085
                    # in parent
 
1086
                    parent_path = path
 
1087
                    try:
 
1088
                        kind = parent_tree.kind(parent_path)
 
1089
                    except errors.NoSuchFile:
 
1090
                        continue
 
1091
                    if kind != 'file':
 
1092
                        # Note: this is slightly unnecessary, because symlinks
 
1093
                        # and directories have a "text" which is the empty
 
1094
                        # text, and we know that won't mess up annotations. But
 
1095
                        # it seems cleaner
 
1096
                        continue
 
1097
                    parent_text_key = (
 
1098
                        parent_path,
 
1099
                        parent_tree.get_file_revision(parent_path))
 
1100
                    if parent_text_key not in maybe_file_parent_keys:
 
1101
                        maybe_file_parent_keys.append(parent_text_key)
 
1102
            # Now we have the parents of this content
 
1103
            from breezy.annotate import Annotator
 
1104
            from .annotate import AnnotateProvider
 
1105
            annotate_provider = AnnotateProvider(
 
1106
                self.branch.repository._file_change_scanner)
 
1107
            annotator = Annotator(annotate_provider)
 
1108
 
 
1109
            from breezy.graph import Graph
 
1110
            graph = Graph(annotate_provider)
 
1111
            heads = graph.heads(maybe_file_parent_keys)
 
1112
            file_parent_keys = []
 
1113
            for key in maybe_file_parent_keys:
 
1114
                if key in heads:
 
1115
                    file_parent_keys.append(key)
 
1116
 
 
1117
            text = self.get_file_text(path)
 
1118
            this_key = (path, default_revision)
 
1119
            annotator.add_special_text(this_key, file_parent_keys, text)
 
1120
            annotations = [(key[-1], line)
 
1121
                           for key, line in annotator.annotate_flat(this_key)]
 
1122
            return annotations
 
1123
 
 
1124
    def _rename_one(self, from_rel, to_rel):
 
1125
        os.rename(self.abspath(from_rel), self.abspath(to_rel))
 
1126
 
 
1127
    def _build_checkout_with_index(self):
 
1128
        build_index_from_tree(
 
1129
            self.user_transport.local_abspath('.'),
 
1130
            self.control_transport.local_abspath("index"),
 
1131
            self.store,
 
1132
            None
 
1133
            if self.branch.head is None
 
1134
            else self.store[self.branch.head].tree,
 
1135
            honor_filemode=self._supports_executable())
 
1136
 
 
1137
    def reset_state(self, revision_ids=None):
 
1138
        """Reset the state of the working tree.
 
1139
 
 
1140
        This does a hard-reset to a last-known-good state. This is a way to
 
1141
        fix if something got corrupted (like the .git/index file)
 
1142
        """
 
1143
        with self.lock_tree_write():
 
1144
            if revision_ids is not None:
 
1145
                self.set_parent_ids(revision_ids)
 
1146
            self.index.clear()
 
1147
            self._index_dirty = True
 
1148
            if self.branch.head is not None:
 
1149
                for entry in self.store.iter_tree_contents(
 
1150
                        self.store[self.branch.head].tree):
 
1151
                    if not validate_path(entry.path):
 
1152
                        continue
 
1153
 
 
1154
                    if S_ISGITLINK(entry.mode):
 
1155
                        pass  # TODO(jelmer): record and return submodule paths
 
1156
                    else:
 
1157
                        # Let's at least try to use the working tree file:
 
1158
                        try:
 
1159
                            st = self._lstat(self.abspath(
 
1160
                                entry.path.decode('utf-8')))
 
1161
                        except OSError:
 
1162
                            # But if it doesn't exist, we'll make something up.
 
1163
                            obj = self.store[entry.sha]
 
1164
                            st = os.stat_result((entry.mode, 0, 0, 0,
 
1165
                                                 0, 0, len(
 
1166
                                                     obj.as_raw_string()), 0,
 
1167
                                                 0, 0))
 
1168
                    (index, subpath) = self._lookup_index(entry.path)
 
1169
                    index[subpath] = index_entry_from_stat(st, entry.sha, 0)
 
1170
 
 
1171
    def _update_git_tree(self, old_revision, new_revision, change_reporter=None,
 
1172
                         show_base=False):
 
1173
        basis_tree = self.revision_tree(old_revision)
 
1174
        if new_revision != old_revision:
 
1175
            with basis_tree.lock_read():
 
1176
                new_basis_tree = self.branch.basis_tree()
 
1177
                merge.merge_inner(
 
1178
                    self.branch,
 
1179
                    new_basis_tree,
 
1180
                    basis_tree,
 
1181
                    this_tree=self,
 
1182
                    change_reporter=change_reporter,
 
1183
                    show_base=show_base)
 
1184
 
 
1185
    def pull(self, source, overwrite=False, stop_revision=None,
 
1186
             change_reporter=None, possible_transports=None, local=False,
 
1187
             show_base=False):
 
1188
        with self.lock_write(), source.lock_read():
 
1189
            old_revision = self.branch.last_revision()
 
1190
            count = self.branch.pull(source, overwrite, stop_revision,
 
1191
                                     possible_transports=possible_transports,
 
1192
                                     local=local)
 
1193
            self._update_git_tree(
 
1194
                old_revision=old_revision,
 
1195
                new_revision=self.branch.last_revision(),
 
1196
                change_reporter=change_reporter,
 
1197
                show_base=show_base)
 
1198
            return count
 
1199
 
 
1200
    def add_reference(self, sub_tree):
 
1201
        """Add a TreeReference to the tree, pointing at sub_tree.
 
1202
 
 
1203
        :param sub_tree: subtree to add.
 
1204
        """
 
1205
        with self.lock_tree_write():
 
1206
            try:
 
1207
                sub_tree_path = self.relpath(sub_tree.basedir)
 
1208
            except errors.PathNotChild:
 
1209
                raise BadReferenceTarget(
 
1210
                    self, sub_tree, 'Target not inside tree.')
 
1211
 
 
1212
            self._add([sub_tree_path], [None], ['tree-reference'])
 
1213
 
 
1214
    def _read_submodule_head(self, path):
 
1215
        return read_submodule_head(self.abspath(path))
 
1216
 
 
1217
    def get_reference_revision(self, path):
 
1218
        hexsha = self._read_submodule_head(path)
 
1219
        if hexsha is None:
 
1220
            return _mod_revision.NULL_REVISION
 
1221
        return self.branch.lookup_foreign_revision_id(hexsha)
 
1222
 
 
1223
    def get_nested_tree(self, path):
 
1224
        return workingtree.WorkingTree.open(self.abspath(path))
 
1225
 
 
1226
    def _directory_is_tree_reference(self, relpath):
 
1227
        # as a special case, if a directory contains control files then
 
1228
        # it's a tree reference, except that the root of the tree is not
 
1229
        return relpath and osutils.lexists(self.abspath(relpath) + u"/.git")
 
1230
 
 
1231
    def extract(self, sub_path, format=None):
 
1232
        """Extract a subtree from this tree.
 
1233
 
 
1234
        A new branch will be created, relative to the path for this tree.
 
1235
        """
 
1236
        def mkdirs(path):
 
1237
            segments = osutils.splitpath(path)
 
1238
            transport = self.branch.controldir.root_transport
 
1239
            for name in segments:
 
1240
                transport = transport.clone(name)
 
1241
                transport.ensure_base()
 
1242
            return transport
 
1243
 
 
1244
        with self.lock_tree_write():
 
1245
            self.flush()
 
1246
            branch_transport = mkdirs(sub_path)
 
1247
            if format is None:
 
1248
                format = self.controldir.cloning_metadir()
 
1249
            branch_transport.ensure_base()
 
1250
            branch_bzrdir = format.initialize_on_transport(branch_transport)
 
1251
            try:
 
1252
                repo = branch_bzrdir.find_repository()
 
1253
            except errors.NoRepositoryPresent:
 
1254
                repo = branch_bzrdir.create_repository()
 
1255
            if not repo.supports_rich_root():
 
1256
                raise errors.RootNotRich()
 
1257
            new_branch = branch_bzrdir.create_branch()
 
1258
            new_branch.pull(self.branch)
 
1259
            for parent_id in self.get_parent_ids():
 
1260
                new_branch.fetch(self.branch, parent_id)
 
1261
            tree_transport = self.controldir.root_transport.clone(sub_path)
 
1262
            if tree_transport.base != branch_transport.base:
 
1263
                tree_bzrdir = format.initialize_on_transport(tree_transport)
 
1264
                tree_bzrdir.set_branch_reference(new_branch)
 
1265
            else:
 
1266
                tree_bzrdir = branch_bzrdir
 
1267
            wt = tree_bzrdir.create_workingtree(_mod_revision.NULL_REVISION)
 
1268
            wt.set_parent_ids(self.get_parent_ids())
 
1269
            return wt
 
1270
 
 
1271
    def _get_check_refs(self):
 
1272
        """Return the references needed to perform a check of this tree.
 
1273
 
 
1274
        The default implementation returns no refs, and is only suitable for
 
1275
        trees that have no local caching and can commit on ghosts at any time.
 
1276
 
 
1277
        :seealso: breezy.check for details about check_refs.
 
1278
        """
 
1279
        return []
 
1280
 
 
1281
    def copy_content_into(self, tree, revision_id=None):
 
1282
        """Copy the current content and user files of this tree into tree."""
 
1283
        with self.lock_read():
 
1284
            if revision_id is None:
 
1285
                merge.transform_tree(tree, self)
 
1286
            else:
 
1287
                # TODO now merge from tree.last_revision to revision (to
 
1288
                # preserve user local changes)
 
1289
                try:
 
1290
                    other_tree = self.revision_tree(revision_id)
 
1291
                except errors.NoSuchRevision:
 
1292
                    other_tree = self.branch.repository.revision_tree(
 
1293
                        revision_id)
 
1294
 
 
1295
                merge.transform_tree(tree, other_tree)
 
1296
                if revision_id == _mod_revision.NULL_REVISION:
 
1297
                    new_parents = []
 
1298
                else:
 
1299
                    new_parents = [revision_id]
 
1300
                tree.set_parent_ids(new_parents)
 
1301
 
 
1302
 
 
1303
class GitWorkingTreeFormat(workingtree.WorkingTreeFormat):
 
1304
 
 
1305
    _tree_class = GitWorkingTree
 
1306
 
 
1307
    supports_versioned_directories = False
 
1308
 
 
1309
    supports_setting_file_ids = False
 
1310
 
 
1311
    supports_store_uncommitted = False
 
1312
 
 
1313
    supports_leftmost_parent_id_as_ghost = False
 
1314
 
 
1315
    supports_righthand_parent_id_as_ghost = False
 
1316
 
 
1317
    requires_normalized_unicode_filenames = True
 
1318
 
 
1319
    supports_merge_modified = False
 
1320
 
 
1321
    ignore_filename = ".gitignore"
 
1322
 
 
1323
    @property
 
1324
    def _matchingcontroldir(self):
 
1325
        from .dir import LocalGitControlDirFormat
 
1326
        return LocalGitControlDirFormat()
 
1327
 
 
1328
    def get_format_description(self):
 
1329
        return "Git Working Tree"
 
1330
 
 
1331
    def initialize(self, a_controldir, revision_id=None, from_branch=None,
 
1332
                   accelerator_tree=None, hardlink=False):
 
1333
        """See WorkingTreeFormat.initialize()."""
 
1334
        if not isinstance(a_controldir, LocalGitDir):
 
1335
            raise errors.IncompatibleFormat(self, a_controldir)
 
1336
        branch = a_controldir.open_branch(nascent_ok=True)
 
1337
        if revision_id is not None:
 
1338
            branch.set_last_revision(revision_id)
 
1339
        wt = GitWorkingTree(
 
1340
            a_controldir, a_controldir.open_repository(), branch)
 
1341
        for hook in MutableTree.hooks['post_build_tree']:
 
1342
            hook(wt)
 
1343
        return wt