/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to workingtree.py

  • Committer: Jelmer Vernooij
  • Date: 2018-03-22 23:28:30 UTC
  • mto: (0.200.1883 work)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@jelmer.uk-20180322232830-fy9mychr0f8s7hn2
Various fixes for annotated tags and symrefs.

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