/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/git/tree.py

  • Committer: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-06-01 21:57:00 UTC
  • mfrom: (7490.39.3 move-launchpad)
  • Revision ID: breezy.the.bot@gmail.com-20200601215700-joxuzo6w172gq74v
Move launchpad hoster support to the launchpad plugin.

Merged from https://code.launchpad.net/~jelmer/brz/move-launchpad/+merge/384931

Show diffs side-by-side

added added

removed removed

Lines of Context:
26
26
    parse_submodules,
27
27
    ConfigFile as GitConfigFile,
28
28
    )
29
 
from dulwich.diff_tree import tree_changes, RenameDetector
 
29
from dulwich.diff_tree import tree_changes
30
30
from dulwich.errors import NotTreeError
31
31
from dulwich.index import (
32
32
    blob_from_path_and_stat,
66
66
    )
67
67
 
68
68
from .mapping import (
69
 
    encode_git_path,
70
 
    decode_git_path,
71
69
    mode_is_executable,
72
70
    mode_kind,
73
71
    default_mapping,
76
74
    TransportObjectStore,
77
75
    TransportRepo,
78
76
    )
79
 
from ..bzr.inventorytree import InventoryTreeChange
80
77
 
81
78
 
82
79
class GitTreeDirectory(_mod_tree.TreeDirectory):
83
80
 
84
 
    __slots__ = ['file_id', 'name', 'parent_id']
 
81
    __slots__ = ['file_id', 'name', 'parent_id', 'children']
85
82
 
86
83
    def __init__(self, file_id, name, parent_id):
87
84
        self.file_id = file_id
88
85
        self.name = name
89
86
        self.parent_id = parent_id
 
87
        # TODO(jelmer)
 
88
        self.children = {}
90
89
 
91
90
    @property
92
91
    def kind(self):
114
113
 
115
114
class GitTreeFile(_mod_tree.TreeFile):
116
115
 
117
 
    __slots__ = ['file_id', 'name', 'parent_id', 'text_size',
118
 
                 'executable', 'git_sha1']
 
116
    __slots__ = ['file_id', 'name', 'parent_id', 'text_size', 'text_sha1',
 
117
                 'executable']
119
118
 
120
119
    def __init__(self, file_id, name, parent_id, text_size=None,
121
 
                 git_sha1=None, executable=None):
 
120
                 text_sha1=None, executable=None):
122
121
        self.file_id = file_id
123
122
        self.name = name
124
123
        self.parent_id = parent_id
125
124
        self.text_size = text_size
126
 
        self.git_sha1 = git_sha1
 
125
        self.text_sha1 = text_sha1
127
126
        self.executable = executable
128
127
 
129
128
    @property
135
134
                self.file_id == other.file_id and
136
135
                self.name == other.name and
137
136
                self.parent_id == other.parent_id and
138
 
                self.git_sha1 == other.git_sha1 and
 
137
                self.text_sha1 == other.text_sha1 and
139
138
                self.text_size == other.text_size and
140
139
                self.executable == other.executable)
141
140
 
142
141
    def __repr__(self):
143
142
        return ("%s(file_id=%r, name=%r, parent_id=%r, text_size=%r, "
144
 
                "git_sha1=%r, executable=%r)") % (
 
143
                "text_sha1=%r, executable=%r)") % (
145
144
            type(self).__name__, self.file_id, self.name, self.parent_id,
146
 
            self.text_size, self.git_sha1, self.executable)
 
145
            self.text_size, self.text_sha1, self.executable)
147
146
 
148
147
    def copy(self):
149
148
        ret = self.__class__(
150
149
            self.file_id, self.name, self.parent_id)
151
 
        ret.git_sha1 = self.git_sha1
 
150
        ret.text_sha1 = self.text_sha1
152
151
        ret.text_size = self.text_size
153
152
        ret.executable = self.executable
154
153
        return ret
256
255
    return path
257
256
 
258
257
 
259
 
class GitTree(_mod_tree.Tree):
260
 
 
261
 
    def iter_git_objects(self):
262
 
        """Iterate over all the objects in the tree.
263
 
 
264
 
        :return :Yields tuples with (path, sha, mode)
265
 
        """
266
 
        raise NotImplementedError(self.iter_git_objects)
267
 
 
268
 
    def git_snapshot(self, want_unversioned=False):
269
 
        """Snapshot a tree, and return tree object.
270
 
 
271
 
        :return: Tree sha and set of extras
272
 
        """
273
 
        raise NotImplementedError(self.snapshot)
274
 
 
275
 
    def preview_transform(self, pb=None):
276
 
        from .transform import GitTransformPreview
277
 
        return GitTransformPreview(self, pb=pb)
278
 
 
279
 
    def find_related_paths_across_trees(self, paths, trees=[],
280
 
                                        require_versioned=True):
281
 
        if paths is None:
282
 
            return None
283
 
        if require_versioned:
284
 
            trees = [self] + (trees if trees is not None else [])
285
 
            unversioned = set()
286
 
            for p in paths:
287
 
                for t in trees:
288
 
                    if t.is_versioned(p):
289
 
                        break
290
 
                else:
291
 
                    unversioned.add(p)
292
 
            if unversioned:
293
 
                raise errors.PathsNotVersionedError(unversioned)
294
 
        return filter(self.is_versioned, paths)
295
 
 
296
 
    def _submodule_info(self):
297
 
        if self._submodules is None:
298
 
            try:
299
 
                with self.get_file('.gitmodules') as f:
300
 
                    config = GitConfigFile.from_file(f)
301
 
                    self._submodules = {
302
 
                        path: (url, section)
303
 
                        for path, url, section in parse_submodules(config)}
304
 
            except errors.NoSuchFile:
305
 
                self._submodules = {}
306
 
        return self._submodules
307
 
 
308
 
 
309
 
class GitRevisionTree(revisiontree.RevisionTree, GitTree):
 
258
class GitRevisionTree(revisiontree.RevisionTree):
310
259
    """Revision tree implementation based on Git objects."""
311
260
 
312
261
    def __init__(self, repository, revision_id):
328
277
                raise errors.NoSuchRevision(repository, revision_id)
329
278
            self.tree = commit.tree
330
279
 
331
 
    def git_snapshot(self, want_unversioned=False):
332
 
        return self.tree, set()
 
280
    def _submodule_info(self):
 
281
        if self._submodules is None:
 
282
            try:
 
283
                with self.get_file('.gitmodules') as f:
 
284
                    config = GitConfigFile.from_file(f)
 
285
                    self._submodules = {
 
286
                        path: (url, section)
 
287
                        for path, url, section in parse_submodules(config)}
 
288
            except errors.NoSuchFile:
 
289
                self._submodules = {}
 
290
        return self._submodules
333
291
 
334
292
    def _get_submodule_repository(self, relpath):
335
293
        if not isinstance(relpath, bytes):
338
296
            info = self._submodule_info()[relpath]
339
297
        except KeyError:
340
298
            nested_repo_transport = self._repository.controldir.user_transport.clone(
341
 
                decode_git_path(relpath))
 
299
                relpath.decode('utf-8'))
342
300
        else:
343
301
            nested_repo_transport = self._repository.controldir.control_transport.clone(
344
 
                posixpath.join('modules', decode_git_path(info[1])))
 
302
                posixpath.join('modules', info[1].decode('utf-8')))
345
303
        nested_controldir = _mod_controldir.ControlDir.open_from_transport(
346
304
            nested_repo_transport)
347
305
        return nested_controldir.find_repository()
350
308
        return self._get_submodule_repository(relpath)._git.object_store
351
309
 
352
310
    def get_nested_tree(self, path):
353
 
        encoded_path = encode_git_path(path)
 
311
        encoded_path = path.encode('utf-8')
354
312
        nested_repo = self._get_submodule_repository(encoded_path)
355
313
        ref_rev = self.get_reference_revision(path)
356
314
        return nested_repo.revision_tree(ref_rev)
363
321
        if self.commit_id == ZERO_SHA:
364
322
            return NULL_REVISION
365
323
        (unused_path, commit_id) = change_scanner.find_last_change_revision(
366
 
            encode_git_path(path), self.commit_id)
 
324
            path.encode('utf-8'), self.commit_id)
367
325
        return self._repository.lookup_foreign_revision_id(
368
326
            commit_id, self.mapping)
369
327
 
410
368
            tree = store[tree_id]
411
369
            for name, mode, hexsha in tree.items():
412
370
                subpath = posixpath.join(path, name)
413
 
                ret.add(decode_git_path(subpath))
 
371
                ret.add(subpath.decode('utf-8'))
414
372
                if stat.S_ISDIR(mode):
415
373
                    todo.append((store, subpath, hexsha))
416
374
        return ret
419
377
        if self.tree is None:
420
378
            raise errors.NoSuchFile(path)
421
379
 
422
 
        encoded_path = encode_git_path(path)
 
380
        encoded_path = path.encode('utf-8')
423
381
        parts = encoded_path.split(b'/')
424
382
        hexsha = self.tree
425
383
        store = self.store
461
419
        else:
462
420
            return True
463
421
 
 
422
    def _submodule_info(self):
 
423
        if self._submodules is None:
 
424
            try:
 
425
                with self.get_file('.gitmodules') as f:
 
426
                    config = GitConfigFile.from_file(f)
 
427
                    self._submodules = {
 
428
                        path: (url, section)
 
429
                        for path, url, section in parse_submodules(config)}
 
430
            except errors.NoSuchFile:
 
431
                self._submodules = {}
 
432
        return self._submodules
 
433
 
464
434
    def list_files(self, include_root=False, from_dir=None, recursive=True,
465
435
                   recurse_nested=False):
466
436
        if self.tree is None:
474
444
            parent_path = posixpath.dirname(from_dir)
475
445
            parent_id = self.mapping.generate_file_id(parent_path)
476
446
            if mode_kind(mode) == 'directory':
477
 
                root_ie = self._get_dir_ie(encode_git_path(from_dir), parent_id)
 
447
                root_ie = self._get_dir_ie(from_dir.encode("utf-8"), parent_id)
478
448
            else:
479
449
                root_ie = self._get_file_ie(
480
 
                    store, encode_git_path(from_dir),
 
450
                    store, from_dir.encode("utf-8"),
481
451
                    posixpath.basename(from_dir), mode, hexsha)
482
452
        if include_root:
483
453
            yield (from_dir, "V", root_ie.kind, root_ie)
484
454
        todo = []
485
455
        if root_ie.kind == 'directory':
486
 
            todo.append((store, encode_git_path(from_dir),
 
456
            todo.append((store, from_dir.encode("utf-8"),
487
457
                         b"", hexsha, root_ie.file_id))
488
458
        while todo:
489
459
            (store, path, relpath, hexsha, parent_id) = todo.pop()
506
476
                else:
507
477
                    ie = self._get_file_ie(
508
478
                        store, child_path, name, mode, hexsha, parent_id)
509
 
                yield (decode_git_path(child_relpath), "V", ie.kind, ie)
 
479
                yield (child_relpath.decode('utf-8'), "V", ie.kind, ie)
510
480
 
511
481
    def _get_file_ie(self, store, path, name, mode, hexsha, parent_id):
512
482
        if not isinstance(path, bytes):
514
484
        if not isinstance(name, bytes):
515
485
            raise TypeError(name)
516
486
        kind = mode_kind(mode)
517
 
        path = decode_git_path(path)
518
 
        name = decode_git_path(name)
 
487
        path = path.decode('utf-8')
 
488
        name = name.decode("utf-8")
519
489
        file_id = self.mapping.generate_file_id(path)
520
490
        ie = entry_factory[kind](file_id, name, parent_id)
521
491
        if kind == 'symlink':
522
 
            ie.symlink_target = decode_git_path(store[hexsha].data)
 
492
            ie.symlink_target = store[hexsha].data.decode('utf-8')
523
493
        elif kind == 'tree-reference':
524
494
            ie.reference_revision = self.mapping.revision_id_foreign_to_bzr(
525
495
                hexsha)
526
496
        else:
527
 
            ie.git_sha1 = hexsha
528
 
            ie.text_size = None
 
497
            data = store[hexsha].data
 
498
            ie.text_sha1 = osutils.sha_string(data)
 
499
            ie.text_size = len(data)
529
500
            ie.executable = mode_is_executable(mode)
530
501
        return ie
531
502
 
532
503
    def _get_dir_ie(self, path, parent_id):
533
 
        path = decode_git_path(path)
 
504
        path = path.decode('utf-8')
534
505
        file_id = self.mapping.generate_file_id(path)
535
506
        return GitTreeDirectory(file_id, posixpath.basename(path), parent_id)
536
507
 
540
511
        if mode is not None and not stat.S_ISDIR(mode):
541
512
            return
542
513
 
543
 
        encoded_path = encode_git_path(path)
 
514
        encoded_path = path.encode('utf-8')
544
515
        file_id = self.path2id(path)
545
516
        tree = store[tree_sha]
546
517
        for name, mode, hexsha in tree.iteritems():
561
532
            if specific_files in ([""], []):
562
533
                specific_files = None
563
534
            else:
564
 
                specific_files = set([encode_git_path(p)
 
535
                specific_files = set([p.encode('utf-8')
565
536
                                      for p in specific_files])
566
537
        todo = deque([(self.store, b"", self.tree, self.path2id(''))])
567
538
        if specific_files is None or u"" in specific_files:
574
545
                if self.mapping.is_special_file(name):
575
546
                    continue
576
547
                child_path = posixpath.join(path, name)
577
 
                child_path_decoded = decode_git_path(child_path)
 
548
                child_path_decoded = child_path.decode('utf-8')
578
549
                if recurse_nested and S_ISGITLINK(mode):
579
550
                    mode = stat.S_IFDIR
580
551
                    store = self._get_submodule_store(child_path)
633
604
        """See RevisionTree.get_symlink_target."""
634
605
        (store, mode, hexsha) = self._lookup_path(path)
635
606
        if stat.S_ISLNK(mode):
636
 
            return decode_git_path(store[hexsha].data)
 
607
            return store[hexsha].data.decode('utf-8')
637
608
        else:
638
609
            return None
639
610
 
642
613
        (store, mode, hexsha) = self._lookup_path(path)
643
614
        if S_ISGITLINK(mode):
644
615
            try:
645
 
                nested_repo = self._get_submodule_repository(encode_git_path(path))
 
616
                nested_repo = self._get_submodule_repository(path.encode('utf-8'))
646
617
            except errors.NotBranchError:
647
618
                return self.mapping.revision_id_foreign_to_bzr(hexsha)
648
619
            else:
668
639
            return (kind, len(contents), executable,
669
640
                    osutils.sha_string(contents))
670
641
        elif kind == 'symlink':
671
 
            return (kind, None, None, decode_git_path(store[hexsha].data))
 
642
            return (kind, None, None, store[hexsha].data.decode('utf-8'))
672
643
        elif kind == 'tree-reference':
673
 
            nested_repo = self._get_submodule_repository(encode_git_path(path))
 
644
            nested_repo = self._get_submodule_repository(path.encode('utf-8'))
674
645
            return (kind, None, None,
675
646
                    nested_repo.lookup_foreign_revision_id(hexsha))
676
647
        else:
677
648
            return (kind, None, None, None)
678
649
 
 
650
    def find_related_paths_across_trees(self, paths, trees=[],
 
651
                                        require_versioned=True):
 
652
        if paths is None:
 
653
            return None
 
654
        if require_versioned:
 
655
            trees = [self] + (trees if trees is not None else [])
 
656
            unversioned = set()
 
657
            for p in paths:
 
658
                for t in trees:
 
659
                    if t.is_versioned(p):
 
660
                        break
 
661
                else:
 
662
                    unversioned.add(p)
 
663
            if unversioned:
 
664
                raise errors.PathsNotVersionedError(unversioned)
 
665
        return filter(self.is_versioned, paths)
 
666
 
679
667
    def _iter_tree_contents(self, include_trees=False):
680
668
        if self.tree is None:
681
669
            return iter([])
709
697
    def walkdirs(self, prefix=u""):
710
698
        (store, mode, hexsha) = self._lookup_path(prefix)
711
699
        todo = deque(
712
 
            [(store, encode_git_path(prefix), hexsha)])
 
700
            [(store, prefix.encode('utf-8'), hexsha, self.path2id(prefix))])
713
701
        while todo:
714
 
            store, path, tree_sha = todo.popleft()
715
 
            path_decoded = decode_git_path(path)
 
702
            store, path, tree_sha, parent_id = todo.popleft()
 
703
            path_decoded = path.decode('utf-8')
716
704
            tree = store[tree_sha]
717
705
            children = []
718
706
            for name, mode, hexsha in tree.iteritems():
719
707
                if self.mapping.is_special_file(name):
720
708
                    continue
721
709
                child_path = posixpath.join(path, name)
 
710
                file_id = self.path2id(child_path.decode('utf-8'))
722
711
                if stat.S_ISDIR(mode):
723
 
                    todo.append((store, child_path, hexsha))
 
712
                    todo.append((store, child_path, hexsha, file_id))
724
713
                children.append(
725
 
                    (decode_git_path(child_path), decode_git_path(name),
 
714
                    (child_path.decode('utf-8'), name.decode('utf-8'),
726
715
                        mode_kind(mode), None,
727
 
                        mode_kind(mode)))
728
 
            yield path_decoded, children
 
716
                        file_id, mode_kind(mode)))
 
717
            yield (path_decoded, parent_id), children
729
718
 
730
719
 
731
720
def tree_delta_from_git_changes(changes, mappings,
732
721
                                specific_files=None,
733
722
                                require_versioned=False, include_root=False,
734
 
                                source_extras=None, target_extras=None):
 
723
                                target_extras=None):
735
724
    """Create a TreeDelta from two git trees.
736
725
 
737
726
    source and target are iterators over tuples with:
740
729
    (old_mapping, new_mapping) = mappings
741
730
    if target_extras is None:
742
731
        target_extras = set()
743
 
    if source_extras is None:
744
 
        source_extras = set()
745
732
    ret = delta.TreeDelta()
746
733
    added = []
747
 
    for (change_type, old, new) in changes:
748
 
        (oldpath, oldmode, oldsha) = old
749
 
        (newpath, newmode, newsha) = new
 
734
    for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
750
735
        if newpath == b'' and not include_root:
751
736
            continue
752
 
        copied = (change_type == 'copy')
753
737
        if oldpath is not None:
754
 
            oldpath_decoded = decode_git_path(oldpath)
 
738
            oldpath_decoded = oldpath.decode('utf-8')
755
739
        else:
756
740
            oldpath_decoded = None
757
741
        if newpath is not None:
758
 
            newpath_decoded = decode_git_path(newpath)
 
742
            newpath_decoded = newpath.decode('utf-8')
759
743
        else:
760
744
            newpath_decoded = None
761
745
        if not (specific_files is None or
767
751
                        specific_files, newpath_decoded))):
768
752
            continue
769
753
 
770
 
        if oldpath is None:
 
754
        if oldpath_decoded is None:
 
755
            fileid = new_mapping.generate_file_id(newpath_decoded)
771
756
            oldexe = None
772
757
            oldkind = None
773
758
            oldname = None
774
759
            oldparent = None
775
760
            oldversioned = False
776
761
        else:
777
 
            oldversioned = (oldpath not in source_extras)
 
762
            oldversioned = True
778
763
            if oldmode:
779
764
                oldexe = mode_is_executable(oldmode)
780
765
                oldkind = mode_kind(oldmode)
781
766
            else:
782
767
                oldexe = False
783
768
                oldkind = None
784
 
            if oldpath == b'':
 
769
            if oldpath_decoded == u'':
785
770
                oldparent = None
786
771
                oldname = u''
787
772
            else:
788
773
                (oldparentpath, oldname) = osutils.split(oldpath_decoded)
789
774
                oldparent = old_mapping.generate_file_id(oldparentpath)
790
 
        if newpath is None:
 
775
            fileid = old_mapping.generate_file_id(oldpath_decoded)
 
776
        if newpath_decoded is None:
791
777
            newexe = None
792
778
            newkind = None
793
779
            newname = None
794
780
            newparent = None
795
781
            newversioned = False
796
782
        else:
797
 
            newversioned = (newpath not in target_extras)
 
783
            newversioned = (newpath_decoded not in target_extras)
798
784
            if newmode:
799
785
                newexe = mode_is_executable(newmode)
800
786
                newkind = mode_kind(newmode)
807
793
            else:
808
794
                newparentpath, newname = osutils.split(newpath_decoded)
809
795
                newparent = new_mapping.generate_file_id(newparentpath)
810
 
        if oldversioned and not copied:
811
 
            fileid = old_mapping.generate_file_id(oldpath_decoded)
812
 
        elif newversioned:
813
 
            fileid = new_mapping.generate_file_id(newpath_decoded)
814
 
        else:
815
 
            fileid = None
816
796
        if old_mapping.is_special_file(oldpath):
817
797
            oldpath = None
818
798
        if new_mapping.is_special_file(newpath):
819
799
            newpath = None
820
800
        if oldpath is None and newpath is None:
821
801
            continue
822
 
        change = InventoryTreeChange(
 
802
        change = _mod_tree.TreeChange(
823
803
            fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
824
804
            (oldversioned, newversioned),
825
805
            (oldparent, newparent), (oldname, newname),
826
 
            (oldkind, newkind), (oldexe, newexe),
827
 
            copied=copied)
828
 
        if newpath is not None and not newversioned and newkind != 'directory':
829
 
            change.file_id = None
830
 
            ret.unversioned.append(change)
831
 
        elif change_type == 'add':
 
806
            (oldkind, newkind), (oldexe, newexe))
 
807
        if oldpath is None:
832
808
            added.append((newpath, newkind))
833
809
        elif newpath is None or newmode == 0:
834
810
            ret.removed.append(change)
835
 
        elif change_type == 'delete':
836
 
            ret.removed.append(change)
837
 
        elif change_type == 'copy':
838
 
            if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
839
 
                continue
840
 
            ret.copied.append(change)
841
 
        elif change_type == 'rename':
842
 
            if stat.S_ISDIR(oldmode) and stat.S_ISDIR(newmode):
843
 
                continue
 
811
        elif oldpath != newpath:
844
812
            ret.renamed.append(change)
845
813
        elif mode_kind(oldmode) != mode_kind(newmode):
846
814
            ret.kind_changed.append(change)
860
828
    for path, kind in added:
861
829
        if kind == 'directory' and path not in implicit_dirs:
862
830
            continue
863
 
        path_decoded = decode_git_path(path)
 
831
        path_decoded = osutils.normalized_filename(path)[0]
864
832
        parent_path, basename = osutils.split(path_decoded)
865
833
        parent_id = new_mapping.generate_file_id(parent_path)
866
 
        file_id = new_mapping.generate_file_id(path_decoded)
867
 
        ret.added.append(
868
 
            InventoryTreeChange(
869
 
                file_id, (None, path_decoded), True,
870
 
                (False, True),
871
 
                (None, parent_id),
 
834
        if path in target_extras:
 
835
            ret.unversioned.append(_mod_tree.TreeChange(
 
836
                None, (None, path_decoded),
 
837
                True, (False, False), (None, parent_id),
872
838
                (None, basename), (None, kind), (None, False)))
 
839
        else:
 
840
            file_id = new_mapping.generate_file_id(path_decoded)
 
841
            ret.added.append(
 
842
                _mod_tree.TreeChange(
 
843
                    file_id, (None, path_decoded), True,
 
844
                    (False, True),
 
845
                    (None, parent_id),
 
846
                    (None, basename), (None, kind), (None, False)))
873
847
 
874
848
    return ret
875
849
 
876
850
 
877
851
def changes_from_git_changes(changes, mapping, specific_files=None,
878
 
                             include_unchanged=False, source_extras=None,
879
 
                             target_extras=None):
 
852
                             include_unchanged=False, target_extras=None):
880
853
    """Create a iter_changes-like generator from a git stream.
881
854
 
882
855
    source and target are iterators over tuples with:
884
857
    """
885
858
    if target_extras is None:
886
859
        target_extras = set()
887
 
    if source_extras is None:
888
 
        source_extras = set()
889
 
    for (change_type, old, new) in changes:
890
 
        if change_type == 'unchanged' and not include_unchanged:
891
 
            continue
892
 
        (oldpath, oldmode, oldsha) = old
893
 
        (newpath, newmode, newsha) = new
 
860
    for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
894
861
        if oldpath is not None:
895
 
            oldpath_decoded = decode_git_path(oldpath)
 
862
            oldpath_decoded = oldpath.decode('utf-8')
896
863
        else:
897
864
            oldpath_decoded = None
898
865
        if newpath is not None:
899
 
            newpath_decoded = decode_git_path(newpath)
 
866
            newpath_decoded = newpath.decode('utf-8')
900
867
        else:
901
868
            newpath_decoded = None
902
869
        if not (specific_files is None or
911
878
            continue
912
879
        if newpath is not None and mapping.is_special_file(newpath):
913
880
            continue
914
 
        if oldpath is None:
 
881
        if oldpath_decoded is None:
 
882
            fileid = mapping.generate_file_id(newpath_decoded)
915
883
            oldexe = None
916
884
            oldkind = None
917
885
            oldname = None
918
886
            oldparent = None
919
887
            oldversioned = False
920
888
        else:
921
 
            oldversioned = (oldpath not in source_extras)
 
889
            oldversioned = True
922
890
            if oldmode:
923
891
                oldexe = mode_is_executable(oldmode)
924
892
                oldkind = mode_kind(oldmode)
931
899
            else:
932
900
                (oldparentpath, oldname) = osutils.split(oldpath_decoded)
933
901
                oldparent = mapping.generate_file_id(oldparentpath)
934
 
        if newpath is None:
 
902
            fileid = mapping.generate_file_id(oldpath_decoded)
 
903
        if newpath_decoded is None:
935
904
            newexe = None
936
905
            newkind = None
937
906
            newname = None
938
907
            newparent = None
939
908
            newversioned = False
940
909
        else:
941
 
            newversioned = (newpath not in target_extras)
 
910
            newversioned = (newpath_decoded not in target_extras)
942
911
            if newmode:
943
912
                newexe = mode_is_executable(newmode)
944
913
                newkind = mode_kind(newmode)
952
921
                newparentpath, newname = osutils.split(newpath_decoded)
953
922
                newparent = mapping.generate_file_id(newparentpath)
954
923
        if (not include_unchanged and
955
 
                oldkind == 'directory' and newkind == 'directory' and
 
924
            oldkind == 'directory' and newkind == 'directory' and
956
925
                oldpath_decoded == newpath_decoded):
957
926
            continue
958
 
        if oldversioned and change_type != 'copy':
959
 
            fileid = mapping.generate_file_id(oldpath_decoded)
960
 
        elif newversioned:
961
 
            fileid = mapping.generate_file_id(newpath_decoded)
962
 
        else:
963
 
            fileid = None
964
 
        if oldkind == 'directory' and newkind == 'directory':
965
 
            modified = False
966
 
        else:
967
 
            modified = (oldsha != newsha) or (oldmode != newmode)
968
 
        yield InventoryTreeChange(
969
 
            fileid, (oldpath_decoded, newpath_decoded),
970
 
            modified,
 
927
        yield _mod_tree.TreeChange(
 
928
            fileid, (oldpath_decoded, newpath_decoded), (oldsha != newsha),
971
929
            (oldversioned, newversioned),
972
930
            (oldparent, newparent), (oldname, newname),
973
 
            (oldkind, newkind), (oldexe, newexe),
974
 
            copied=(change_type == 'copy'))
 
931
            (oldkind, newkind), (oldexe, newexe))
975
932
 
976
933
 
977
934
class InterGitTrees(_mod_tree.InterTree):
981
938
    _matching_to_tree_format = None
982
939
    _test_mutable_trees_to_test_trees = None
983
940
 
984
 
    def __init__(self, source, target):
985
 
        super(InterGitTrees, self).__init__(source, target)
986
 
        if self.source.store == self.target.store:
987
 
            self.store = self.source.store
988
 
        else:
989
 
            self.store = OverlayObjectStore(
990
 
                [self.source.store, self.target.store])
991
 
        self.rename_detector = RenameDetector(self.store)
992
 
 
993
941
    @classmethod
994
942
    def is_compatible(cls, source, target):
995
 
        return isinstance(source, GitTree) and isinstance(target, GitTree)
 
943
        return (isinstance(source, GitRevisionTree) and
 
944
                isinstance(target, GitRevisionTree))
996
945
 
997
946
    def compare(self, want_unchanged=False, specific_files=None,
998
947
                extra_trees=None, require_versioned=False, include_root=False,
999
948
                want_unversioned=False):
1000
949
        with self.lock_read():
1001
 
            changes, source_extras, target_extras = self._iter_git_changes(
 
950
            changes, target_extras = self._iter_git_changes(
1002
951
                want_unchanged=want_unchanged,
1003
952
                require_versioned=require_versioned,
1004
953
                specific_files=specific_files,
1007
956
            return tree_delta_from_git_changes(
1008
957
                changes, (self.source.mapping, self.target.mapping),
1009
958
                specific_files=specific_files,
1010
 
                include_root=include_root,
1011
 
                source_extras=source_extras, target_extras=target_extras)
 
959
                include_root=include_root, target_extras=target_extras)
1012
960
 
1013
961
    def iter_changes(self, include_unchanged=False, specific_files=None,
1014
962
                     pb=None, extra_trees=[], require_versioned=True,
1015
963
                     want_unversioned=False):
1016
964
        with self.lock_read():
1017
 
            changes, source_extras, target_extras = self._iter_git_changes(
 
965
            changes, target_extras = self._iter_git_changes(
1018
966
                want_unchanged=include_unchanged,
1019
967
                require_versioned=require_versioned,
1020
968
                specific_files=specific_files,
1024
972
                changes, self.target.mapping,
1025
973
                specific_files=specific_files,
1026
974
                include_unchanged=include_unchanged,
1027
 
                source_extras=source_extras,
1028
975
                target_extras=target_extras)
1029
976
 
1030
977
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
1031
978
                          require_versioned=False, extra_trees=None,
1032
 
                          want_unversioned=False, include_trees=True):
1033
 
        trees = [self.source]
1034
 
        if extra_trees is not None:
1035
 
            trees.extend(extra_trees)
1036
 
        if specific_files is not None:
1037
 
            specific_files = self.target.find_related_paths_across_trees(
1038
 
                specific_files, trees,
1039
 
                require_versioned=require_versioned)
1040
 
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
1041
 
        with self.lock_read():
1042
 
            from_tree_sha, from_extras = self.source.git_snapshot(
1043
 
                want_unversioned=want_unversioned)
1044
 
            to_tree_sha, to_extras = self.target.git_snapshot(
1045
 
                want_unversioned=want_unversioned)
1046
 
            changes = tree_changes(
1047
 
                self.store, from_tree_sha, to_tree_sha,
1048
 
                include_trees=include_trees,
1049
 
                rename_detector=self.rename_detector,
1050
 
                want_unchanged=want_unchanged, change_type_same=True)
1051
 
            return changes, from_extras, to_extras
 
979
                          want_unversioned=False):
 
980
        raise NotImplementedError(self._iter_git_changes)
1052
981
 
1053
982
    def find_target_path(self, path, recurse='none'):
1054
983
        ret = self.find_target_paths([path], recurse=recurse)
1061
990
    def find_target_paths(self, paths, recurse='none'):
1062
991
        paths = set(paths)
1063
992
        ret = {}
1064
 
        changes = self._iter_git_changes(
1065
 
            specific_files=paths, include_trees=False)[0]
1066
 
        for (change_type, old, new) in changes:
1067
 
            if old[0] is None:
1068
 
                continue
1069
 
            oldpath = decode_git_path(old[0])
 
993
        changes = self._iter_git_changes(specific_files=paths)[0]
 
994
        for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
1070
995
            if oldpath in paths:
1071
 
                ret[oldpath] = decode_git_path(new[0]) if new[0] else None
 
996
                ret[oldpath] = newpath
1072
997
        for path in paths:
1073
998
            if path not in ret:
1074
999
                if self.source.has_filename(path):
1083
1008
    def find_source_paths(self, paths, recurse='none'):
1084
1009
        paths = set(paths)
1085
1010
        ret = {}
1086
 
        changes = self._iter_git_changes(
1087
 
            specific_files=paths, include_trees=False)[0]
1088
 
        for (change_type, old, new) in changes:
1089
 
            if new[0] is None:
1090
 
                continue
1091
 
            newpath = decode_git_path(new[0])
 
1011
        changes = self._iter_git_changes(specific_files=paths)[0]
 
1012
        for (oldpath, newpath), (oldmode, newmode), (oldsha, newsha) in changes:
1092
1013
            if newpath in paths:
1093
 
                ret[newpath] = decode_git_path(old[0]) if old[0] else None
 
1014
                ret[newpath] = oldpath
1094
1015
        for path in paths:
1095
1016
            if path not in ret:
1096
1017
                if self.target.has_filename(path):
1103
1024
        return ret
1104
1025
 
1105
1026
 
1106
 
_mod_tree.InterTree.register_optimiser(InterGitTrees)
1107
 
 
1108
 
 
1109
 
class MutableGitIndexTree(mutabletree.MutableTree, GitTree):
 
1027
class InterGitRevisionTrees(InterGitTrees):
 
1028
    """InterTree that works between two git revision trees."""
 
1029
 
 
1030
    _matching_from_tree_format = None
 
1031
    _matching_to_tree_format = None
 
1032
    _test_mutable_trees_to_test_trees = None
 
1033
 
 
1034
    @classmethod
 
1035
    def is_compatible(cls, source, target):
 
1036
        return (isinstance(source, GitRevisionTree) and
 
1037
                isinstance(target, GitRevisionTree))
 
1038
 
 
1039
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
 
1040
                          require_versioned=True, extra_trees=None,
 
1041
                          want_unversioned=False):
 
1042
        trees = [self.source]
 
1043
        if extra_trees is not None:
 
1044
            trees.extend(extra_trees)
 
1045
        if specific_files is not None:
 
1046
            specific_files = self.target.find_related_paths_across_trees(
 
1047
                specific_files, trees,
 
1048
                require_versioned=require_versioned)
 
1049
 
 
1050
        if (self.source._repository._git.object_store !=
 
1051
                self.target._repository._git.object_store):
 
1052
            store = OverlayObjectStore(
 
1053
                [self.source._repository._git.object_store,
 
1054
                    self.target._repository._git.object_store])
 
1055
        else:
 
1056
            store = self.source._repository._git.object_store
 
1057
        return store.tree_changes(
 
1058
            self.source.tree, self.target.tree, want_unchanged=want_unchanged,
 
1059
            include_trees=True, change_type_same=True), set()
 
1060
 
 
1061
 
 
1062
_mod_tree.InterTree.register_optimiser(InterGitRevisionTrees)
 
1063
 
 
1064
 
 
1065
class MutableGitIndexTree(mutabletree.MutableTree):
1110
1066
 
1111
1067
    def __init__(self):
1112
1068
        self._lock_mode = None
1115
1071
        self._index_dirty = False
1116
1072
        self._submodules = None
1117
1073
 
1118
 
    def git_snapshot(self, want_unversioned=False):
1119
 
        return snapshot_workingtree(self, want_unversioned=want_unversioned)
1120
 
 
1121
1074
    def is_versioned(self, path):
1122
1075
        with self.lock_read():
1123
 
            path = encode_git_path(path.rstrip('/'))
 
1076
            path = path.rstrip('/').encode('utf-8')
1124
1077
            (index, subpath) = self._lookup_index(path)
1125
1078
            return (subpath in index or self._has_dir(path))
1126
1079
 
1137
1090
        if self._lock_mode is None:
1138
1091
            raise errors.ObjectNotLocked(self)
1139
1092
        self._versioned_dirs = set()
1140
 
        for p, sha, mode in self.iter_git_objects():
 
1093
        for p, i in self._recurse_index_entries():
1141
1094
            self._ensure_versioned_dir(posixpath.dirname(p))
1142
1095
 
1143
1096
    def _ensure_versioned_dir(self, dirname):
1186
1139
    def _read_submodule_head(self, path):
1187
1140
        raise NotImplementedError(self._read_submodule_head)
1188
1141
 
 
1142
    def _submodule_info(self):
 
1143
        if self._submodules is None:
 
1144
            try:
 
1145
                with self.get_file('.gitmodules') as f:
 
1146
                    config = GitConfigFile.from_file(f)
 
1147
                    self._submodules = {
 
1148
                        path: (url, section)
 
1149
                        for path, url, section in parse_submodules(config)}
 
1150
            except errors.NoSuchFile:
 
1151
                self._submodules = {}
 
1152
        return self._submodules
 
1153
 
1189
1154
    def _lookup_index(self, encoded_path):
1190
1155
        if not isinstance(encoded_path, bytes):
1191
1156
            raise TypeError(encoded_path)
1221
1186
        # TODO(jelmer): Keep track of dirty per index
1222
1187
        self._index_dirty = True
1223
1188
 
1224
 
    def _apply_index_changes(self, changes):
1225
 
        for (path, kind, executability, reference_revision,
1226
 
             symlink_target) in changes:
1227
 
            if kind is None or kind == 'directory':
1228
 
                (index, subpath) = self._lookup_index(
1229
 
                    encode_git_path(path))
1230
 
                try:
1231
 
                    self._index_del_entry(index, subpath)
1232
 
                except KeyError:
1233
 
                    pass
1234
 
                else:
1235
 
                    self._versioned_dirs = None
1236
 
            else:
1237
 
                self._index_add_entry(
1238
 
                    path, kind,
1239
 
                    reference_revision=reference_revision,
1240
 
                    symlink_target=symlink_target)
1241
 
        self.flush()
1242
 
 
1243
 
    def _index_add_entry(
1244
 
            self, path, kind, flags=0, reference_revision=None,
1245
 
            symlink_target=None):
 
1189
    def _index_add_entry(self, path, kind, flags=0, reference_revision=None):
1246
1190
        if kind == "directory":
1247
1191
            # Git indexes don't contain directories
1248
1192
            return
1249
 
        elif kind == "file":
 
1193
        if kind == "file":
1250
1194
            blob = Blob()
1251
1195
            try:
1252
1196
                file, stat_val = self.get_file_with_stat(path)
1271
1215
                # old index
1272
1216
                stat_val = os.stat_result(
1273
1217
                    (stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
1274
 
            if symlink_target is None:
1275
 
                symlink_target = self.get_symlink_target(path)
1276
 
            blob.set_raw_string(encode_git_path(symlink_target))
 
1218
            blob.set_raw_string(
 
1219
                self.get_symlink_target(path).encode("utf-8"))
1277
1220
            # Add object to the repository if it didn't exist yet
1278
1221
            if blob.id not in self.store:
1279
1222
                self.store.add_object(blob)
1296
1239
            raise AssertionError("unknown kind '%s'" % kind)
1297
1240
        # Add an entry to the index or update the existing entry
1298
1241
        ensure_normalized_path(path)
1299
 
        encoded_path = encode_git_path(path)
 
1242
        encoded_path = path.encode("utf-8")
1300
1243
        if b'\r' in encoded_path or b'\n' in encoded_path:
1301
1244
            # TODO(jelmer): Why do we need to do this?
1302
1245
            trace.mutter('ignoring path with invalid newline in it: %r', path)
1307
1250
        if self._versioned_dirs is not None:
1308
1251
            self._ensure_versioned_dir(index_path)
1309
1252
 
1310
 
    def iter_git_objects(self):
1311
 
        for p, entry in self._recurse_index_entries():
1312
 
            yield p, entry.sha, entry.mode
1313
 
 
1314
1253
    def _recurse_index_entries(self, index=None, basepath=b"",
1315
1254
                               recurse_nested=False):
1316
1255
        # Iterate over all index entries
1345
1284
                    recurse_nested=recurse_nested):
1346
1285
                if self.mapping.is_special_file(path):
1347
1286
                    continue
1348
 
                path = decode_git_path(path)
 
1287
                path = path.decode("utf-8")
1349
1288
                if specific_files is not None and path not in specific_files:
1350
1289
                    continue
1351
1290
                (parent, name) = posixpath.split(path)
1397
1336
        elif kind == 'tree-reference':
1398
1337
            ie.reference_revision = self.get_reference_revision(path)
1399
1338
        else:
1400
 
            ie.git_sha1 = sha
1401
 
            ie.text_size = size
 
1339
            try:
 
1340
                data = self.get_file_text(path)
 
1341
            except errors.NoSuchFile:
 
1342
                data = None
 
1343
            except IOError as e:
 
1344
                if e.errno != errno.ENOENT:
 
1345
                    raise
 
1346
                data = None
 
1347
            if data is None:
 
1348
                data = self.branch.repository._git.object_store[sha].data
 
1349
            ie.text_sha1 = osutils.sha_string(data)
 
1350
            ie.text_size = len(data)
1402
1351
            ie.executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
1403
1352
        return ie
1404
1353
 
1421
1370
    def _unversion_path(self, path):
1422
1371
        if self._lock_mode is None:
1423
1372
            raise errors.ObjectNotLocked(self)
1424
 
        encoded_path = encode_git_path(path)
 
1373
        encoded_path = path.encode("utf-8")
1425
1374
        count = 0
1426
1375
        (index, subpath) = self._lookup_index(encoded_path)
1427
1376
        try:
1454
1403
        for (old_path, new_path, file_id, ie) in delta:
1455
1404
            if old_path is not None:
1456
1405
                (index, old_subpath) = self._lookup_index(
1457
 
                    encode_git_path(old_path))
 
1406
                    old_path.encode('utf-8'))
1458
1407
                if old_subpath in index:
1459
1408
                    self._index_del_entry(index, old_subpath)
1460
1409
                    self._versioned_dirs = None
1480
1429
            return rename_tuples
1481
1430
 
1482
1431
    def rename_one(self, from_rel, to_rel, after=None):
1483
 
        from_path = encode_git_path(from_rel)
 
1432
        from_path = from_rel.encode("utf-8")
1484
1433
        to_rel, can_access = osutils.normalized_filename(to_rel)
1485
1434
        if not can_access:
1486
1435
            raise errors.InvalidNormalization(to_rel)
1487
 
        to_path = encode_git_path(to_rel)
 
1436
        to_path = to_rel.encode("utf-8")
1488
1437
        with self.lock_tree_write():
1489
1438
            if not after:
1490
1439
                # Perhaps it's already moved?
1565
1514
            self._versioned_dirs = None
1566
1515
            self.flush()
1567
1516
 
 
1517
    def find_related_paths_across_trees(self, paths, trees=[],
 
1518
                                        require_versioned=True):
 
1519
        if paths is None:
 
1520
            return None
 
1521
 
 
1522
        if require_versioned:
 
1523
            trees = [self] + (trees if trees is not None else [])
 
1524
            unversioned = set()
 
1525
            for p in paths:
 
1526
                for t in trees:
 
1527
                    if t.is_versioned(p):
 
1528
                        break
 
1529
                else:
 
1530
                    unversioned.add(p)
 
1531
            if unversioned:
 
1532
                raise errors.PathsNotVersionedError(unversioned)
 
1533
 
 
1534
        return filter(self.is_versioned, paths)
 
1535
 
1568
1536
    def path_content_summary(self, path):
1569
1537
        """See Tree.path_content_summary."""
1570
1538
        try:
1591
1559
            return (kind, None, None, None)
1592
1560
 
1593
1561
    def stored_kind(self, relpath):
1594
 
        if relpath == '':
1595
 
            return 'directory'
1596
 
        (index, index_path) = self._lookup_index(encode_git_path(relpath))
 
1562
        (index, index_path) = self._lookup_index(relpath.encode('utf-8'))
1597
1563
        if index is None:
1598
 
            return None
 
1564
            return kind
1599
1565
        try:
1600
1566
            mode = index[index_path].mode
1601
1567
        except KeyError:
1602
 
            for p in index:
1603
 
                if osutils.is_inside(
1604
 
                        decode_git_path(index_path), decode_git_path(p)):
1605
 
                    return 'directory'
1606
 
            return None
 
1568
            return kind
1607
1569
        else:
1608
 
            return mode_kind(mode)
 
1570
            if S_ISGITLINK(mode):
 
1571
                return 'tree-reference'
 
1572
            return 'directory'
1609
1573
 
1610
1574
    def kind(self, relpath):
1611
1575
        kind = osutils.file_kind(self.abspath(relpath))
1619
1583
    def _live_entry(self, relpath):
1620
1584
        raise NotImplementedError(self._live_entry)
1621
1585
 
1622
 
    def transform(self, pb=None):
1623
 
        from .transform import GitTreeTransform
1624
 
        return GitTreeTransform(self, pb=pb)
1625
 
 
1626
 
    def has_changes(self, _from_tree=None):
1627
 
        """Quickly check that the tree contains at least one commitable change.
1628
 
 
1629
 
        :param _from_tree: tree to compare against to find changes (default to
1630
 
            the basis tree and is intended to be used by tests).
1631
 
 
1632
 
        :return: True if a change is found. False otherwise
1633
 
        """
 
1586
    def get_transform(self, pb=None):
 
1587
        from ..transform import TreeTransform
 
1588
        return TreeTransform(self, pb=pb)
 
1589
 
 
1590
 
 
1591
 
 
1592
class InterIndexGitTree(InterGitTrees):
 
1593
    """InterTree that works between a Git revision tree and an index."""
 
1594
 
 
1595
    def __init__(self, source, target):
 
1596
        super(InterIndexGitTree, self).__init__(source, target)
 
1597
        self._index = target.index
 
1598
 
 
1599
    @classmethod
 
1600
    def is_compatible(cls, source, target):
 
1601
        return (isinstance(source, GitRevisionTree) and
 
1602
                isinstance(target, MutableGitIndexTree))
 
1603
 
 
1604
    def _iter_git_changes(self, want_unchanged=False, specific_files=None,
 
1605
                          require_versioned=False, extra_trees=None,
 
1606
                          want_unversioned=False):
 
1607
        trees = [self.source]
 
1608
        if extra_trees is not None:
 
1609
            trees.extend(extra_trees)
 
1610
        if specific_files is not None:
 
1611
            specific_files = self.target.find_related_paths_across_trees(
 
1612
                specific_files, trees,
 
1613
                require_versioned=require_versioned)
 
1614
        # TODO(jelmer): Restrict to specific_files, for performance reasons.
1634
1615
        with self.lock_read():
1635
 
            # Check pending merges
1636
 
            if len(self.get_parent_ids()) > 1:
1637
 
                return True
1638
 
            if _from_tree is None:
1639
 
                _from_tree = self.basis_tree()
1640
 
            changes = self.iter_changes(_from_tree)
1641
 
            if self.supports_symlinks():
1642
 
                # Fast path for has_changes.
1643
 
                try:
1644
 
                    change = next(changes)
1645
 
                    if change.path[1] == '':
1646
 
                        next(changes)
1647
 
                    return True
1648
 
                except StopIteration:
1649
 
                    # No changes
1650
 
                    return False
1651
 
            else:
1652
 
                # Slow path for has_changes.
1653
 
                # Handle platforms that do not support symlinks in the
1654
 
                # conditional below. This is slower than the try/except
1655
 
                # approach below that but we don't have a choice as we
1656
 
                # need to be sure that all symlinks are removed from the
1657
 
                # entire changeset. This is because in platforms that
1658
 
                # do not support symlinks, they show up as None in the
1659
 
                # working copy as compared to the repository.
1660
 
                # Also, exclude root as mention in the above fast path.
1661
 
                changes = filter(
1662
 
                    lambda c: c[6][0] != 'symlink' and c[4] != (None, None),
1663
 
                    changes)
1664
 
                try:
1665
 
                    next(iter(changes))
1666
 
                except StopIteration:
1667
 
                    return False
1668
 
                return True
1669
 
 
1670
 
 
1671
 
def snapshot_workingtree(target, want_unversioned=False):
 
1616
            return changes_between_git_tree_and_working_copy(
 
1617
                self.source.store, self.source.tree,
 
1618
                self.target, want_unchanged=want_unchanged,
 
1619
                want_unversioned=want_unversioned)
 
1620
 
 
1621
 
 
1622
_mod_tree.InterTree.register_optimiser(InterIndexGitTree)
 
1623
 
 
1624
 
 
1625
def changes_between_git_tree_and_working_copy(store, from_tree_sha, target,
 
1626
                                              want_unchanged=False,
 
1627
                                              want_unversioned=False):
 
1628
    """Determine the changes between a git tree and a working tree with index.
 
1629
 
 
1630
    """
1672
1631
    extras = set()
1673
1632
    blobs = {}
1674
1633
    # Report dirified directories to commit_tree first, so that they can be
1692
1651
                    blobs[path] = (index_entry.sha, index_entry.mode)
1693
1652
                else:
1694
1653
                    dirified.append((path, Tree().id, stat.S_IFDIR))
1695
 
                    target.store.add_object(Tree())
 
1654
                    store.add_object(Tree())
1696
1655
            else:
1697
1656
                mode = live_entry.mode
1698
1657
                if not trust_executable:
1700
1659
                        mode |= 0o111
1701
1660
                    else:
1702
1661
                        mode &= ~0o111
1703
 
                if live_entry.sha != index_entry.sha:
1704
 
                    rp = decode_git_path(path)
1705
 
                    if stat.S_ISREG(live_entry.mode):
1706
 
                        blob = Blob()
1707
 
                        with target.get_file(rp) as f:
1708
 
                            blob.data = f.read()
1709
 
                    elif stat.S_ISLNK(live_entry.mode):
1710
 
                        blob = Blob()
1711
 
                        blob.data = target.get_symlink_target(rp).encode(osutils._fs_enc)
1712
 
                    else:
1713
 
                        blob = None
1714
 
                    if blob is not None:
1715
 
                        target.store.add_object(blob)
1716
1662
                blobs[path] = (live_entry.sha, cleanup_mode(live_entry.mode))
1717
1663
    if want_unversioned:
1718
 
        for extra in target._iter_files_recursive(include_dirs=False):
 
1664
        for e in target.extras():
 
1665
            st = target._lstat(e)
1719
1666
            try:
1720
 
                extra, accessible = osutils.normalized_filename(extra)
 
1667
                np, accessible = osutils.normalized_filename(e)
1721
1668
            except UnicodeDecodeError:
1722
1669
                raise errors.BadFilenameEncoding(
1723
 
                    extra, osutils._fs_enc)
1724
 
            np = encode_git_path(extra)
1725
 
            if np in blobs:
1726
 
                continue
1727
 
            st = target._lstat(extra)
 
1670
                    e, osutils._fs_enc)
1728
1671
            if stat.S_ISDIR(st.st_mode):
1729
1672
                blob = Tree()
1730
1673
            elif stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
1731
1674
                blob = blob_from_path_and_stat(
1732
 
                    target.abspath(extra).encode(osutils._fs_enc), st)
 
1675
                    target.abspath(e).encode(osutils._fs_enc), st)
1733
1676
            else:
1734
1677
                continue
1735
 
            target.store.add_object(blob)
 
1678
            store.add_object(blob)
 
1679
            np = np.encode('utf-8')
1736
1680
            blobs[np] = (blob.id, cleanup_mode(st.st_mode))
1737
1681
            extras.add(np)
1738
 
    return commit_tree(
1739
 
        target.store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()]), extras
 
1682
    to_tree_sha = commit_tree(
 
1683
        store, dirified + [(p, s, m) for (p, (s, m)) in blobs.items()])
 
1684
    return store.tree_changes(
 
1685
        from_tree_sha, to_tree_sha, include_trees=True,
 
1686
        want_unchanged=want_unchanged, change_type_same=True), extras