memory: persist every git index mutation
This commit is contained in:
parent
8cd2adb828
commit
0c9db09888
1 changed files with 106 additions and 9 deletions
|
|
@ -224,6 +224,7 @@ impl MemoryRepo {
|
|||
index
|
||||
.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)
|
||||
.context("staging initial memory files")?;
|
||||
index.write().context("persisting initial git index")?;
|
||||
let tree_id = index.write_tree().context("writing git tree")?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let signature = git2::Signature::now(
|
||||
|
|
@ -576,15 +577,7 @@ impl MemoryRepo {
|
|||
let repo_path = self.root.clone();
|
||||
let is_git_repo = repo_path.join(".git").exists();
|
||||
|
||||
let mut file_count = 0;
|
||||
if let Ok(entries) = std::fs::read_dir(&repo_path) {
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
if name != ".git" && name.ends_with(".md") {
|
||||
file_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let file_count = Self::count_md_files(&repo_path);
|
||||
|
||||
let (last_commit, has_uncommitted, remote_url) = if is_git_repo {
|
||||
match git2::Repository::open(&repo_path) {
|
||||
|
|
@ -639,9 +632,14 @@ impl MemoryRepo {
|
|||
index.add_path(Path::new(&md_path))?;
|
||||
} else if self.root.join(&rel_path).exists() {
|
||||
index.add_path(Path::new(&rel_path))?;
|
||||
} else if index.get_path(Path::new(&md_path), 0).is_some() {
|
||||
index.remove_path(Path::new(&md_path))?;
|
||||
} else if index.get_path(Path::new(&rel_path), 0).is_some() {
|
||||
index.remove_path(Path::new(&rel_path))?;
|
||||
}
|
||||
}
|
||||
|
||||
index.write().context("persisting git index")?;
|
||||
let tree_id = index.write_tree().context("writing tree")?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
let parent = repo.head().ok().and_then(|h| h.peel_to_commit().ok());
|
||||
|
|
@ -781,6 +779,34 @@ impl MemoryRepo {
|
|||
git2::Repository::open(&self.root).context("opening memory git repository")
|
||||
}
|
||||
|
||||
fn count_md_files(root: &Path) -> usize {
|
||||
let mut count = 0;
|
||||
let mut directories = vec![root.to_path_buf()];
|
||||
|
||||
while let Some(directory) = directories.pop() {
|
||||
let Ok(entries) = std::fs::read_dir(directory) else {
|
||||
continue;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
if name.to_string_lossy().starts_with(".git") {
|
||||
continue;
|
||||
}
|
||||
let Ok(kind) = entry.file_type() else {
|
||||
continue;
|
||||
};
|
||||
if kind.is_dir() {
|
||||
directories.push(entry.path());
|
||||
} else if kind.is_file() && entry.path().extension().is_some_and(|ext| ext == "md")
|
||||
{
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
/// Whether the `description` frontmatter contract applies to a label.
|
||||
///
|
||||
/// `description` is required on `MemoryFrontmatter` files — the prompt
|
||||
|
|
@ -1413,6 +1439,67 @@ mod tests {
|
|||
assert_eq!(file.body, "Hello memory world");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn consecutive_writes_survive_in_head_and_index() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let repo = MemoryRepo::new("test-agent", dir.path());
|
||||
repo.init().await.unwrap();
|
||||
|
||||
repo.write("journal/first", "first thought").await.unwrap();
|
||||
repo.write("journal/second", "second thought")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let git = git2::Repository::open(repo.root()).unwrap();
|
||||
let head = git.head().unwrap().peel_to_commit().unwrap();
|
||||
let tree = head.tree().unwrap();
|
||||
let index = git.index().unwrap();
|
||||
for path in [
|
||||
"system/persona.md",
|
||||
"system/state.md",
|
||||
"journal/first.md",
|
||||
"journal/second.md",
|
||||
] {
|
||||
assert!(
|
||||
tree.get_path(Path::new(path)).is_ok(),
|
||||
"HEAD lost {path}"
|
||||
);
|
||||
assert!(
|
||||
index.get_path(Path::new(path), 0).is_some(),
|
||||
"index lost {path}"
|
||||
);
|
||||
}
|
||||
|
||||
let status = repo.status().unwrap();
|
||||
assert_eq!(status.file_count, 4);
|
||||
assert!(!status.has_uncommitted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_survives_in_head_and_index() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let repo = MemoryRepo::new("test-agent", dir.path());
|
||||
repo.init().await.unwrap();
|
||||
repo.write("journal/keep", "keep me").await.unwrap();
|
||||
repo.write("journal/remove", "remove me").await.unwrap();
|
||||
|
||||
repo.delete("journal/remove").await.unwrap();
|
||||
|
||||
let git = git2::Repository::open(repo.root()).unwrap();
|
||||
let head = git.head().unwrap().peel_to_commit().unwrap();
|
||||
let tree = head.tree().unwrap();
|
||||
let index = git.index().unwrap();
|
||||
assert!(tree.get_path(Path::new("journal/keep.md")).is_ok());
|
||||
assert!(tree.get_path(Path::new("journal/remove.md")).is_err());
|
||||
assert!(index
|
||||
.get_path(Path::new("journal/remove.md"), 0)
|
||||
.is_none());
|
||||
|
||||
let status = repo.status().unwrap();
|
||||
assert_eq!(status.file_count, 3);
|
||||
assert!(!status.has_uncommitted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_without_remote_errors_with_provisioning_hint() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
|
|
@ -1443,6 +1530,9 @@ mod tests {
|
|||
let repo = MemoryRepo::new("test-agent", dir.path());
|
||||
repo.init().await.unwrap();
|
||||
repo.write("journal/entry", "first thought").await.unwrap();
|
||||
repo.write("journal/second", "second thought")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let remote_dir = TempDir::new().unwrap();
|
||||
let bare = remote_dir.path().join("memfs.git");
|
||||
|
|
@ -1464,6 +1554,13 @@ mod tests {
|
|||
branch.target().unwrap().to_string(),
|
||||
repo.head_commit_hex().unwrap()
|
||||
);
|
||||
let tree = remote_repo
|
||||
.find_commit(branch.target().unwrap())
|
||||
.unwrap()
|
||||
.tree()
|
||||
.unwrap();
|
||||
assert!(tree.get_path(Path::new("journal/entry.md")).is_ok());
|
||||
assert!(tree.get_path(Path::new("journal/second.md")).is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
|||
Loading…
Reference in a new issue