git: start tracking HEAD of underlying Git repo

This patch adds a place for tracking the current `HEAD` commit in the
underlying Git repo. It updates `git::import_refs()` to record it. We
don't use it anywhere yet.

This is part of #44.
This commit is contained in:
Martin von Zweigbergk 2021-11-28 12:29:04 -08:00
parent d06c74f5b8
commit 8a2f630ac0
7 changed files with 88 additions and 1 deletions

View file

@ -64,6 +64,7 @@ message View {
repeated Tag tags = 6;
// Only a subset of the refs. For example, does not include refs/notes/.
repeated GitRef git_refs = 3;
bytes git_head = 7;
}
message Operation {

View file

@ -62,7 +62,6 @@ pub fn import_refs(
|| git_ref.name().is_none()
{
// Skip other refs (such as notes) and symbolic refs, as well as non-utf8 refs.
// TODO: Is it useful to import HEAD (especially if it's detached)?
continue;
}
let full_name = git_ref.name().unwrap().to_string();
@ -114,6 +113,19 @@ pub fn import_refs(
}
}
}
// TODO: Should this be a separate function? We may not always want to import
// the Git HEAD (and add it to our set of heads).
if let Ok(head_git_commit) = git_repo
.head()
.and_then(|head_ref| head_ref.peel_to_commit())
{
let head_commit_id = CommitId::from_bytes(head_git_commit.id().as_bytes());
let head_commit = store.get_commit(&head_commit_id).unwrap();
mut_repo.add_head(&head_commit);
mut_repo.set_git_head(head_commit_id);
} else {
mut_repo.clear_git_head();
}
Ok(())
}

View file

@ -142,6 +142,10 @@ pub struct View {
pub branches: BTreeMap<String, BranchTarget>,
pub tags: BTreeMap<String, RefTarget>,
pub git_refs: BTreeMap<String, RefTarget>,
/// The commit the Git HEAD points to.
// TODO: Support multiple Git worktrees?
// TODO: Do we want to store the current branch name too?
pub git_head: Option<CommitId>,
// The commit that *should be* checked out in the (default) working copy. Note that the
// working copy (.jj/working_copy/) has the source of truth about which commit *is* checked out
// (to be precise: the commit to which we most recently completed a checkout to).
@ -157,6 +161,7 @@ impl View {
branches: BTreeMap::new(),
tags: BTreeMap::new(),
git_refs: BTreeMap::new(),
git_head: None,
checkout,
}
}

View file

@ -664,6 +664,14 @@ impl MutableRepo {
self.view.remove_git_ref(name);
}
pub fn set_git_head(&mut self, head_id: CommitId) {
self.view.set_git_head(head_id);
}
pub fn clear_git_head(&mut self) {
self.view.clear_git_head();
}
pub fn set_view(&mut self, data: op_store::View) {
self.view.set_view(data);
self.enforce_view_invariants();

View file

@ -237,6 +237,10 @@ fn view_to_proto(view: &View) -> crate::protos::op_store::View {
proto.git_refs.push(git_ref_proto);
}
if let Some(git_head) = &view.git_head {
proto.set_git_head(git_head.to_bytes());
}
proto
}
@ -295,6 +299,10 @@ fn view_from_proto(proto: &crate::protos::op_store::View) -> View {
}
}
if !proto.git_head.is_empty() {
view.git_head = Some(CommitId::new(proto.git_head.clone()));
}
view
}
@ -387,6 +395,7 @@ mod tests {
"refs/heads/main".to_string() => git_refs_main_target,
"refs/heads/feature".to_string() => git_refs_feature_target
},
git_head: Some(CommitId::from_hex("fff111")),
checkout: checkout_id,
};
let view_id = store.write_view(&view).unwrap();

View file

@ -71,6 +71,10 @@ impl View {
&self.data.git_refs
}
pub fn git_head(&self) -> Option<CommitId> {
self.data.git_head.clone()
}
pub fn set_checkout(&mut self, id: CommitId) {
self.data.checkout = id;
}
@ -211,6 +215,14 @@ impl View {
self.data.git_refs.remove(name);
}
pub fn set_git_head(&mut self, head_id: CommitId) {
self.data.git_head = Some(head_id);
}
pub fn clear_git_head(&mut self) {
self.data.git_head = None;
}
pub fn set_view(&mut self, data: op_store::View) {
self.data = data;
}

View file

@ -69,6 +69,8 @@ fn test_import_refs() {
empty_git_commit(&git_repo, "refs/notes/x", &[&commit2]);
empty_git_commit(&git_repo, "refs/remotes/origin/HEAD", &[&commit2]);
git_repo.set_head("refs/heads/main").unwrap();
let git_repo = repo.store().git_repo().unwrap();
let mut tx = repo.start_transaction("test");
jujutsu_lib::git::import_refs(tx.mut_repo(), &git_repo).unwrap();
@ -136,6 +138,7 @@ fn test_import_refs() {
view.git_refs().get("refs/tags/v1.0"),
Some(RefTarget::Normal(commit_id(&commit5))).as_ref()
);
assert_eq!(view.git_head(), Some(commit_id(&commit2)));
}
#[test]
@ -260,6 +263,43 @@ fn test_import_refs_empty_git_repo() {
assert_eq!(repo.view().branches().len(), 0);
assert_eq!(repo.view().tags().len(), 0);
assert_eq!(repo.view().git_refs().len(), 0);
assert_eq!(repo.view().git_head(), None);
}
#[test]
fn test_import_refs_detached_head() {
let settings = testutils::user_settings();
let temp_dir = tempfile::tempdir().unwrap();
let git_repo_dir = temp_dir.path().join("source");
let jj_repo_dir = temp_dir.path().join("jj");
let git_repo = git2::Repository::init_bare(&git_repo_dir).unwrap();
let commit1 = empty_git_commit(&git_repo, "refs/heads/main", &[]);
// Delete the reference. Check that the detached HEAD commit still gets added to
// the set of heads
git_repo
.find_reference("refs/heads/main")
.unwrap()
.delete()
.unwrap();
git_repo.set_head_detached(commit1.id()).unwrap();
std::fs::create_dir(&jj_repo_dir).unwrap();
let repo = ReadonlyRepo::init_external_git(&settings, jj_repo_dir, git_repo_dir);
let mut tx = repo.start_transaction("test");
jujutsu_lib::git::import_refs(tx.mut_repo(), &git_repo).unwrap();
let repo = tx.commit();
let expected_heads = hashset! {
repo.view().checkout().clone(),
commit_id(&commit1),
};
assert_eq!(*repo.view().heads(), expected_heads);
assert_eq!(repo.view().git_refs().len(), 0);
assert_eq!(
repo.view().git_head(),
Some(CommitId::from_bytes(commit1.id().as_bytes()))
);
}
#[test]