diff --git a/radicle/src/git.rs b/radicle/src/git.rs index 4e00b8e4..58890170 100644 --- a/radicle/src/git.rs +++ b/radicle/src/git.rs @@ -31,8 +31,12 @@ pub const PROTOCOL_PORT: u16 = 9418; #[derive(thiserror::Error, Debug)] pub enum RefError { - #[error("invalid ref name '{0}'")] - InvalidName(format::RefString), + #[error("ref name is not valid UTF-8")] + InvalidName, + #[error("unexpected symbolic ref: {0}")] + Symbolic(RefString), + #[error("unexpected unqualified ref: {0}")] + Unqualified(RefString), #[error("invalid ref format: {0}")] Format(#[from] format::Error), #[error("expected ref to begin with 'refs/namespaces' but found '{0}'")] @@ -67,8 +71,20 @@ pub mod refs { /// Where project information is kept. pub static IDENTITY_BRANCH: Lazy = Lazy::new(|| refname!("radicle/id")); - pub mod storage { + /// Try to get a qualified reference from a generic reference. + pub fn qualified_from<'a>(r: &'a git2::Reference) -> Result<(Qualified<'a>, Oid), RefError> { + let name = r.name().ok_or(RefError::InvalidName)?; + let refstr = RefStr::try_from_str(name)?; + let target = r + .target() + .ok_or_else(|| RefError::Symbolic(refstr.to_owned()))?; + let qualified = Qualified::from_refstr(refstr) + .ok_or_else(|| RefError::Unqualified(refstr.to_owned()))?; + Ok((qualified, target.into())) + } + + pub mod storage { use super::*; /// Create the [`Namespaced`] `branch` under the `remote` namespace, i.e. @@ -157,7 +173,7 @@ where match input.to_namespaced() { None => { let refname = Qualified::from_refstr(input) - .ok_or_else(|| RefError::InvalidName(input.to_owned()))?; + .ok_or_else(|| RefError::Unqualified(input.to_owned()))?; Ok((None, refname)) } diff --git a/radicle/src/rad.rs b/radicle/src/rad.rs index 1ec9cc42..2bb5447f 100644 --- a/radicle/src/rad.rs +++ b/radicle/src/rad.rs @@ -154,15 +154,13 @@ pub fn fork( ) -> Result<(), ForkError> { let me = signer.public_key(); let repository = storage.repository(proj)?; - let (canonical_id, project) = repository.project_identity()?; - let (canonical_head, _) = repository.head()?; + // TODO: We should get the id branch pointer from a stored canonical reference. + let (canonical_id, _) = repository.project_identity()?; + let (canonical_branch, canonical_head) = repository.head()?; let raw = repository.raw(); - // TODO: We should only get the project HEAD once we've stored the canonical identity - // branch on disk. This way it can use what we stored, instead of recomputing it. - raw.reference( - &git::refs::storage::branch(me, &project.default_branch), + &canonical_branch.with_namespace(me.into()), *canonical_head, false, &format!("creating default branch for {me}"), @@ -353,7 +351,7 @@ mod tests { .unwrap(); let project_repo = storage.repository(proj).unwrap(); - let (head, _) = project_repo.head().unwrap(); + let (_, head) = project_repo.head().unwrap(); // Test canonical refs. assert_eq!(project_repo.raw().refname_to_id("HEAD").unwrap(), *head); diff --git a/radicle/src/storage.rs b/radicle/src/storage.rs index 0d4bfe2e..e2eac4ae 100644 --- a/radicle/src/storage.rs +++ b/radicle/src/storage.rs @@ -242,10 +242,20 @@ pub trait ReadRepository { fn blob_at<'a>(&'a self, oid: Oid, path: &'a Path) -> Result, git_ext::Error>; - /// Get the canonical head of this repository. + /// Get the head of this repository. + /// + /// Returns the value of `HEAD` if it is set, otherwise computes the canonical head + /// using [`ReadRepository::canonical_head`]. /// /// Returns the [`Oid`] as well as the qualified reference name. - fn head(&self) -> Result<(Oid, Qualified), ProjectError>; + fn head(&self) -> Result<(Qualified, Oid), ProjectError>; + + /// Compute the canonical head of this repository. + /// + /// Ignores any existing `HEAD` reference. + /// + /// Returns the [`Oid`] as well as the qualified reference name. + fn canonical_head(&self) -> Result<(Qualified, Oid), ProjectError>; /// Get the `reference` for the given `remote`. /// diff --git a/radicle/src/storage/git.rs b/radicle/src/storage/git.rs index e40c3326..5b2a87c6 100644 --- a/radicle/src/storage/git.rs +++ b/radicle/src/storage/git.rs @@ -487,10 +487,18 @@ impl ReadRepository for Repository { Repository::project(self) } - fn head(&self) -> Result<(Oid, Qualified), ProjectError> { - // TODO: We shouldn't need to re-construct the history here; we should use the cached - // document head of the "trusted" (local) user for this project. - // In the `fork` function for example, we call Repository::project_identity again, + fn head(&self) -> Result<(Qualified, Oid), ProjectError> { + // If `HEAD` is already set locally, just return that. + if let Ok(head) = self.backend.head() { + if let Ok((name, oid)) = git::refs::qualified_from(&head) { + return Ok((name.to_owned(), oid)); + } + } + self.canonical_head() + } + + fn canonical_head(&self) -> Result<(Qualified, Oid), ProjectError> { + // TODO: In the `fork` function for example, we call Repository::project_identity again, // This should only be necessary once. let (_, project) = self.project_identity()?; let branch_ref = Qualified::from(lit::refs_heads(&project.default_branch)); @@ -509,7 +517,7 @@ impl ReadRepository for Repository { heads => raw.merge_base_many(heads), }?; - Ok((oid.into(), branch_ref)) + Ok((branch_ref, oid.into())) } } @@ -631,7 +639,7 @@ impl WriteRepository for Repository { fn set_head(&self) -> Result { let head_ref = refname!("HEAD"); - let (head, branch_ref) = self.head()?; + let (branch_ref, head) = self.canonical_head()?; log::debug!("Setting ref {:?} -> {:?}", &branch_ref, head); self.raw() diff --git a/radicle/src/test/storage.rs b/radicle/src/test/storage.rs index d485d0f5..c6beceac 100644 --- a/radicle/src/test/storage.rs +++ b/radicle/src/test/storage.rs @@ -78,7 +78,11 @@ impl ReadRepository for MockRepository { Ok(true) } - fn head(&self) -> Result<(Oid, fmt::Qualified), ProjectError> { + fn head(&self) -> Result<(fmt::Qualified, Oid), ProjectError> { + todo!() + } + + fn canonical_head(&self) -> Result<(fmt::Qualified, Oid), ProjectError> { todo!() }