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

Merge test-run support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
from dulwich.ignore import (
26
26
    IgnoreFilterManager,
27
27
    )
28
 
from dulwich.config import ConfigFile as GitConfigFile
29
28
from dulwich.file import GitFile, FileLocked
30
29
from dulwich.index import (
31
30
    Index,
32
31
    SHA1Writer,
33
32
    build_index_from_tree,
 
33
    changes_from_tree,
34
34
    index_entry_from_path,
35
35
    index_entry_from_stat,
 
36
    iter_fresh_entries,
36
37
    FLAG_STAGEMASK,
37
38
    read_submodule_head,
38
39
    validate_path,
42
43
    tree_lookup_path,
43
44
    )
44
45
from dulwich.objects import (
 
46
    Blob,
 
47
    Tree,
 
48
    S_IFGITLINK,
45
49
    S_ISGITLINK,
 
50
    ZERO_SHA,
 
51
    )
 
52
from dulwich.repo import (
 
53
    NotGitRepository,
 
54
    Repo as GitRepo,
46
55
    )
47
56
import os
48
57
import posixpath
 
58
import re
49
59
import stat
50
60
import sys
51
61
 
52
 
from .. import (
53
 
    branch as _mod_branch,
 
62
from ... import (
54
63
    conflicts as _mod_conflicts,
55
64
    errors,
56
65
    controldir as _mod_controldir,
63
72
    trace,
64
73
    transport as _mod_transport,
65
74
    tree,
66
 
    urlutils,
67
75
    workingtree,
68
76
    )
69
 
from ..decorators import (
 
77
from ...decorators import (
70
78
    only_raises,
71
79
    )
72
 
from ..mutabletree import (
 
80
from ...bzr import (
 
81
    inventory,
 
82
    )
 
83
from ...mutabletree import (
73
84
    BadReferenceTarget,
74
85
    MutableTree,
75
86
    )
76
 
from ..sixish import text_type
77
87
 
78
88
 
79
89
from .dir import (
83
93
    MutableGitIndexTree,
84
94
    )
85
95
from .mapping import (
 
96
    GitFileIdMap,
86
97
    mode_kind,
87
98
    )
88
99
 
89
 
 
90
 
class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree):
 
100
IGNORE_FILENAME = ".gitignore"
 
101
 
 
102
 
 
103
class GitWorkingTree(MutableGitIndexTree,workingtree.WorkingTree):
91
104
    """A Git working tree."""
92
105
 
93
106
    def __init__(self, controldir, repo, branch):
99
112
        self.store = self.repository._git.object_store
100
113
        self.mapping = self.repository.get_mapping()
101
114
        self._branch = branch
102
 
        self._transport = self.repository._git._controltransport
 
115
        self._transport = controldir.transport
103
116
        self._format = GitWorkingTreeFormat()
104
117
        self.index = None
105
118
        self._index_file = None
118
131
        self.index = Index(self.control_transport.local_abspath('index'))
119
132
        self._index_dirty = False
120
133
 
121
 
    def _get_submodule_index(self, relpath):
122
 
        if not isinstance(relpath, bytes):
123
 
            raise TypeError(relpath)
124
 
        try:
125
 
            info = self._submodule_info()[relpath]
126
 
        except KeyError:
127
 
            index_path = os.path.join(self.basedir, relpath.decode('utf-8'), '.git', 'index')
128
 
        else:
129
 
            index_path = self.control_transport.local_abspath(
130
 
                posixpath.join('modules', info[1].decode('utf-8'), 'index'))
131
 
        return Index(index_path)
132
 
 
133
134
    def lock_read(self):
134
135
        """Lock the repository for read operations.
135
136
 
145
146
        return lock.LogicalLockResult(self.unlock)
146
147
 
147
148
    def _lock_write_tree(self):
 
149
        # TODO(jelmer): Actually create index.lock
148
150
        if not self._lock_mode:
149
151
            self._lock_mode = 'w'
150
152
            self._lock_count = 1
151
153
            try:
152
 
                self._index_file = GitFile(
153
 
                    self.control_transport.local_abspath('index'), 'wb')
 
154
                self._index_file = GitFile(self.control_transport.local_abspath('index'), 'wb')
154
155
            except FileLocked:
155
156
                raise errors.LockContention('index')
156
157
            self._read_index()
157
158
        elif self._lock_mode == 'r':
158
159
            raise errors.ReadOnlyError(self)
159
160
        else:
160
 
            self._lock_count += 1
 
161
            self._lock_count +=1
161
162
 
162
163
    def lock_tree_write(self):
163
164
        self.branch.lock_read()
164
165
        try:
165
166
            self._lock_write_tree()
166
167
            return lock.LogicalLockResult(self.unlock)
167
 
        except BaseException:
 
168
        except:
168
169
            self.branch.unlock()
169
170
            raise
170
171
 
173
174
        try:
174
175
            self._lock_write_tree()
175
176
            return lock.LogicalLockResult(self.unlock)
176
 
        except BaseException:
 
177
        except:
177
178
            self.branch.unlock()
178
179
            raise
179
180
 
204
205
                    self._flush(self._index_file)
205
206
                    self._index_file.close()
206
207
                else:
207
 
                    # Something else already triggered a write of the index
208
 
                    # file by calling .flush()
 
208
                    # Somebody else already wrote the index file
 
209
                    # by calling .flush()
209
210
                    self._index_file.abort()
210
211
                self._index_file = None
211
212
            self._lock_mode = None
224
225
        else:
225
226
            self.case_sensitive = False
226
227
 
227
 
    def get_transform(self, pb=None):
228
 
        from ..transform import TreeTransform
229
 
        return TreeTransform(self, pb=pb)
230
 
 
231
228
    def merge_modified(self):
232
229
        return {}
233
230
 
239
236
 
240
237
    def _set_merges_from_parent_ids(self, rhs_parent_ids):
241
238
        try:
242
 
            merges = [self.branch.lookup_bzr_revision_id(
243
 
                revid)[0] for revid in rhs_parent_ids]
 
239
            merges = [self.branch.lookup_bzr_revision_id(revid)[0] for revid in rhs_parent_ids]
244
240
        except errors.NoSuchRevision as e:
245
241
            raise errors.GhostRevisionUnusableHere(e.revision)
246
242
        if merges:
247
 
            self.control_transport.put_bytes(
248
 
                'MERGE_HEAD', b'\n'.join(merges),
 
243
            self.control_transport.put_bytes('MERGE_HEAD', '\n'.join(merges),
249
244
                mode=self.controldir._get_file_mode())
250
245
        else:
251
246
            try:
266
261
            working tree. Any of these may be ghosts.
267
262
        """
268
263
        with self.lock_tree_write():
269
 
            self._check_parents_for_ghosts(
270
 
                revision_ids, allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
264
            self._check_parents_for_ghosts(revision_ids,
 
265
                allow_leftmost_as_ghost=allow_leftmost_as_ghost)
271
266
            for revision_id in revision_ids:
272
267
                _mod_revision.check_not_reserved_id(revision_id)
273
268
 
297
292
            pass
298
293
        else:
299
294
            for l in osutils.split_lines(merges_bytes):
300
 
                revision_id = l.rstrip(b'\n')
301
 
                parents.append(
302
 
                    self.branch.lookup_foreign_revision_id(revision_id))
 
295
                revision_id = l.rstrip('\n')
 
296
                parents.append(self.branch.lookup_foreign_revision_id(revision_id))
303
297
        return parents
304
298
 
305
299
    def check_state(self):
307
301
        pass
308
302
 
309
303
    def remove(self, files, verbose=False, to_file=None, keep_files=True,
310
 
               force=False):
 
304
        force=False):
311
305
        """Remove nominated files from the working tree metadata.
312
306
 
313
307
        :param files: File paths relative to the basedir.
315
309
        :param force: Delete files and directories, even if they are changed
316
310
            and even if the directories are not empty.
317
311
        """
318
 
        if not isinstance(files, list):
 
312
        if isinstance(files, basestring):
319
313
            files = [files]
320
314
 
321
315
        if to_file is None:
323
317
 
324
318
        def backup(file_to_backup):
325
319
            abs_path = self.abspath(file_to_backup)
326
 
            backup_name = self.controldir._available_backup_name(
327
 
                file_to_backup)
 
320
            backup_name = self.controldir._available_backup_name(file_to_backup)
328
321
            osutils.rename(abs_path, self.abspath(backup_name))
329
322
            return "removed %s (but kept a copy: %s)" % (
330
323
                file_to_backup, backup_name)
361
354
            files = list(all_files)
362
355
 
363
356
            if len(files) == 0:
364
 
                return  # nothing to do
 
357
                return # nothing to do
365
358
 
366
 
            # Sort needed to first handle directory content before the
367
 
            # directory
 
359
            # Sort needed to first handle directory content before the directory
368
360
            files.sort(reverse=True)
369
361
 
370
362
            # Bail out if we are going to delete files we shouldn't
371
363
            if not keep_files and not force:
372
 
                for change in self.iter_changes(
373
 
                        self.basis_tree(), include_unchanged=True,
374
 
                        require_versioned=False, want_unversioned=True,
375
 
                        specific_files=files):
376
 
                    if change.versioned[0] is False:
 
364
                for (file_id, path, content_change, versioned, parent_id, name,
 
365
                     kind, executable) in self.iter_changes(self.basis_tree(),
 
366
                         include_unchanged=True, require_versioned=False,
 
367
                         want_unversioned=True, specific_files=files):
 
368
                    if versioned[0] == False:
377
369
                        # The record is unknown or newly added
378
 
                        files_to_backup.append(change.path[1])
379
 
                        files_to_backup.extend(
380
 
                            osutils.parent_directories(change.path[1]))
381
 
                    elif (change.changed_content and (change.kind[1] is not None)
382
 
                            and osutils.is_inside_any(files, change.path[1])):
 
370
                        files_to_backup.append(path[1])
 
371
                        files_to_backup.extend(osutils.parent_directories(path[1]))
 
372
                    elif (content_change and (kind[1] is not None) and
 
373
                            osutils.is_inside_any(files, path[1])):
383
374
                        # Versioned and changed, but not deleted, and still
384
375
                        # in one of the dirs to be deleted.
385
 
                        files_to_backup.append(change.path[1])
386
 
                        files_to_backup.extend(
387
 
                            osutils.parent_directories(change.path[1]))
 
376
                        files_to_backup.append(path[1])
 
377
                        files_to_backup.extend(osutils.parent_directories(path[1]))
388
378
 
389
379
            for f in files:
390
380
                if f == '':
432
422
        # expand any symlinks in the directory part, while leaving the
433
423
        # filename alone
434
424
        # only expanding if symlinks are supported avoids windows path bugs
435
 
        if self.supports_symlinks():
 
425
        if osutils.has_symlinks():
436
426
            file_list = list(map(osutils.normalizepath, file_list))
437
427
 
438
428
        conflicts_related = set()
442
432
        added = []
443
433
        ignored = {}
444
434
        user_dirs = []
445
 
 
446
435
        def call_action(filepath, kind):
447
 
            if filepath == '':
448
 
                return
449
436
            if action is not None:
450
437
                parent_path = posixpath.dirname(filepath)
451
438
                parent_id = self.path2id(parent_path)
455
442
                    raise workingtree.SettingFileIdUnsupported()
456
443
 
457
444
        with self.lock_tree_write():
458
 
            for filepath in osutils.canonical_relpaths(
459
 
                    self.basedir, file_list):
 
445
            for filepath in osutils.canonical_relpaths(self.basedir, file_list):
460
446
                filepath, can_access = osutils.normalized_filename(filepath)
461
447
                if not can_access:
462
448
                    raise errors.InvalidNormalization(filepath)
464
450
                abspath = self.abspath(filepath)
465
451
                kind = osutils.file_kind(abspath)
466
452
                if kind in ("file", "symlink"):
467
 
                    (index, subpath) = self._lookup_index(
468
 
                        filepath.encode('utf-8'))
 
453
                    (index, subpath) = self._lookup_index(filepath.encode('utf-8'))
469
454
                    if subpath in index:
470
455
                        # Already present
471
456
                        continue
474
459
                        self._index_add_entry(filepath, kind)
475
460
                    added.append(filepath)
476
461
                elif kind == "directory":
477
 
                    (index, subpath) = self._lookup_index(
478
 
                        filepath.encode('utf-8'))
 
462
                    (index, subpath) = self._lookup_index(filepath.encode('utf-8'))
479
463
                    if subpath not in index:
480
464
                        call_action(filepath, kind)
481
465
                    if recurse:
486
470
                abs_user_dir = self.abspath(user_dir)
487
471
                if user_dir != '':
488
472
                    try:
489
 
                        transport = _mod_transport.get_transport_from_path(
490
 
                            abs_user_dir)
 
473
                        transport = _mod_transport.get_transport_from_path(abs_user_dir)
491
474
                        _mod_controldir.ControlDirFormat.find_format(transport)
492
475
                        subtree = True
493
476
                    except errors.NotBranchError:
502
485
 
503
486
                for name in os.listdir(abs_user_dir):
504
487
                    subp = os.path.join(user_dir, name)
505
 
                    if (self.is_control_filename(subp) or
506
 
                            self.mapping.is_special_file(subp)):
 
488
                    if self.is_control_filename(subp) or self.mapping.is_special_file(subp):
507
489
                        continue
508
490
                    ignore_glob = self.is_ignored(subp)
509
491
                    if ignore_glob is not None:
514
496
                    if kind == "directory":
515
497
                        user_dirs.append(subp)
516
498
                    else:
517
 
                        (index, subpath) = self._lookup_index(
518
 
                            subp.encode('utf-8'))
519
 
                        if subpath in index:
 
499
                        if subp in self.index:
520
500
                            # Already present
521
501
                            continue
522
502
                        if subp in conflicts_related:
523
503
                            continue
524
 
                        call_action(subp, kind)
 
504
                        call_action(filepath, kind)
525
505
                        if save:
526
506
                            self._index_add_entry(subp, kind)
527
507
                        added.append(subp)
530
510
    def has_filename(self, filename):
531
511
        return osutils.lexists(self.abspath(filename))
532
512
 
533
 
    def _iter_files_recursive(self, from_dir=None, include_dirs=False,
534
 
                              recurse_nested=False):
 
513
    def _iter_files_recursive(self, from_dir=None, include_dirs=False):
535
514
        if from_dir is None:
536
515
            from_dir = u""
537
 
        if not isinstance(from_dir, text_type):
538
 
            raise TypeError(from_dir)
539
 
        encoded_from_dir = self.abspath(from_dir).encode(osutils._fs_enc)
540
 
        for (dirpath, dirnames, filenames) in os.walk(encoded_from_dir):
541
 
            dir_relpath = dirpath[len(self.basedir):].strip(b"/")
542
 
            if self.controldir.is_control_filename(
543
 
                    dir_relpath.decode(osutils._fs_enc)):
 
516
        for (dirpath, dirnames, filenames) in os.walk(self.abspath(from_dir).encode(osutils._fs_enc)):
 
517
            dir_relpath = dirpath[len(self.basedir):].strip("/")
 
518
            if self.controldir.is_control_filename(dir_relpath):
544
519
                continue
545
520
            for name in list(dirnames):
546
 
                if self.controldir.is_control_filename(
547
 
                        name.decode(osutils._fs_enc)):
 
521
                if self.controldir.is_control_filename(name):
548
522
                    dirnames.remove(name)
549
523
                    continue
550
524
                relpath = os.path.join(dir_relpath, name)
551
 
                if not recurse_nested and self._directory_is_tree_reference(relpath.decode(osutils._fs_enc)):
552
 
                    dirnames.remove(name)
553
525
                if include_dirs:
554
526
                    try:
555
527
                        yield relpath.decode(osutils._fs_enc)
556
528
                    except UnicodeDecodeError:
557
529
                        raise errors.BadFilenameEncoding(
558
530
                            relpath, osutils._fs_enc)
559
 
                    if not self.is_versioned(relpath.decode(osutils._fs_enc)):
560
 
                        dirnames.remove(name)
 
531
                if not self._has_dir(relpath):
 
532
                    dirnames.remove(name)
561
533
            for name in filenames:
562
 
                if self.mapping.is_special_file(name):
563
 
                    continue
564
 
                if self.controldir.is_control_filename(
565
 
                        name.decode(osutils._fs_enc, 'replace')):
566
 
                    continue
567
 
                yp = os.path.join(dir_relpath, name)
568
 
                try:
569
 
                    yield yp.decode(osutils._fs_enc)
570
 
                except UnicodeDecodeError:
571
 
                    raise errors.BadFilenameEncoding(
572
 
                        yp, osutils._fs_enc)
 
534
                if not self.mapping.is_special_file(name):
 
535
                    yp = os.path.join(dir_relpath, name)
 
536
                    try:
 
537
                        yield yp.decode(osutils._fs_enc)
 
538
                    except UnicodeDecodeError:
 
539
                        raise errors.BadFilenameEncoding(
 
540
                            yp, osutils._fs_enc)
573
541
 
574
542
    def extras(self):
575
543
        """Yield all unversioned files in this WorkingTree.
576
544
        """
577
545
        with self.lock_read():
578
 
            index_paths = set(
579
 
                [p.decode('utf-8') for p, i in self._recurse_index_entries()])
580
 
            all_paths = set(self._iter_files_recursive(include_dirs=False))
581
 
            return iter(all_paths - index_paths)
 
546
            index_paths = set([p.decode('utf-8') for p, i in self._recurse_index_entries()])
 
547
            all_paths = set(self._iter_files_recursive(include_dirs=True))
 
548
            for p in (all_paths - index_paths):
 
549
                if not self._has_dir(p):
 
550
                    yield p
582
551
 
583
552
    def _gather_kinds(self, files, kinds):
584
553
        """See MutableTree._gather_kinds."""
587
556
                if kinds[pos] is None:
588
557
                    fullpath = osutils.normpath(self.abspath(f))
589
558
                    try:
590
 
                        kind = osutils.file_kind(fullpath)
 
559
                         kind = osutils.file_kind(fullpath)
591
560
                    except OSError as e:
592
561
                        if e.errno == errno.ENOENT:
593
562
                            raise errors.NoSuchFile(fullpath)
594
 
                    if f != '' and self._directory_is_tree_reference(f):
 
563
                    if kind == 'directory' and f != '' and os.path.exists(os.path.join(fullpath, '.git')):
595
564
                        kind = 'tree-reference'
596
565
                    kinds[pos] = kind
597
566
 
599
568
        if self._lock_mode != 'w':
600
569
            raise errors.NotWriteLocked(self)
601
570
        # TODO(jelmer): This shouldn't be writing in-place, but index.lock is
602
 
        # already in use and GitFile doesn't allow overriding the lock file
603
 
        # name :(
 
571
        # already in use and GitFile doesn't allow overriding the lock file name :(
604
572
        f = open(self.control_transport.local_abspath('index'), 'wb')
605
573
        # Note that _flush will close the file
606
574
        self._flush(f)
610
578
            shaf = SHA1Writer(f)
611
579
            write_index_dict(shaf, self.index)
612
580
            shaf.close()
613
 
        except BaseException:
 
581
        except:
614
582
            f.abort()
615
583
            raise
616
584
        self._index_dirty = False
617
585
 
618
 
    def get_file_mtime(self, path):
 
586
    def has_or_had_id(self, file_id):
 
587
        if self.has_id(file_id):
 
588
            return True
 
589
        if self.had_id(file_id):
 
590
            return True
 
591
        return False
 
592
 
 
593
    def had_id(self, file_id):
 
594
        path = self._basis_fileid_map.lookup_file_id(file_id)
 
595
        try:
 
596
            head = self.repository._git.head()
 
597
        except KeyError:
 
598
            # Assume no if basis is not accessible
 
599
            return False
 
600
        try:
 
601
            root_tree = self.store[head].tree
 
602
        except KeyError:
 
603
            return False
 
604
        try:
 
605
            tree_lookup_path(self.store.__getitem__, root_tree, path)
 
606
        except KeyError:
 
607
            return False
 
608
        else:
 
609
            return True
 
610
 
 
611
    def get_file_mtime(self, path, file_id=None):
619
612
        """See Tree.get_file_mtime."""
620
613
        try:
621
614
            return self._lstat(path).st_mtime
622
615
        except OSError as e:
623
 
            if e.errno == errno.ENOENT:
 
616
            (num, msg) = e
 
617
            if num == errno.ENOENT:
624
618
                raise errors.NoSuchFile(path)
625
619
            raise
626
620
 
634
628
            ignore_globs = set()
635
629
            ignore_globs.update(ignores.get_runtime_ignores())
636
630
            ignore_globs.update(ignores.get_user_ignores())
637
 
            self._global_ignoreglobster = globbing.ExceptionGlobster(
638
 
                ignore_globs)
 
631
            self._global_ignoreglobster = globbing.ExceptionGlobster(ignore_globs)
639
632
        match = self._global_ignoreglobster.match(filename)
640
633
        if match is not None:
641
634
            return match
642
635
        try:
643
636
            if self.kind(filename) == 'directory':
644
 
                filename += '/'
 
637
                filename += b'/'
645
638
        except errors.NoSuchFile:
646
639
            pass
647
 
        filename = filename.lstrip('/')
 
640
        filename = filename.lstrip(b'/')
648
641
        ignore_manager = self._get_ignore_manager()
649
642
        ps = list(ignore_manager.find_matching(filename))
650
643
        if not ps:
676
669
            raise errors.GhostRevisionUnusableHere(revid)
677
670
 
678
671
    def _reset_data(self):
679
 
        pass
 
672
        try:
 
673
            head = self.repository._git.head()
 
674
        except KeyError:
 
675
            self._basis_fileid_map = GitFileIdMap({}, self.mapping)
 
676
        else:
 
677
            self._basis_fileid_map = self.mapping.get_fileid_map(
 
678
                self.store.__getitem__, self.store[head].tree)
 
679
        self._fileid_map = self._basis_fileid_map.copy()
680
680
 
681
 
    def get_file_verifier(self, path, stat_value=None):
 
681
    def get_file_verifier(self, path, file_id=None, stat_value=None):
682
682
        with self.lock_read():
683
683
            (index, subpath) = self._lookup_index(path.encode('utf-8'))
684
684
            try:
688
688
                    return ("GIT", None)
689
689
                raise errors.NoSuchFile(path)
690
690
 
691
 
    def get_file_sha1(self, path, stat_value=None):
 
691
    def get_file_sha1(self, path, file_id=None, stat_value=None):
692
692
        with self.lock_read():
693
693
            if not self.is_versioned(path):
694
694
                raise errors.NoSuchFile(path)
696
696
            try:
697
697
                return osutils.sha_file_by_name(abspath)
698
698
            except OSError as e:
699
 
                if e.errno in (errno.EISDIR, errno.ENOENT):
 
699
                (num, msg) = e
 
700
                if num in (errno.EISDIR, errno.ENOENT):
700
701
                    return None
701
702
                raise
702
703
 
710
711
    def _is_executable_from_path_and_stat_from_basis(self, path, stat_result):
711
712
        return self.basis_tree().is_executable(path)
712
713
 
713
 
    def stored_kind(self, path):
 
714
    def stored_kind(self, path, file_id=None):
714
715
        with self.lock_read():
715
 
            encoded_path = path.encode('utf-8')
716
 
            (index, subpath) = self._lookup_index(encoded_path)
 
716
            (index, subpath) = self._lookup_index(path.encode('utf-8'))
717
717
            try:
718
718
                return mode_kind(index[subpath].mode)
719
719
            except KeyError:
720
720
                # Maybe it's a directory?
721
 
                if self._has_dir(encoded_path):
 
721
                if self._has_dir(path):
722
722
                    return "directory"
723
723
                raise errors.NoSuchFile(path)
724
724
 
726
726
        return os.lstat(self.abspath(path))
727
727
 
728
728
    def _live_entry(self, path):
729
 
        encoded_path = self.abspath(path.decode('utf-8')).encode(
730
 
            osutils._fs_enc)
731
 
        return index_entry_from_path(encoded_path)
 
729
        return index_entry_from_path(self.abspath(path.decode('utf-8')).encode(osutils._fs_enc))
732
730
 
733
 
    def is_executable(self, path):
 
731
    def is_executable(self, path, file_id=None):
734
732
        with self.lock_read():
735
 
            if self._supports_executable():
 
733
            if getattr(self, "_supports_executable", osutils.supports_executable)():
736
734
                mode = self._lstat(path).st_mode
737
735
            else:
738
736
                (index, subpath) = self._lookup_index(path.encode('utf-8'))
743
741
            return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
744
742
 
745
743
    def _is_executable_from_path_and_stat(self, path, stat_result):
746
 
        if self._supports_executable():
 
744
        if getattr(self, "_supports_executable", osutils.supports_executable)():
747
745
            return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
748
746
        else:
749
 
            return self._is_executable_from_path_and_stat_from_basis(
750
 
                path, stat_result)
 
747
            return self._is_executable_from_path_and_stat_from_basis(path, stat_result)
751
748
 
752
 
    def list_files(self, include_root=False, from_dir=None, recursive=True,
753
 
                   recurse_nested=False):
754
 
        if from_dir is None or from_dir == '.':
 
749
    def list_files(self, include_root=False, from_dir=None, recursive=True):
 
750
        if from_dir is None:
755
751
            from_dir = u""
756
752
        dir_ids = {}
757
753
        fk_entries = {'directory': tree.TreeDirectory,
761
757
        with self.lock_read():
762
758
            root_ie = self._get_dir_ie(u"", None)
763
759
            if include_root and not from_dir:
764
 
                yield "", "V", root_ie.kind, root_ie
 
760
                yield "", "V", root_ie.kind, root_ie.file_id, root_ie
765
761
            dir_ids[u""] = root_ie.file_id
766
762
            if recursive:
767
 
                path_iterator = sorted(
768
 
                    self._iter_files_recursive(
769
 
                        from_dir, include_dirs=True,
770
 
                        recurse_nested=recurse_nested))
 
763
                path_iterator = sorted(self._iter_files_recursive(from_dir, include_dirs=True))
771
764
            else:
772
 
                encoded_from_dir = self.abspath(from_dir).encode(
773
 
                    osutils._fs_enc)
774
 
                path_iterator = sorted(
775
 
                    [os.path.join(from_dir, name.decode(osutils._fs_enc))
776
 
                     for name in os.listdir(encoded_from_dir)
777
 
                     if not self.controldir.is_control_filename(
778
 
                         name.decode(osutils._fs_enc)) and
779
 
                     not self.mapping.is_special_file(
780
 
                         name.decode(osutils._fs_enc))])
 
765
                path_iterator = sorted([os.path.join(from_dir, name.decode(osutils._fs_enc)) for name in
 
766
                    os.listdir(self.abspath(from_dir).encode(osutils._fs_enc)) if not self.controldir.is_control_filename(name)
 
767
                    and not self.mapping.is_special_file(name)])
781
768
            for path in path_iterator:
782
769
                try:
783
770
                    encoded_path = path.encode("utf-8")
791
778
                    value = None
792
779
                kind = self.kind(path)
793
780
                parent, name = posixpath.split(path)
794
 
                for dir_path, dir_ie in self._add_missing_parent_ids(
795
 
                        parent, dir_ids):
 
781
                for dir_path, dir_ie in self._add_missing_parent_ids(parent, dir_ids):
796
782
                    pass
797
 
                if kind == 'tree-reference' and recurse_nested:
798
 
                    ie = self._get_dir_ie(path, self.path2id(path))
799
 
                    yield (posixpath.relpath(path, from_dir), 'V', 'directory',
800
 
                           ie)
801
 
                    continue
802
 
                if kind == 'directory':
 
783
                if kind in ('directory', 'tree-reference'):
803
784
                    if path != from_dir:
804
 
                        if self._has_dir(encoded_path):
 
785
                        if self._has_dir(path):
805
786
                            ie = self._get_dir_ie(path, self.path2id(path))
806
787
                            status = "V"
 
788
                            file_id = ie.file_id
807
789
                        elif self.is_ignored(path):
808
790
                            status = "I"
809
791
                            ie = fk_entries[kind]()
 
792
                            file_id = None
810
793
                        else:
811
794
                            status = "?"
812
795
                            ie = fk_entries[kind]()
813
 
                        yield (posixpath.relpath(path, from_dir), status, kind,
814
 
                               ie)
 
796
                            file_id = None
 
797
                        yield posixpath.relpath(path, from_dir), status, kind, file_id, ie
815
798
                    continue
816
799
                if value is not None:
817
800
                    ie = self._get_file_ie(name, path, value, dir_ids[parent])
818
 
                    yield (posixpath.relpath(path, from_dir), "V", ie.kind, ie)
 
801
                    yield posixpath.relpath(path, from_dir), "V", ie.kind, ie.file_id, ie
819
802
                else:
820
803
                    ie = fk_entries[kind]()
821
 
                    yield (posixpath.relpath(path, from_dir),
822
 
                           ("I" if self.is_ignored(path) else "?"), kind, ie)
 
804
                    yield posixpath.relpath(path, from_dir), ("I" if self.is_ignored(path) else "?"), kind, None, ie
823
805
 
824
806
    def all_file_ids(self):
825
 
        raise errors.UnsupportedOperation(self.all_file_ids, self)
 
807
        with self.lock_read():
 
808
            ids = {u"": self.path2id("")}
 
809
            for path in self.index:
 
810
                if self.mapping.is_special_file(path):
 
811
                    continue
 
812
                path = path.decode("utf-8")
 
813
                parent = posixpath.dirname(path).strip("/")
 
814
                for e in self._add_missing_parent_ids(parent, ids):
 
815
                    pass
 
816
                ids[path] = self.path2id(path)
 
817
            return set(ids.values())
826
818
 
827
819
    def all_versioned_paths(self):
828
820
        with self.lock_read():
839
831
                    paths.add(path)
840
832
            return paths
841
833
 
842
 
    def iter_child_entries(self, path):
 
834
    def iter_child_entries(self, path, file_id=None):
843
835
        encoded_path = path.encode('utf-8')
844
836
        with self.lock_read():
845
837
            parent_id = self.path2id(path)
846
838
            found_any = False
 
839
            seen_children = set()
847
840
            for item_path, value in self.index.iteritems():
848
 
                decoded_item_path = item_path.decode('utf-8')
849
841
                if self.mapping.is_special_file(item_path):
850
842
                    continue
851
 
                if not osutils.is_inside(path, decoded_item_path):
 
843
                if not osutils.is_inside(encoded_path, item_path):
852
844
                    continue
853
845
                found_any = True
854
 
                subpath = posixpath.relpath(decoded_item_path, path)
855
 
                if '/' in subpath:
856
 
                    dirname = subpath.split('/', 1)[0]
857
 
                    file_ie = self._get_dir_ie(
858
 
                        posixpath.join(path, dirname), parent_id)
 
846
                subpath = posixpath.relpath(item_path, encoded_path)
 
847
                if b'/' in subpath:
 
848
                    dirname = subpath.split(b'/', 1)[0]
 
849
                    file_ie = self._get_dir_ie(posixpath.join(path, dirname), parent_id)
859
850
                else:
860
 
                    (unused_parent, name) = posixpath.split(decoded_item_path)
 
851
                    (parent, name) = posixpath.split(item_path)
861
852
                    file_ie = self._get_file_ie(
862
 
                        name, decoded_item_path, value, parent_id)
 
853
                            name.decode('utf-8'),
 
854
                            item_path.decode('utf-8'), value, parent_id)
863
855
                yield file_ie
864
856
            if not found_any and path != u'':
865
857
                raise errors.NoSuchFile(path)
869
861
            conflicts = _mod_conflicts.ConflictList()
870
862
            for item_path, value in self.index.iteritems():
871
863
                if value.flags & FLAG_STAGEMASK:
872
 
                    conflicts.append(_mod_conflicts.TextConflict(
873
 
                        item_path.decode('utf-8')))
 
864
                    conflicts.append(_mod_conflicts.TextConflict(item_path.decode('utf-8')))
874
865
            return conflicts
875
866
 
876
867
    def set_conflicts(self, conflicts):
891
882
        if conflicted:
892
883
            self.index[path] = (value[:9] + (value[9] | FLAG_STAGEMASK, ))
893
884
        else:
894
 
            self.index[path] = (value[:9] + (value[9] & ~ FLAG_STAGEMASK, ))
 
885
            self.index[path] = (value[:9] + (value[9] &~ FLAG_STAGEMASK, ))
895
886
 
896
887
    def add_conflicts(self, new_conflicts):
897
888
        with self.lock_tree_write():
898
889
            for conflict in new_conflicts:
899
 
                if conflict.typestring in ('text conflict',
900
 
                                           'contents conflict'):
 
890
                if conflict.typestring in ('text conflict', 'contents conflict'):
901
891
                    try:
902
 
                        self._set_conflicted(
903
 
                            conflict.path.encode('utf-8'), True)
 
892
                        self._set_conflicted(conflict.path.encode('utf-8'), True)
904
893
                    except KeyError:
905
 
                        raise errors.UnsupportedOperation(
906
 
                            self.add_conflicts, self)
 
894
                        raise errors.UnsupportedOperation(self.add_conflicts, self)
907
895
                else:
908
896
                    raise errors.UnsupportedOperation(self.add_conflicts, self)
909
897
 
933
921
            current_disk = next(disk_iterator)
934
922
            disk_finished = False
935
923
        except OSError as e:
936
 
            if not (e.errno == errno.ENOENT
937
 
                    or (sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
 
924
            if not (e.errno == errno.ENOENT or
 
925
                (sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
938
926
                raise
939
927
            current_disk = None
940
928
            disk_finished = True
953
941
                    cur_disk_dir_content) = ((None, None), None)
954
942
            if not disk_finished:
955
943
                # strip out .bzr dirs
956
 
                if (cur_disk_dir_path_from_top[top_strip_len:] == ''
957
 
                        and len(cur_disk_dir_content) > 0):
 
944
                if (cur_disk_dir_path_from_top[top_strip_len:] == '' and
 
945
                    len(cur_disk_dir_content) > 0):
958
946
                    # osutils.walkdirs can be made nicer -
959
947
                    # yield the path-from-prefix rather than the pathjoined
960
948
                    # value.
961
949
                    bzrdir_loc = bisect_left(cur_disk_dir_content,
962
 
                                             ('.git', '.git'))
963
 
                    if (bzrdir_loc < len(cur_disk_dir_content) and
964
 
                        self.controldir.is_control_filename(
 
950
                        ('.git', '.git'))
 
951
                    if (bzrdir_loc < len(cur_disk_dir_content)
 
952
                        and self.controldir.is_control_filename(
965
953
                            cur_disk_dir_content[bzrdir_loc][0])):
966
954
                        # we dont yield the contents of, or, .bzr itself.
967
955
                        del cur_disk_dir_content[bzrdir_loc]
972
960
                # everything is missing
973
961
                direction = -1
974
962
            else:
975
 
                direction = ((current_inv[0][0] > cur_disk_dir_relpath)
976
 
                             - (current_inv[0][0] < cur_disk_dir_relpath))
 
963
                direction = cmp(current_inv[0][0], cur_disk_dir_relpath)
977
964
            if direction > 0:
978
965
                # disk is before inventory - unknown
979
966
                dirblock = [(relpath, basename, kind, stat, None, None) for
980
 
                            relpath, basename, kind, stat, top_path in
981
 
                            cur_disk_dir_content]
 
967
                    relpath, basename, kind, stat, top_path in
 
968
                    cur_disk_dir_content]
982
969
                yield (cur_disk_dir_relpath, None), dirblock
983
970
                try:
984
971
                    current_disk = next(disk_iterator)
987
974
            elif direction < 0:
988
975
                # inventory is before disk - missing.
989
976
                dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
990
 
                            for relpath, basename, dkind, stat, fileid, kind in
991
 
                            current_inv[1]]
 
977
                    for relpath, basename, dkind, stat, fileid, kind in
 
978
                    current_inv[1]]
992
979
                yield (current_inv[0][0], current_inv[0][1]), dirblock
993
980
                try:
994
981
                    current_inv = next(inventory_iterator)
999
986
                # merge the inventory and disk data together
1000
987
                dirblock = []
1001
988
                for relpath, subiterator in itertools.groupby(sorted(
1002
 
                        current_inv[1] + cur_disk_dir_content,
1003
 
                        key=operator.itemgetter(0)), operator.itemgetter(1)):
 
989
                    current_inv[1] + cur_disk_dir_content,
 
990
                    key=operator.itemgetter(0)), operator.itemgetter(1)):
1004
991
                    path_elements = list(subiterator)
1005
992
                    if len(path_elements) == 2:
1006
993
                        inv_row, disk_row = path_elements
1007
994
                        # versioned, present file
1008
995
                        dirblock.append((inv_row[0],
1009
 
                                         inv_row[1], disk_row[2],
1010
 
                                         disk_row[3], inv_row[4],
1011
 
                                         inv_row[5]))
 
996
                            inv_row[1], disk_row[2],
 
997
                            disk_row[3], inv_row[4],
 
998
                            inv_row[5]))
1012
999
                    elif len(path_elements[0]) == 5:
1013
1000
                        # unknown disk file
1014
 
                        dirblock.append(
1015
 
                            (path_elements[0][0], path_elements[0][1],
1016
 
                                path_elements[0][2], path_elements[0][3],
1017
 
                                None, None))
 
1001
                        dirblock.append((path_elements[0][0],
 
1002
                            path_elements[0][1], path_elements[0][2],
 
1003
                            path_elements[0][3], None, None))
1018
1004
                    elif len(path_elements[0]) == 6:
1019
1005
                        # versioned, absent file.
1020
 
                        dirblock.append(
1021
 
                            (path_elements[0][0], path_elements[0][1],
1022
 
                                'unknown', None, path_elements[0][4],
1023
 
                                path_elements[0][5]))
 
1006
                        dirblock.append((path_elements[0][0],
 
1007
                            path_elements[0][1], 'unknown', None,
 
1008
                            path_elements[0][4], path_elements[0][5]))
1024
1009
                    else:
1025
1010
                        raise NotImplementedError('unreachable code')
1026
1011
                yield current_inv[0], dirblock
1033
1018
                except StopIteration:
1034
1019
                    disk_finished = True
1035
1020
 
1036
 
    def _walkdirs(self, prefix=u""):
1037
 
        if prefix != u"":
1038
 
            prefix += u"/"
 
1021
    def _walkdirs(self, prefix=""):
 
1022
        if prefix != "":
 
1023
            prefix += "/"
1039
1024
        prefix = prefix.encode('utf-8')
1040
1025
        per_dir = defaultdict(set)
1041
 
        if prefix == b"":
1042
 
            per_dir[(u'', self.path2id(''))] = set()
1043
 
 
 
1026
        if prefix == "":
 
1027
            per_dir[('', self.get_root_id())] = set()
1044
1028
        def add_entry(path, kind):
1045
 
            if path == b'' or not path.startswith(prefix):
 
1029
            if path == '' or not path.startswith(prefix):
1046
1030
                return
1047
1031
            (dirname, child_name) = posixpath.split(path)
1048
1032
            add_entry(dirname, 'directory')
1052
1036
                raise ValueError(value)
1053
1037
            per_dir[(dirname, dir_file_id)].add(
1054
1038
                (path.decode("utf-8"), child_name.decode("utf-8"),
1055
 
                 kind, None,
1056
 
                 self.path2id(path.decode("utf-8")),
1057
 
                 kind))
 
1039
                kind, None,
 
1040
                self.path2id(path.decode("utf-8")),
 
1041
                kind))
1058
1042
        with self.lock_read():
1059
1043
            for path, value in self.index.iteritems():
1060
1044
                if self.mapping.is_special_file(path):
1073
1057
    def apply_inventory_delta(self, changes):
1074
1058
        for (old_path, new_path, file_id, ie) in changes:
1075
1059
            if old_path is not None:
1076
 
                (index, old_subpath) = self._lookup_index(
1077
 
                    old_path.encode('utf-8'))
 
1060
                (index, old_subpath) = self._lookup_index(old_path.encode('utf-8'))
1078
1061
                try:
1079
1062
                    self._index_del_entry(index, old_subpath)
1080
1063
                except KeyError:
1084
1067
            if new_path is not None and ie.kind != 'directory':
1085
1068
                if ie.kind == 'tree-reference':
1086
1069
                    self._index_add_entry(
1087
 
                        new_path, ie.kind,
1088
 
                        reference_revision=ie.reference_revision)
 
1070
                            new_path, ie.kind,
 
1071
                            reference_revision=ie.reference_revision)
1089
1072
                else:
1090
1073
                    self._index_add_entry(new_path, ie.kind)
1091
1074
        self.flush()
1092
1075
 
1093
 
    def annotate_iter(self, path,
 
1076
    def annotate_iter(self, path, file_id=None,
1094
1077
                      default_revision=_mod_revision.CURRENT_REVISION):
1095
1078
        """See Tree.annotate_iter
1096
1079
 
1108
1091
                    parent_tree = self.revision_tree(parent_id)
1109
1092
                except errors.NoSuchRevisionInTree:
1110
1093
                    parent_tree = self.branch.repository.revision_tree(
1111
 
                        parent_id)
 
1094
                            parent_id)
1112
1095
                with parent_tree.lock_read():
1113
 
                    # TODO(jelmer): Use rename/copy tracker to find path name
1114
 
                    # in parent
 
1096
                    # TODO(jelmer): Use rename/copy tracker to find path name in parent
1115
1097
                    parent_path = path
1116
1098
                    try:
1117
1099
                        kind = parent_tree.kind(parent_path)
1118
1100
                    except errors.NoSuchFile:
1119
1101
                        continue
1120
1102
                    if kind != 'file':
1121
 
                        # Note: this is slightly unnecessary, because symlinks
1122
 
                        # and directories have a "text" which is the empty
1123
 
                        # text, and we know that won't mess up annotations. But
1124
 
                        # it seems cleaner
 
1103
                        # Note: this is slightly unnecessary, because symlinks and
 
1104
                        # directories have a "text" which is the empty text, and we
 
1105
                        # know that won't mess up annotations. But it seems cleaner
1125
1106
                        continue
1126
1107
                    parent_text_key = (
1127
1108
                        parent_path,
1128
1109
                        parent_tree.get_file_revision(parent_path))
1129
1110
                    if parent_text_key not in maybe_file_parent_keys:
1130
1111
                        maybe_file_parent_keys.append(parent_text_key)
1131
 
            # Now we have the parents of this content
1132
 
            from breezy.annotate import Annotator
1133
 
            from .annotate import AnnotateProvider
1134
 
            annotate_provider = AnnotateProvider(
1135
 
                self.branch.repository._file_change_scanner)
1136
 
            annotator = Annotator(annotate_provider)
1137
 
 
1138
 
            from breezy.graph import Graph
1139
 
            graph = Graph(annotate_provider)
 
1112
            graph = self.branch.repository.get_file_graph()
1140
1113
            heads = graph.heads(maybe_file_parent_keys)
1141
1114
            file_parent_keys = []
1142
1115
            for key in maybe_file_parent_keys:
1143
1116
                if key in heads:
1144
1117
                    file_parent_keys.append(key)
1145
1118
 
 
1119
            # Now we have the parents of this content
 
1120
            from breezy.annotate import Annotator
 
1121
            from .annotate import AnnotateProvider
 
1122
            annotator = Annotator(AnnotateProvider(
 
1123
                self.branch.repository._file_change_scanner))
1146
1124
            text = self.get_file_text(path)
1147
1125
            this_key = (path, default_revision)
1148
1126
            annotator.add_special_text(this_key, file_parent_keys, text)
1158
1136
            self.user_transport.local_abspath('.'),
1159
1137
            self.control_transport.local_abspath("index"),
1160
1138
            self.store,
1161
 
            None
1162
 
            if self.branch.head is None
1163
 
            else self.store[self.branch.head].tree,
1164
 
            honor_filemode=self._supports_executable())
 
1139
            None if self.branch.head is None else self.store[self.branch.head].tree)
1165
1140
 
1166
1141
    def reset_state(self, revision_ids=None):
1167
1142
        """Reset the state of the working tree.
1175
1150
            self.index.clear()
1176
1151
            self._index_dirty = True
1177
1152
            if self.branch.head is not None:
1178
 
                for entry in self.store.iter_tree_contents(
1179
 
                        self.store[self.branch.head].tree):
 
1153
                for entry in self.store.iter_tree_contents(self.store[self.branch.head].tree):
1180
1154
                    if not validate_path(entry.path):
1181
1155
                        continue
1182
1156
 
1183
1157
                    if S_ISGITLINK(entry.mode):
1184
 
                        pass  # TODO(jelmer): record and return submodule paths
 
1158
                        pass # TODO(jelmer): record and return submodule paths
1185
1159
                    else:
1186
1160
                        # Let's at least try to use the working tree file:
1187
1161
                        try:
1188
 
                            st = self._lstat(self.abspath(
1189
 
                                entry.path.decode('utf-8')))
 
1162
                            st = self._lstat(self.abspath(entry.path))
1190
1163
                        except OSError:
1191
1164
                            # But if it doesn't exist, we'll make something up.
1192
1165
                            obj = self.store[entry.sha]
1193
1166
                            st = os.stat_result((entry.mode, 0, 0, 0,
1194
 
                                                 0, 0, len(
1195
 
                                                     obj.as_raw_string()), 0,
1196
 
                                                 0, 0))
 
1167
                                  0, 0, len(obj.as_raw_string()), 0,
 
1168
                                  0, 0))
1197
1169
                    (index, subpath) = self._lookup_index(entry.path)
1198
1170
                    index[subpath] = index_entry_from_stat(st, entry.sha, 0)
1199
1171
 
1200
 
    def _update_git_tree(self, old_revision, new_revision, change_reporter=None,
1201
 
                         show_base=False):
1202
 
        basis_tree = self.revision_tree(old_revision)
1203
 
        if new_revision != old_revision:
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
1172
    def pull(self, source, overwrite=False, stop_revision=None,
1215
1173
             change_reporter=None, possible_transports=None, local=False,
1216
 
             show_base=False, tag_selector=None):
 
1174
             show_base=False):
1217
1175
        with self.lock_write(), source.lock_read():
1218
1176
            old_revision = self.branch.last_revision()
 
1177
            basis_tree = self.basis_tree()
1219
1178
            count = self.branch.pull(source, overwrite, stop_revision,
1220
1179
                                     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)
 
1180
                                     local=local)
 
1181
            new_revision = self.branch.last_revision()
 
1182
            if new_revision != old_revision:
 
1183
                with basis_tree.lock_read():
 
1184
                    new_basis_tree = self.branch.basis_tree()
 
1185
                    merge.merge_inner(
 
1186
                                self.branch,
 
1187
                                new_basis_tree,
 
1188
                                basis_tree,
 
1189
                                this_tree=self,
 
1190
                                change_reporter=change_reporter,
 
1191
                                show_base=show_base)
1227
1192
            return count
1228
1193
 
1229
1194
    def add_reference(self, sub_tree):
1236
1201
                sub_tree_path = self.relpath(sub_tree.basedir)
1237
1202
            except errors.PathNotChild:
1238
1203
                raise BadReferenceTarget(
1239
 
                    self, sub_tree, 'Target not inside tree.')
 
1204
                        self, sub_tree, 'Target not inside tree.')
1240
1205
 
1241
1206
            self._add([sub_tree_path], [None], ['tree-reference'])
1242
1207
 
1243
1208
    def _read_submodule_head(self, path):
1244
1209
        return read_submodule_head(self.abspath(path))
1245
1210
 
1246
 
    def get_reference_revision(self, path, branch=None):
 
1211
    def get_reference_revision(self, path, file_id=None):
1247
1212
        hexsha = self._read_submodule_head(path)
1248
1213
        if hexsha is None:
1249
1214
            return _mod_revision.NULL_REVISION
1250
1215
        return self.branch.lookup_foreign_revision_id(hexsha)
1251
1216
 
1252
 
    def get_nested_tree(self, path):
 
1217
    def get_nested_tree(self, path, file_id=None):
1253
1218
        return workingtree.WorkingTree.open(self.abspath(path))
1254
1219
 
1255
1220
    def _directory_is_tree_reference(self, relpath):
1257
1222
        # it's a tree reference, except that the root of the tree is not
1258
1223
        return relpath and osutils.lexists(self.abspath(relpath) + u"/.git")
1259
1224
 
1260
 
    def extract(self, sub_path, format=None):
 
1225
    def extract(self, sub_path, file_id=None, format=None):
1261
1226
        """Extract a subtree from this tree.
1262
1227
 
1263
1228
        A new branch will be created, relative to the path for this tree.
1319
1284
                    other_tree = self.revision_tree(revision_id)
1320
1285
                except errors.NoSuchRevision:
1321
1286
                    other_tree = self.branch.repository.revision_tree(
1322
 
                        revision_id)
 
1287
                            revision_id)
1323
1288
 
1324
1289
                merge.transform_tree(tree, other_tree)
1325
1290
                if revision_id == _mod_revision.NULL_REVISION:
1328
1293
                    new_parents = [revision_id]
1329
1294
                tree.set_parent_ids(new_parents)
1330
1295
 
1331
 
    def reference_parent(self, path, possible_transports=None):
1332
 
        remote_url = self.get_reference_info(path)
1333
 
        if remote_url is None:
1334
 
            trace.warning("Unable to find submodule info for %s", path)
1335
 
            return None
1336
 
        return _mod_branch.Branch.open(remote_url, possible_transports=possible_transports)
1337
 
 
1338
 
    def get_reference_info(self, path):
1339
 
        submodule_info = self._submodule_info()
1340
 
        info = submodule_info.get(path.encode('utf-8'))
1341
 
        if info is None:
1342
 
            return None
1343
 
        return info[0].decode('utf-8')
1344
 
 
1345
 
    def set_reference_info(self, tree_path, branch_location):
1346
 
        path = self.abspath('.gitmodules')
1347
 
        try:
1348
 
            config = GitConfigFile.from_path(path)
1349
 
        except EnvironmentError as e:
1350
 
            if e.errno == errno.ENOENT:
1351
 
                config = GitConfigFile()
1352
 
            else:
1353
 
                raise
1354
 
        section = (b'submodule', tree_path.encode('utf-8'))
1355
 
        if branch_location is None:
1356
 
            try:
1357
 
                del config[section]
1358
 
            except KeyError:
1359
 
                pass
1360
 
        else:
1361
 
            branch_location = urlutils.join(
1362
 
                urlutils.strip_segment_parameters(self.branch.user_url),
1363
 
                branch_location)
1364
 
            config.set(
1365
 
                section,
1366
 
                b'path', tree_path.encode('utf-8'))
1367
 
            config.set(
1368
 
                section,
1369
 
                b'url', branch_location.encode('utf-8'))
1370
 
        config.write_to_path(path)
1371
 
        self.add('.gitmodules')
1372
 
 
1373
1296
 
1374
1297
class GitWorkingTreeFormat(workingtree.WorkingTreeFormat):
1375
1298
 
1389
1312
 
1390
1313
    supports_merge_modified = False
1391
1314
 
1392
 
    ignore_filename = ".gitignore"
1393
 
 
1394
1315
    @property
1395
1316
    def _matchingcontroldir(self):
1396
1317
        from .dir import LocalGitControlDirFormat
1408
1329
        if revision_id is not None:
1409
1330
            branch.set_last_revision(revision_id)
1410
1331
        wt = GitWorkingTree(
1411
 
            a_controldir, a_controldir.open_repository(), branch)
 
1332
                a_controldir, a_controldir.open_repository(), branch)
1412
1333
        for hook in MutableTree.hooks['post_build_tree']:
1413
1334
            hook(wt)
1414
1335
        return wt