mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
fix(vcs): reopen the repository when the pack directory changes (#1920)
Repo::gix() reopened the cached gitoxide handle only when config or packed-refs changed. An external git fetch/pull/repack writes new packs under objects/pack and moves refs, but never touches those files — so the ODB kept serving the snapshot taken at open, and refs whose tip lived only in the new pack failed to peel: branches vanished and status warned "Could not peel reference". Watch the pack directory's mtime too and reopen when it changes, so externally added objects become visible (issue #1919). Fixes #1919
This commit is contained in:
1 parent
8ca6e4bc01
commit
6efff739dd
2 files changed
+40
-2
No files matched your search
@@ -15,6 +15,11 @@ pub(crate) struct Cache {
|
||||
repo: gix::ThreadSafeRepository,
|
||||
files: [PathBuf; 4],
|
||||
stamp: Vec<Option<(SystemTime, u64)>>,
|
||||
/// `objects/pack` directory and its modification time. An external
|
||||
/// `git fetch`/`pull`/`repack` adds packs without touching the `files`
|
||||
/// above — watching the directory catches them (issue #1919).
|
||||
pack_dir: PathBuf,
|
||||
pack_stamp: Option<SystemTime>,
|
||||
}
|
||||
|
||||
fn cache_files(git_dir: &Path, common_dir: &Path) -> [PathBuf; 4] {
|
||||
@@ -36,6 +41,12 @@ fn stamp(files: &[PathBuf]) -> Vec<Option<(SystemTime, u64)>> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// The pack directory's modification time: adding or removing a pack file
|
||||
/// updates it. `None` when the directory can't be read.
|
||||
fn pack_stamp(pack_dir: &Path) -> Option<SystemTime> {
|
||||
std::fs::metadata(pack_dir).ok()?.modified().ok()
|
||||
}
|
||||
|
||||
pub(crate) fn discover(path: &Path) -> IdeResult<Option<Repo>> {
|
||||
let start = if path.is_file() { path.parent().unwrap_or(path) } else { path };
|
||||
if let Err(e) = gix::discover::upwards(start) {
|
||||
@@ -57,7 +68,8 @@ pub(crate) fn discover(path: &Path) -> IdeResult<Option<Repo>> {
|
||||
let git_dir = absolute(repo.git_dir())?;
|
||||
let common_dir = absolute(repo.common_dir())?;
|
||||
let files = cache_files(&git_dir, &common_dir);
|
||||
let cache = Cache { stamp: stamp(&files), files, repo: ts };
|
||||
let pack_dir = common_dir.join("objects").join("pack");
|
||||
let cache = Cache { stamp: stamp(&files), files, repo: ts, pack_stamp: pack_stamp(&pack_dir), pack_dir };
|
||||
Ok(Some(Repo {
|
||||
execution_allowed: std::sync::atomic::AtomicBool::new(true),
|
||||
root,
|
||||
@@ -96,11 +108,13 @@ impl Repo {
|
||||
}
|
||||
let mut cache = self.inner.lock().unwrap_or_else(|p| p.into_inner());
|
||||
let now = stamp(&cache.files);
|
||||
if now != cache.stamp {
|
||||
let pack_now = pack_stamp(&cache.pack_dir);
|
||||
if now != cache.stamp || pack_now != cache.pack_stamp {
|
||||
match gix::ThreadSafeRepository::open(&self.git_dir) {
|
||||
Ok(fresh) => {
|
||||
cache.repo = fresh;
|
||||
cache.stamp = now;
|
||||
cache.pack_stamp = pack_now;
|
||||
}
|
||||
Err(e) => tracing::warn!("reopening repository failed: {e}"),
|
||||
}
|
||||
|
||||
@@ -1366,6 +1366,30 @@ fn status_reflects_external_changes_without_reopen() {
|
||||
assert_eq!(repo.branch_info().unwrap().head.as_deref(), Some("x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branches_in_newly_written_packs_stay_visible() {
|
||||
let fx = Fixture::new();
|
||||
fx.write("a", "1\n");
|
||||
fx.commit_all("a");
|
||||
let repo = fx.repo();
|
||||
assert_eq!(repo.branches().unwrap().len(), 1);
|
||||
|
||||
// An external pull: objects move into a pack the IDE has never seen, then a
|
||||
// new branch points at a commit living only in that pack.
|
||||
fx.write("a", "2\n");
|
||||
fx.commit_all("b");
|
||||
fx.git(&["repack", "-adq"]);
|
||||
fx.git(&["checkout", "-q", "-b", "uat"]);
|
||||
let uat = fx.git(&["rev-parse", "uat"]);
|
||||
assert!(std::fs::read_dir(fx.path(".git/objects/pack")).unwrap().count() >= 2);
|
||||
|
||||
let names: Vec<String> = repo.branches().unwrap().into_iter().map(|b| b.name).collect();
|
||||
assert!(names.contains(&"uat".to_string()), "branches: {names:?}, want uat at {uat}");
|
||||
|
||||
// Status peels HEAD for the tree-index diff: the same failure drops it too.
|
||||
assert!(repo.status().is_ok(), "status failed: {:?}", repo.status().unwrap_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linked_worktree_and_submodule() {
|
||||
let fx = Fixture::new();
|
||||
|
||||
Reference in new issue
Block a user