/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: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

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