Skip to content

Commit 74ff8e5

Browse files
committed
Support index format v3
1 parent 271328f commit 74ff8e5

File tree

3 files changed

+50
-6
lines changed

3 files changed

+50
-6
lines changed

git/index/fun.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
)
3737
from git.util import IndexFileSHA1Writer, finalize_process
3838

39-
from .typ import BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT
39+
from .typ import CE_EXTENDED, BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT
4040
from .util import pack, unpack
4141

4242
# typing -----------------------------------------------------------------------------
@@ -158,7 +158,7 @@ def write_cache(
158158
write = stream_sha.write
159159

160160
# Header
161-
version = 2
161+
version = 3 if any(entry.extended_flags for entry in entries) else 2
162162
write(b"DIRC")
163163
write(pack(">LL", version, len(entries)))
164164

@@ -172,6 +172,8 @@ def write_cache(
172172
plen = len(path) & CE_NAMEMASK # Path length
173173
assert plen == len(path), "Path %s too long to fit into index" % entry.path
174174
flags = plen | (entry.flags & CE_NAMEMASK_INV) # Clear possible previous values.
175+
if entry.extended_flags:
176+
flags |= CE_EXTENDED
175177
write(
176178
pack(
177179
">LLLLLL20sH",
@@ -185,6 +187,8 @@ def write_cache(
185187
flags,
186188
)
187189
)
190+
if entry.extended_flags:
191+
write(pack(">H", entry.extended_flags))
188192
write(path)
189193
real_size = (tell() - beginoffset + 8) & ~7
190194
write(b"\0" * ((beginoffset + real_size) - tell()))
@@ -206,8 +210,7 @@ def read_header(stream: IO[bytes]) -> Tuple[int, int]:
206210
unpacked = cast(Tuple[int, int], unpack(">LL", stream.read(4 * 2)))
207211
version, num_entries = unpacked
208212

209-
# TODO: Handle version 3: extended data, see read-cache.c.
210-
assert version in (1, 2), "Unsupported git index version %i, only 1 and 2 are supported" % version
213+
assert version in (1, 2, 3), "Unsupported git index version %i, only 1, 2, and 3 are supported" % version
211214
return version, num_entries
212215

213216

@@ -260,12 +263,15 @@ def read_cache(
260263
ctime = unpack(">8s", read(8))[0]
261264
mtime = unpack(">8s", read(8))[0]
262265
(dev, ino, mode, uid, gid, size, sha, flags) = unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2))
266+
extended_flags = 0
267+
if flags & CE_EXTENDED:
268+
extended_flags = unpack(">H", read(2))[0]
263269
path_size = flags & CE_NAMEMASK
264270
path = read(path_size).decode(defenc)
265271

266272
real_size = (tell() - beginoffset + 8) & ~7
267273
read((beginoffset + real_size) - tell())
268-
entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size))
274+
entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size, extended_flags))
269275
# entry_key would be the method to use, but we save the effort.
270276
entries[(path, entry.stage)] = entry
271277
count += 1

git/index/typ.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
CE_VALID = 0x8000
3333
CE_STAGESHIFT = 12
3434

35+
CE_EXT_SKIP_WORKTREE = 0x4000
36+
CE_EXT_INTENT_TO_ADD = 0x2000
37+
3538
# } END invariants
3639

3740

@@ -87,6 +90,8 @@ class BaseIndexEntryHelper(NamedTuple):
8790
uid: int = 0
8891
gid: int = 0
8992
size: int = 0
93+
# version 3 extended flags, only when (flags & CE_EXTENDED) is set
94+
extended_flags: int = 0
9095

9196

9297
class BaseIndexEntry(BaseIndexEntryHelper):
@@ -102,7 +107,7 @@ def __new__(
102107
cls,
103108
inp_tuple: Union[
104109
Tuple[int, bytes, int, PathLike],
105-
Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int],
110+
Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int, int],
106111
],
107112
) -> "BaseIndexEntry":
108113
"""Override ``__new__`` to allow construction from a tuple for backwards
@@ -134,6 +139,14 @@ def stage(self) -> int:
134139
"""
135140
return (self.flags & CE_STAGEMASK) >> CE_STAGESHIFT
136141

142+
@property
143+
def skip_worktree(self) -> bool:
144+
return (self.extended_flags & CE_EXT_SKIP_WORKTREE) > 0
145+
146+
@property
147+
def intent_to_add(self) -> bool:
148+
return (self.extended_flags & CE_EXT_INTENT_TO_ADD) > 0
149+
137150
@classmethod
138151
def from_blob(cls, blob: Blob, stage: int = 0) -> "BaseIndexEntry":
139152
""":return: Fully equipped BaseIndexEntry at the given stage"""

test/test_index.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,31 @@ def test_index_add_non_normalized_path(self, rw_repo):
12181218

12191219
rw_repo.index.add(non_normalized_path)
12201220

1221+
@with_rw_directory
1222+
def test_index_version_v3(self, tmp_dir):
1223+
tmp_dir = Path(tmp_dir)
1224+
with cwd(tmp_dir):
1225+
subprocess.run(["git", "init", "-q"], check=True)
1226+
file = tmp_dir / "file.txt"
1227+
file.write_text("hello")
1228+
subprocess.run(["git", "add", "-N", "file.txt"], check=True)
1229+
1230+
repo = Repo(tmp_dir)
1231+
1232+
assert len(repo.index.entries) == 1
1233+
entry = list(repo.index.entries.values())[0]
1234+
assert entry.path == "file.txt"
1235+
assert entry.intent_to_add
1236+
1237+
file2 = tmp_dir / "file2.txt"
1238+
file2.write_text("world")
1239+
repo.index.add(["file2.txt"])
1240+
repo.index.write()
1241+
1242+
status_str = subprocess.check_output(["git", "status", "--porcelain"], text=True)
1243+
assert " A file.txt\n" in status_str
1244+
assert "A file2.txt\n" in status_str
1245+
12211246

12221247
class TestIndexUtils:
12231248
@pytest.mark.parametrize("file_path_type", [str, Path])

0 commit comments

Comments
 (0)