Skip to content

Commit 4c3a8d3

Browse files
oxen lfs fetch-all keeps git status clean
1 parent e2492f4 commit 4c3a8d3

File tree

1 file changed

+47
-10
lines changed

1 file changed

+47
-10
lines changed

oxen-rust/src/lib/src/lfs/sync.rs

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use std::path::Path;
1+
use std::path::{Path, PathBuf};
2+
use std::process::Command;
23

34
use crate::error::OxenError;
45
use crate::lfs::config::LfsConfig;
@@ -54,7 +55,7 @@ pub async fn pull_from_remote(
5455

5556
let store = LocalVersionStore::new(&versions_dir);
5657
let lfs_config = LfsConfig::load(oxen_dir)?;
57-
let mut restored = 0u64;
58+
let mut restored_paths: Vec<PathBuf> = Vec::new();
5859

5960
for file_status in &statuses {
6061
if file_status.local {
@@ -63,7 +64,7 @@ pub async fn pull_from_remote(
6364
store
6465
.copy_version_to_path(&file_status.pointer.oid, &dest)
6566
.await?;
66-
restored += 1;
67+
restored_paths.push(file_status.path.clone());
6768
} else {
6869
// Try smudge (which checks origin for local clones).
6970
let pointer_data = file_status.pointer.encode();
@@ -73,7 +74,7 @@ pub async fn pull_from_remote(
7374
// Smudge resolved it — write to working tree.
7475
let dest = repo_root.join(&file_status.path);
7576
std::fs::write(&dest, &result)?;
76-
restored += 1;
77+
restored_paths.push(file_status.path.clone());
7778
} else if !local_only {
7879
// TODO (Phase 3): Fetch from remote, then restore.
7980
log::warn!(
@@ -84,8 +85,12 @@ pub async fn pull_from_remote(
8485
}
8586
}
8687

87-
if restored > 0 {
88-
println!("oxen lfs pull: restored {restored} file(s)");
88+
if !restored_paths.is_empty() {
89+
// Re-add restored files so Git's index stat cache reflects the new
90+
// on-disk content. The clean filter produces the same pointer blob,
91+
// so no actual index change occurs — only the stat cache is updated.
92+
git_add(repo_root, &restored_paths);
93+
println!("oxen lfs pull: restored {} file(s)", restored_paths.len());
8994
}
9095

9196
Ok(())
@@ -113,7 +118,7 @@ pub async fn fetch_all(repo_root: &Path, oxen_dir: &Path) -> Result<(), OxenErro
113118
}
114119

115120
let store = LocalVersionStore::new(&versions_dir);
116-
let mut restored = 0u64;
121+
let mut restored_paths: Vec<PathBuf> = Vec::new();
117122
let mut failures: Vec<String> = Vec::new();
118123

119124
for file_status in &statuses {
@@ -124,7 +129,7 @@ pub async fn fetch_all(repo_root: &Path, oxen_dir: &Path) -> Result<(), OxenErro
124129
store
125130
.copy_version_to_path(&file_status.pointer.oid, &dest)
126131
.await?;
127-
restored += 1;
132+
restored_paths.push(file_status.path.clone());
128133
println!(" restored: {}", file_status.path.display());
129134
continue;
130135
}
@@ -142,7 +147,7 @@ pub async fn fetch_all(repo_root: &Path, oxen_dir: &Path) -> Result<(), OxenErro
142147
));
143148
} else {
144149
std::fs::write(&dest, &result)?;
145-
restored += 1;
150+
restored_paths.push(file_status.path.clone());
146151
println!(" restored: {}", file_status.path.display());
147152
}
148153
}
@@ -156,10 +161,42 @@ pub async fn fetch_all(repo_root: &Path, oxen_dir: &Path) -> Result<(), OxenErro
156161
return Err(OxenError::basic_str(msg));
157162
}
158163

159-
println!("oxen lfs fetch-all: all {restored} file(s) restored successfully");
164+
// Re-add restored files so Git's index stat cache reflects the new
165+
// on-disk content. The clean filter produces the same pointer blob,
166+
// so no actual index change occurs — only the stat cache is updated.
167+
git_add(repo_root, &restored_paths);
168+
169+
println!(
170+
"oxen lfs fetch-all: all {} file(s) restored successfully",
171+
restored_paths.len()
172+
);
160173
Ok(())
161174
}
162175

176+
/// Run `git add` on a list of paths so Git's index stat cache is updated.
177+
///
178+
/// After we replace a pointer file with real content, the on-disk size and
179+
/// mtime change. Without re-adding, `git status` shows the files as modified
180+
/// even though the clean filter produces the identical blob. Re-adding lets
181+
/// Git refresh its stat cache.
182+
fn git_add(repo_root: &Path, paths: &[PathBuf]) {
183+
if paths.is_empty() {
184+
return;
185+
}
186+
187+
let path_args: Vec<&str> = paths.iter().filter_map(|p| p.to_str()).collect();
188+
if path_args.is_empty() {
189+
return;
190+
}
191+
192+
let mut cmd = Command::new("git");
193+
cmd.arg("add").args(&path_args).current_dir(repo_root);
194+
195+
if let Err(e) = cmd.output() {
196+
log::warn!("oxen lfs: failed to run git add to refresh index: {e}");
197+
}
198+
}
199+
163200
/// Scan working tree for pointer files and return the list of OIDs
164201
/// that need to be pushed.
165202
pub async fn list_pushable_oids(

0 commit comments

Comments
 (0)