From a1983ebe9632c4603f7dde471d4e4b39151415ab Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Sun, 7 Feb 2021 23:45:14 -0800 Subject: [PATCH] git: add a ref to each commit we create I just learned that attaching a git note is not enough to keep a commit from being GC'd. I had read `git help gc` before but it was quite misleading (I just sent a patch to clarify it). Since the git note is not enough, we need to create some other reference. This patch makes it so we write refs in `refs/jj/keep/` for every commit we create. We will probably want to remove unnecessary refs (ancestors of commits pointed to by other refs) once we have a `jj gc` command. --- lib/src/git_store.rs | 60 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/lib/src/git_store.rs b/lib/src/git_store.rs index fe84fdc2e..500d107fd 100644 --- a/lib/src/git_store.rs +++ b/lib/src/git_store.rs @@ -29,7 +29,11 @@ use crate::store::{ }; use backoff::{ExponentialBackoff, Operation}; use std::ops::Deref; +use uuid::Uuid; +/// Ref namespace used only for preventing GC. +const NO_GC_REF_NAMESPACE: &str = "refs/jj/keep/"; +/// Notes ref for commit metadata const COMMITS_NOTES_REF: &str = "refs/notes/jj/commits"; const CONFLICT_SUFFIX: &str = ".jjconflict"; @@ -108,6 +112,18 @@ fn deserialize_note(commit: &mut Commit, note: &str) { } } +/// Creates a random ref in refs/jj/. Used for preventing GC of commits we +/// create. +fn create_no_gc_ref() -> String { + let mut no_gc_ref = NO_GC_REF_NAMESPACE.to_owned(); + let mut uuid_buffer = Uuid::encode_buffer(); + let uuid_str = Uuid::new_v4() + .to_hyphenated() + .encode_lower(&mut uuid_buffer); + no_gc_ref.push_str(uuid_str); + no_gc_ref +} + fn write_note( git_repo: &git2::Repository, committer: &git2::Signature, @@ -349,8 +365,14 @@ impl Store for GitStore { parents.push(parent_git_commit); } let parent_refs: Vec<_> = parents.iter().collect(); - let git_id = - locked_repo.commit(None, &author, &committer, &message, &git_tree, &parent_refs)?; + let git_id = locked_repo.commit( + Some(&create_no_gc_ref()), + &author, + &committer, + &message, + &git_tree, + &parent_refs, + )?; let id = CommitId(git_id.as_bytes().to_vec()); let note = serialize_note(contents); @@ -579,6 +601,40 @@ mod tests { ); } + #[test] + fn commit_has_ref() { + let temp_dir = tempfile::tempdir().unwrap(); + let git_repo_path = temp_dir.path(); + let git_repo = git2::Repository::init(git_repo_path.clone()).unwrap(); + let store = GitStore::load(git_repo_path.to_owned()); + let signature = Signature { + name: "Someone".to_string(), + email: "someone@example.com".to_string(), + timestamp: Timestamp { + timestamp: MillisSinceEpoch(0), + tz_offset: 0, + }, + }; + let commit = Commit { + parents: vec![], + predecessors: vec![], + root_tree: store.empty_tree_id().clone(), + change_id: ChangeId(vec![]), + description: "initial".to_string(), + author: signature.clone(), + committer: signature, + is_open: false, + is_pruned: false, + }; + let commit_id = store.write_commit(&commit).unwrap(); + let git_refs: Vec<_> = git_repo + .references_glob("refs/jj/keep/*") + .unwrap() + .map(|git_ref| git_ref.unwrap().target().unwrap()) + .collect(); + assert_eq!(git_refs, vec![Oid::from_bytes(&commit_id.0).unwrap()]); + } + #[test] fn overlapping_git_commit_id() { let temp_dir = tempfile::tempdir().unwrap();