|
@@ -1629,9 +1629,31 @@ def index_entry_from_stat(
|
|
|
|
|
|
|
|
from dulwich.objects import ObjectID
|
|
from dulwich.objects import ObjectID
|
|
|
|
|
|
|
|
|
|
+ # Use nanosecond precision when available to avoid precision loss
|
|
|
|
|
+ # through float representation
|
|
|
|
|
+ ctime: int | float | tuple[int, int]
|
|
|
|
|
+ mtime: int | float | tuple[int, int]
|
|
|
|
|
+ st_ctime_ns = getattr(stat_val, "st_ctime_ns", None)
|
|
|
|
|
+ if st_ctime_ns is not None:
|
|
|
|
|
+ ctime = (
|
|
|
|
|
+ st_ctime_ns // 1_000_000_000,
|
|
|
|
|
+ st_ctime_ns % 1_000_000_000,
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ ctime = stat_val.st_ctime
|
|
|
|
|
+
|
|
|
|
|
+ st_mtime_ns = getattr(stat_val, "st_mtime_ns", None)
|
|
|
|
|
+ if st_mtime_ns is not None:
|
|
|
|
|
+ mtime = (
|
|
|
|
|
+ st_mtime_ns // 1_000_000_000,
|
|
|
|
|
+ st_mtime_ns % 1_000_000_000,
|
|
|
|
|
+ )
|
|
|
|
|
+ else:
|
|
|
|
|
+ mtime = stat_val.st_mtime
|
|
|
|
|
+
|
|
|
return IndexEntry(
|
|
return IndexEntry(
|
|
|
- ctime=stat_val.st_ctime,
|
|
|
|
|
- mtime=stat_val.st_mtime,
|
|
|
|
|
|
|
+ ctime=ctime,
|
|
|
|
|
+ mtime=mtime,
|
|
|
dev=stat_val.st_dev,
|
|
dev=stat_val.st_dev,
|
|
|
ino=stat_val.st_ino,
|
|
ino=stat_val.st_ino,
|
|
|
mode=mode,
|
|
mode=mode,
|
|
@@ -2773,17 +2795,27 @@ def _stat_matches_entry(st: os.stat_result, entry: IndexEntry) -> bool:
|
|
|
entry: Index entry to compare against
|
|
entry: Index entry to compare against
|
|
|
Returns: True if stat matches and file is likely unchanged
|
|
Returns: True if stat matches and file is likely unchanged
|
|
|
"""
|
|
"""
|
|
|
- # Get entry mtime
|
|
|
|
|
|
|
+ # Get entry mtime with nanosecond precision if available
|
|
|
if isinstance(entry.mtime, tuple):
|
|
if isinstance(entry.mtime, tuple):
|
|
|
entry_mtime_sec = entry.mtime[0]
|
|
entry_mtime_sec = entry.mtime[0]
|
|
|
|
|
+ entry_mtime_nsec = entry.mtime[1]
|
|
|
else:
|
|
else:
|
|
|
entry_mtime_sec = int(entry.mtime)
|
|
entry_mtime_sec = int(entry.mtime)
|
|
|
-
|
|
|
|
|
- # Compare modification time (seconds only for now)
|
|
|
|
|
- # Note: We use int() to compare only seconds, as nanosecond precision
|
|
|
|
|
- # can vary across filesystems
|
|
|
|
|
- if int(st.st_mtime) != entry_mtime_sec:
|
|
|
|
|
- return False
|
|
|
|
|
|
|
+ entry_mtime_nsec = 0
|
|
|
|
|
+
|
|
|
|
|
+ # Compare modification time with nanosecond precision if available
|
|
|
|
|
+ # This is important for fast workflows (e.g., stash) where files can be
|
|
|
|
|
+ # modified multiple times within the same second
|
|
|
|
|
+ if hasattr(st, "st_mtime_ns"):
|
|
|
|
|
+ # Use nanosecond precision when available
|
|
|
|
|
+ st_mtime_nsec = st.st_mtime_ns
|
|
|
|
|
+ entry_mtime_nsec_total = entry_mtime_sec * 1_000_000_000 + entry_mtime_nsec
|
|
|
|
|
+ if st_mtime_nsec != entry_mtime_nsec_total:
|
|
|
|
|
+ return False
|
|
|
|
|
+ else:
|
|
|
|
|
+ # Fall back to second precision
|
|
|
|
|
+ if int(st.st_mtime) != entry_mtime_sec:
|
|
|
|
|
+ return False
|
|
|
|
|
|
|
|
# Compare file size
|
|
# Compare file size
|
|
|
if st.st_size != entry.size:
|
|
if st.st_size != entry.size:
|