From 8a2f630ac0f178face8760a1881bec9aeb850603 Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Sun, 28 Nov 2021 12:29:04 -0800 Subject: [PATCH] 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. --- lib/protos/op_store.proto | 1 + lib/src/git.rs | 14 ++++++++++++- lib/src/op_store.rs | 5 +++++ lib/src/repo.rs | 8 ++++++++ lib/src/simple_op_store.rs | 9 +++++++++ lib/src/view.rs | 12 ++++++++++++ lib/tests/test_git.rs | 40 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 88 insertions(+), 1 deletion(-) diff --git a/lib/protos/op_store.proto b/lib/protos/op_store.proto index f753b7e0b..4e4956b71 100644 --- a/lib/protos/op_store.proto +++ b/lib/protos/op_store.proto @@ -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 { diff --git a/lib/src/git.rs b/lib/src/git.rs index 98310ce5a..218aebf0c 100644 --- a/lib/src/git.rs +++ b/lib/src/git.rs @@ -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(()) } diff --git a/lib/src/op_store.rs b/lib/src/op_store.rs index 6a7225294..a446cc571 100644 --- a/lib/src/op_store.rs +++ b/lib/src/op_store.rs @@ -142,6 +142,10 @@ pub struct View { pub branches: BTreeMap, pub tags: BTreeMap, pub git_refs: BTreeMap, + /// 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, // 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, } } diff --git a/lib/src/repo.rs b/lib/src/repo.rs index 7a4b7c58b..7d7bad3c0 100644 --- a/lib/src/repo.rs +++ b/lib/src/repo.rs @@ -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(); diff --git a/lib/src/simple_op_store.rs b/lib/src/simple_op_store.rs index ebe5e39ff..5cb54f593 100644 --- a/lib/src/simple_op_store.rs +++ b/lib/src/simple_op_store.rs @@ -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(); diff --git a/lib/src/view.rs b/lib/src/view.rs index 5798f02d4..787585f7c 100644 --- a/lib/src/view.rs +++ b/lib/src/view.rs @@ -71,6 +71,10 @@ impl View { &self.data.git_refs } + pub fn git_head(&self) -> Option { + 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; } diff --git a/lib/tests/test_git.rs b/lib/tests/test_git.rs index 1de0cbbdd..885242b93 100644 --- a/lib/tests/test_git.rs +++ b/lib/tests/test_git.rs @@ -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]