|
11 | 11 | import contextlib |
12 | 12 | import datetime |
13 | 13 | import glob |
14 | | -from io import BytesIO |
15 | 14 | import os |
16 | 15 | import os.path as osp |
17 | 16 | from stat import S_ISLNK |
18 | 17 | import subprocess |
19 | 18 | import sys |
20 | 19 | import tempfile |
21 | 20 |
|
22 | | -from gitdb.base import IStream |
23 | 21 | from gitdb.db import MemoryDB |
24 | 22 |
|
25 | 23 | from git.compat import defenc, force_bytes |
|
34 | 32 | LockedFD, |
35 | 33 | join_path_native, |
36 | 34 | file_contents_ro, |
| 35 | + hex_to_bin, |
37 | 36 | _is_path_rooted, |
38 | 37 | _to_relative_path, |
39 | 38 | to_native_path_linux, |
|
58 | 57 |
|
59 | 58 | from typing import ( |
60 | 59 | Any, |
61 | | - BinaryIO, |
62 | 60 | Callable, |
63 | 61 | cast, |
64 | 62 | Dict, |
@@ -726,24 +724,35 @@ def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry |
726 | 724 | """ |
727 | 725 | st = os.lstat(filepath) # Handles non-symlinks as well. |
728 | 726 |
|
| 727 | + fprogress(filepath, False, filepath) |
729 | 728 | if S_ISLNK(st.st_mode): |
730 | 729 | # In PY3, readlink is a string, but we need bytes. |
731 | 730 | # In PY2, it was just OS encoded bytes, we assumed UTF-8. |
732 | | - def open_stream() -> BinaryIO: |
733 | | - return BytesIO(force_bytes(os.readlink(filepath), encoding=defenc)) |
| 731 | + # |
| 732 | + # `git hash-object` on a symlink path follows the link and hashes the |
| 733 | + # target file's content, whereas Git stores the link's target string as |
| 734 | + # the blob content. So the target string is written to a regular |
| 735 | + # temporary file and hashed from there instead of passing the symlink |
| 736 | + # path directly. |
| 737 | + target = force_bytes(os.readlink(filepath), encoding=defenc) |
| 738 | + fd, tmp_path = tempfile.mkstemp() |
| 739 | + try: |
| 740 | + os.write(fd, target) |
| 741 | + os.close(fd) |
| 742 | + hexsha = self.repo.git.hash_object(tmp_path, w=True, no_filters=True) |
| 743 | + finally: |
| 744 | + os.remove(tmp_path) |
734 | 745 | else: |
735 | | - |
736 | | - def open_stream() -> BinaryIO: |
737 | | - return open(filepath, "rb") |
738 | | - |
739 | | - with open_stream() as stream: |
740 | | - fprogress(filepath, False, filepath) |
741 | | - istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream)) |
742 | | - fprogress(filepath, True, filepath) |
| 746 | + # Let the `git` binary write the object. This matches `git add`'s |
| 747 | + # behavior exactly, including applying any clean filters configured via |
| 748 | + # `.gitattributes`, and avoids reimplementing object creation (and its |
| 749 | + # object-file permission handling) in Python. |
| 750 | + hexsha = self.repo.git.hash_object(filepath, w=True) |
| 751 | + fprogress(filepath, True, filepath) |
743 | 752 | return BaseIndexEntry( |
744 | 753 | ( |
745 | 754 | stat_mode_to_index_mode(st.st_mode), |
746 | | - istream.binsha, |
| 755 | + hex_to_bin(hexsha), |
747 | 756 | 0, |
748 | 757 | to_native_path_linux(filepath), |
749 | 758 | ) |
|
0 commit comments