/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: Martin
  • Date: 2019-06-16 19:53:27 UTC
  • mto: This revision was merged to the branch mainline in revision 7349.
  • Revision ID: gzlist@googlemail.com-20190616195327-awvhgbjo9g6mkt57
Relocate the bzr log file out of $HOME

Now under $XDG_CACHE_HOME on nix and %LOCALAPPDATA% on windows.

Setting $BRZ_HOME will override the cache location, to simplify test
isolation, and $BRZ_LOG is still the final word.

Drive-by fix various docs around bzr/brz spelling.

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