Fix git head error handling and add symbolic head function

This commit is contained in:
Josh Junon 2024-02-08 14:55:46 +01:00 committed by GitButler
parent 5960c1e6f5
commit 8c1bfe0617
4 changed files with 81 additions and 17 deletions

View File

@ -567,7 +567,7 @@ impl<E: GitExecutor + 'static> crate::Repository for Repository<E> {
}
}
async fn head(&self) -> Result<Option<String>, crate::Error<Self::Error>> {
async fn head(&self) -> Result<String, crate::Error<Self::Error>> {
let args = vec!["-C", &self.path, "rev-parse", "HEAD"];
let (status, stdout, stderr) = self
@ -577,9 +577,49 @@ impl<E: GitExecutor + 'static> crate::Repository for Repository<E> {
.map_err(Error::<E>::Exec)?;
if status == 0 {
Ok(Some(stdout.to_owned()))
} else if status != 0 && stderr.to_lowercase().contains("ambiguous argument") {
Ok(None)
Ok(stdout.to_owned())
} else {
Err(Error::<E>::Failed {
status,
args: args.into_iter().map(Into::into).collect(),
stdout,
stderr,
})?
}
}
async fn symbolic_head(&self) -> Result<String, crate::Error<Self::Error>> {
let args = vec!["-C", &self.path, "symbolic-ref", "HEAD"];
let (status, stdout, stderr) = self
.exec
.execute(&args, None)
.await
.map_err(Error::<E>::Exec)?;
if status != 0 {
return Err(Error::<E>::Failed {
status,
args: args.into_iter().map(Into::into).collect(),
stdout,
stderr,
})?;
}
// now we try to rev-parse it because the Git CLI will always
// return the default branch as a ref/head/... even if there
// is nothing on that branch (no history).
let refname = stdout.to_owned();
let args = vec!["-C", &self.path, "rev-parse", "--verify", &refname];
let (status, stdout, stderr) = self
.exec
.execute(&args, None)
.await
.map_err(Error::<E>::Exec)?;
if status == 0 {
Ok(refname)
} else {
Err(Error::<E>::Failed {
status,

View File

@ -326,17 +326,22 @@ impl<R: ThreadedResource> crate::Repository for Repository<R> {
.await
}
async fn head(&self) -> Result<Option<String>, crate::Error<Self::Error>> {
async fn head(&self) -> Result<String, crate::Error<Self::Error>> {
self.repo
.with(|repo| {
let head = repo.head()?;
Ok(head
.symbolic_target()
.map(ToOwned::to_owned)
.or_else(|| head.target().map(|oid| oid.to_string())))
// We can unwrap here because we assert that the target of the
// `.target()` call is a direct reference due to calling
// `.resolve()` immediately before it.
Ok(repo.head()?.resolve()?.target().unwrap().to_string())
})
.await
.await
}
async fn symbolic_head(&self) -> Result<String, crate::Error<Self::Error>> {
self.repo
.with(|repo| Ok(String::from_utf8_lossy(repo.head()?.name_bytes()).to_string()))
.await
.await
}
}

View File

@ -58,6 +58,18 @@ macro_rules! gitbutler_git_integration_tests {
assert_eq!(repo.remote("origin").await.unwrap(), "https://example.com/test.git".to_owned());
}
async fn get_head_no_commits(repo) {
use crate::*;
assert!(repo.head().await.is_err());
}
async fn get_symbolic_head_no_commits(repo) {
use crate::*;
assert!(repo.symbolic_head().await.is_err());
}
// DO NOT ADD IO TESTS HERE. THIS IS THE WRONG SPOT.
}
$crate::private::test_impl! {
@ -124,10 +136,7 @@ macro_rules! gitbutler_git_integration_tests {
}).await
}
async fn get_head_no_commits(repo) {
use crate::*;
assert_eq!(repo.head().await.unwrap(), None);
}
// DO NOT ADD NON-IO TESTS HERE. THIS IS THE WRONG SPOT.
}
};
}

View File

@ -117,8 +117,18 @@ pub trait Repository {
async fn remote(&self, remote: &str) -> Result<String, Error<Self::Error>>;
/// Gets the current HEAD ref of the repository.
/// If the repository is empty, this will return `None`.
async fn head(&self) -> Result<Option<String>, Error<Self::Error>>;
///
/// Errors if the repository is empty.
async fn head(&self) -> Result<String, Error<Self::Error>>;
/// Gets the symbolic HEAD of the repository.
///
/// Returns `"HEAD"` if the current HEAD
/// is not a symbolic ref (e.g. a detached head state
/// or a direct reference to a commit).
///
/// Errors if the repository is empty.
async fn symbolic_head(&self) -> Result<String, Error<Self::Error>>;
}
/// Provides authentication credentials when performing