use std::path::{Path, PathBuf};
use anyhow::{Context, Error};
use git2::Repository;
#[derive(Debug, Clone)]
pub struct CommitInfo {
pub hash: String,
pub message: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RemoteInfo {
pub name: String,
pub url: String,
}
pub struct GitRepo {
path: PathBuf,
repo: Repository,
}
impl GitRepo {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
Ok(Self {
path: path.as_ref().to_path_buf(),
repo: Repository::open(path).context("Cannot open git repo at given path")?,
})
}
pub fn init<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path_ref = path.as_ref();
if Repository::open(path_ref).is_ok() {
return Err(anyhow::anyhow!("Directory is already a git repository"));
}
let repo = Repository::init(path_ref).context("Failed to initialize git repository")?;
let git_repo = Self {
path: path_ref.to_path_buf(),
repo,
};
git_repo
.repo
.set_head("refs/heads/master")
.context("Failed to set HEAD to master")?;
Ok(git_repo)
}
pub fn init_bare<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path_ref = path.as_ref();
if Repository::open(path_ref).is_ok() {
return Err(anyhow::anyhow!("Directory is already a git repository"));
}
let repo =
Repository::init_bare(path_ref).context("Failed to initialize bare git repository")?;
let git_repo = Self {
path: path_ref.to_path_buf(),
repo,
};
git_repo
.repo
.set_head("refs/heads/master")
.context("Failed to set HEAD to master")?;
Ok(git_repo)
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn is_bare(&self) -> bool {
self.repo.is_bare()
}
pub(crate) fn repo(&self) -> &Repository {
&self.repo
}
pub fn set_user_config(&self, name: &str, email: &str) -> Result<(), Error> {
let mut config = self
.repo()
.config()
.context("Failed to get repository config")?;
config
.set_str("user.name", name)
.context("Failed to set user.name")?;
config
.set_str("user.email", email)
.context("Failed to set user.email")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use git2::Repository;
use crate::{git::GitRepo, test_utils::RepoAssertions};
#[test]
fn open_works() {
let temp_dir = assert_fs::TempDir::new().unwrap();
let path = temp_dir.path();
Repository::init(path).unwrap();
let repo = GitRepo::open(path);
assert_eq!(repo.unwrap().path(), temp_dir.path());
}
#[test]
fn open_fails_in_non_git_folder() {
let temp_dir = assert_fs::TempDir::new().unwrap();
let path = temp_dir.path();
let repo = GitRepo::open(path);
assert!(repo.is_err());
}
#[test]
fn init_works() {
let temp_dir = assert_fs::TempDir::new().unwrap();
let path = temp_dir.path();
let repo = GitRepo::init(path).unwrap();
assert_eq!(repo.path(), temp_dir.path());
let branches = repo.get_all_branches().unwrap();
assert_eq!(branches.len(), 0);
repo.assert_current_branch("master");
}
#[test]
fn init_fails_in_git_folder() {
let temp_dir = assert_fs::TempDir::new().unwrap();
let path = temp_dir.path();
Repository::init(path).unwrap();
let repo = GitRepo::init(path);
assert!(repo.is_err());
}
#[test]
fn init_bare_works() {
let temp_dir = assert_fs::TempDir::new().unwrap();
let path = temp_dir.path();
let repo = GitRepo::init_bare(path).unwrap();
assert_eq!(repo.path(), temp_dir.path());
assert!(repo.is_bare());
let branches = repo.get_all_branches().unwrap();
assert_eq!(branches.len(), 0);
repo.assert_current_branch("master");
}
#[test]
fn init_bare_fails_in_git_folder() {
let temp_dir = assert_fs::TempDir::new().unwrap();
let path = temp_dir.path();
Repository::init(path).unwrap();
let repo = GitRepo::init_bare(path);
assert!(repo.is_err());
}
}