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.
This commit is contained in:
Martin von Zweigbergk 2021-02-07 23:45:14 -08:00
parent dd98f0564e
commit a1983ebe96

View file

@ -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();