328 lines
11 KiB
Rust
328 lines
11 KiB
Rust
use std::error::Error;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::cli::{cli_exec, cli_exec_from_dir};
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct GitOptions {
|
|
bin: String,
|
|
workdir: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct CheckoutOptions {
|
|
// Checkout with -b if branch doesn't exist
|
|
pub create: bool,
|
|
#[serde(alias = "ref")]
|
|
pub git_ref: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct PushOptions {
|
|
pub rebase_to: Option<String>,
|
|
// If rebase, should be always set to true
|
|
pub force: bool,
|
|
pub brahcn: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct CommitOptions {
|
|
pub message: String,
|
|
pub add: bool,
|
|
}
|
|
|
|
impl GitOptions {
|
|
pub fn new(bin: String, workdir: Option<String>) -> Self {
|
|
Self { bin, workdir }
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
|
pub struct Git {
|
|
pub url: String,
|
|
pub repo_path: String,
|
|
}
|
|
|
|
impl Git {
|
|
pub fn new(url: String, repo_path: String) -> Self {
|
|
Self { url, repo_path }
|
|
}
|
|
|
|
pub fn clone(&self, git_opts: GitOptions) -> Result<(), Box<dyn Error>> {
|
|
let cmd = format!("{} clone {} {}", git_opts.bin, self.url, self.repo_path);
|
|
match self.exec(cmd, git_opts.workdir.clone()) {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
|
|
pub fn checkout(
|
|
&self,
|
|
git_opts: GitOptions,
|
|
opts: CheckoutOptions,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let cmd = format!(
|
|
"{} -C {} checkout {}",
|
|
git_opts.bin, self.repo_path, opts.git_ref
|
|
);
|
|
match self.exec(cmd, git_opts.workdir.clone()) {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => match opts.create {
|
|
true => {
|
|
let cmd = format!(
|
|
"{} -C {} checkout -b {}",
|
|
git_opts.bin, self.repo_path, opts.git_ref
|
|
);
|
|
match self.exec(cmd, git_opts.workdir.clone()) {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
false => Err(err),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn commit(&self, git_opts: GitOptions, opts: CommitOptions) -> Result<(), Box<dyn Error>> {
|
|
if opts.add {
|
|
let cmd = format!("{} -C {} add .", git_opts.bin, self.repo_path);
|
|
let _ = self.exec(cmd, git_opts.workdir.clone())?;
|
|
}
|
|
let cmd = format!(
|
|
"{} diff --cached --quiet || {} -C {} commit -m \"{}\"",
|
|
git_opts.bin, git_opts.bin, self.repo_path, opts.message
|
|
);
|
|
match self.exec(cmd, git_opts.workdir.clone()) {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
// TODO: Add tests for rebase and force
|
|
pub fn push(&self, git_opts: GitOptions, opts: PushOptions) -> Result<(), Box<dyn Error>> {
|
|
if let Some(branch) = opts.rebase_to {
|
|
let cmd = format!("{} -C {} rebase {}", git_opts.bin, self.repo_path, branch);
|
|
let _ = self.exec(cmd, git_opts.workdir.clone())?;
|
|
}
|
|
let mut args = String::new();
|
|
if opts.force {
|
|
args = format!("{} --force", args);
|
|
}
|
|
let cmd = format!(
|
|
"{} -C {} push --set-upstream origin {} {}",
|
|
git_opts.bin, self.repo_path, opts.brahcn, args
|
|
);
|
|
let _ = self.exec(cmd, git_opts.workdir.clone());
|
|
Ok(())
|
|
}
|
|
// TODO: Add tests
|
|
pub fn init(&self, git_opts: GitOptions) -> Result<(), Box<dyn Error>> {
|
|
let cmd = format!("{} -C {} init", git_opts.bin, self.repo_path);
|
|
let _ = self.exec(cmd, git_opts.workdir.clone())?;
|
|
let cmd = format!(
|
|
"{} -C {} remote add origin {}",
|
|
git_opts.bin, self.repo_path, self.url
|
|
);
|
|
let _ = self.exec(cmd, git_opts.workdir.clone())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn exec(&self, cmd: String, workdir: Option<String>) -> Result<String, Box<dyn Error>> {
|
|
match workdir {
|
|
Some(workdir) => cli_exec_from_dir(cmd, workdir),
|
|
None => cli_exec(cmd),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::cli::cli_exec_from_dir;
|
|
use crate::git::{CheckoutOptions, CommitOptions, Git, PushOptions};
|
|
use std::error::Error;
|
|
use std::path::Path;
|
|
|
|
use tempfile::tempdir;
|
|
|
|
use super::GitOptions;
|
|
|
|
fn prepare_a_repo() -> Result<String, Box<dyn Error>> {
|
|
let tmp_dir = tempdir()?
|
|
.into_path()
|
|
.into_os_string()
|
|
.into_string()
|
|
.unwrap();
|
|
cli_exec_from_dir("git init".to_string(), tmp_dir.clone())?;
|
|
Ok(tmp_dir)
|
|
}
|
|
|
|
fn prepare_a_workdir() -> Result<String, Box<dyn Error>> {
|
|
let tmp_dir = tempdir()?
|
|
.into_path()
|
|
.into_os_string()
|
|
.into_string()
|
|
.unwrap();
|
|
Ok(tmp_dir)
|
|
}
|
|
|
|
#[test]
|
|
fn test_pull_no_wd() -> Result<(), Box<dyn Error>> {
|
|
let tmp_dir = prepare_a_workdir()?;
|
|
let git_dir = format!("{}/test", tmp_dir);
|
|
let git = Git::new(prepare_a_repo()?, git_dir.clone());
|
|
let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
|
|
git.clone(git_opts)?;
|
|
let result = Path::new(&git_dir).exists();
|
|
assert!(result);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_pull_wd() -> Result<(), Box<dyn Error>> {
|
|
let tmp_dir = prepare_a_workdir()?;
|
|
let git_dir = "test".to_string();
|
|
println!("{}", tmp_dir.clone());
|
|
let git = Git::new(prepare_a_repo()?, git_dir.clone());
|
|
let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
|
|
git.clone(git_opts)?;
|
|
let result = Path::new(&format!("{}/{}", tmp_dir, git_dir)).exists();
|
|
assert!(result);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_checkout_no_create() -> Result<(), Box<dyn Error>> {
|
|
let tmp_dir = prepare_a_workdir()?;
|
|
let git_dir = "test".to_string();
|
|
let git = Git::new(prepare_a_repo()?, git_dir.clone());
|
|
let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
|
|
cli_exec_from_dir("git checkout -b test".to_string(), git.url.clone())?;
|
|
cli_exec_from_dir(
|
|
"touch test.txt && git add . && git commit -m test".to_string(),
|
|
git.url.clone(),
|
|
)?;
|
|
cli_exec_from_dir("git checkout -b main".to_string(), git.url.clone())?;
|
|
let checkout_options = CheckoutOptions {
|
|
create: false,
|
|
git_ref: "test".to_string(),
|
|
};
|
|
git.clone(git_opts.clone())?;
|
|
git.checkout(git_opts, checkout_options)?;
|
|
let result = cli_exec_from_dir(
|
|
"git rev-parse --abbrev-ref HEAD".to_string(),
|
|
format!("{}/{}", tmp_dir.clone(), git_dir.clone()),
|
|
)?;
|
|
assert_eq!(result, "test");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_checkout_no_create_err() -> Result<(), Box<dyn Error>> {
|
|
let tmp_dir = prepare_a_workdir()?;
|
|
let git_dir = "test".to_string();
|
|
let git = Git::new(prepare_a_repo()?, git_dir.clone());
|
|
let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
|
|
cli_exec_from_dir("git checkout -b main".to_string(), git.url.clone())?;
|
|
cli_exec_from_dir(
|
|
"touch test.txt && git add . && git commit -m test".to_string(),
|
|
git.url.clone(),
|
|
)?;
|
|
git.clone(git_opts.clone())?;
|
|
let checkout_options = CheckoutOptions {
|
|
create: false,
|
|
git_ref: "test".to_string(),
|
|
};
|
|
let res = git.checkout(git_opts, checkout_options);
|
|
assert!(res.is_err());
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_checkout_create() -> Result<(), Box<dyn Error>> {
|
|
let tmp_dir = prepare_a_workdir()?;
|
|
let git_dir = "test".to_string();
|
|
let git = Git::new(prepare_a_repo()?, git_dir.clone());
|
|
let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
|
|
cli_exec_from_dir("git checkout -b main".to_string(), git.url.clone())?;
|
|
cli_exec_from_dir(
|
|
"touch test.txt && git add . && git commit -m test".to_string(),
|
|
git.url.clone(),
|
|
)?;
|
|
git.clone(git_opts.clone())?;
|
|
let checkout_options = CheckoutOptions {
|
|
create: true,
|
|
git_ref: "test".to_string(),
|
|
};
|
|
git.checkout(git_opts, checkout_options)?;
|
|
let result = cli_exec_from_dir(
|
|
"git rev-parse --abbrev-ref HEAD".to_string(),
|
|
format!("{}/{}", tmp_dir.clone(), git_dir.clone()),
|
|
)?;
|
|
assert_eq!(result, "test");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit() -> Result<(), Box<dyn Error>> {
|
|
let tmp_dir = prepare_a_workdir()?;
|
|
let git_dir = "test".to_string();
|
|
let full_path = format!("{}/{}", tmp_dir.clone(), git_dir.clone());
|
|
let git = Git::new(prepare_a_repo()?, git_dir.clone());
|
|
let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
|
|
git.clone(git_opts.clone())?;
|
|
let commit_options = CommitOptions {
|
|
message: "test commit".to_string(),
|
|
add: false,
|
|
};
|
|
cli_exec_from_dir("touch test.txt && git add .".to_string(), full_path.clone())?;
|
|
git.commit(git_opts, commit_options)?;
|
|
let result = cli_exec_from_dir("git log --format=%B -n 1 HEAD".to_string(), full_path)?;
|
|
assert_eq!(result, "test commit\n");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_commit_add() -> Result<(), Box<dyn Error>> {
|
|
let tmp_dir = prepare_a_workdir()?;
|
|
let git_dir = "test".to_string();
|
|
let full_path = format!("{}/{}", tmp_dir.clone(), git_dir.clone());
|
|
let git = Git::new(prepare_a_repo()?, git_dir.clone());
|
|
let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
|
|
git.clone(git_opts.clone())?;
|
|
let commit_options = CommitOptions {
|
|
message: "test commit".to_string(),
|
|
add: true,
|
|
};
|
|
cli_exec_from_dir("touch test.txt".to_string(), full_path.clone())?;
|
|
git.commit(git_opts, commit_options)?;
|
|
let result = cli_exec_from_dir("git log --format=%B -n 1 HEAD".to_string(), full_path)?;
|
|
assert_eq!(result, "test commit\n");
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_push_no_rebase() -> Result<(), Box<dyn Error>> {
|
|
let tmp_dir = prepare_a_workdir()?;
|
|
let git_dir = "test".to_string();
|
|
let full_path = format!("{}/{}", tmp_dir.clone(), git_dir.clone());
|
|
let git = Git::new(prepare_a_repo()?, git_dir.clone());
|
|
let git_opts = GitOptions::new("git".to_string(), Some(tmp_dir.clone()));
|
|
git.clone(git_opts.clone())?;
|
|
let push_options = PushOptions {
|
|
rebase_to: None,
|
|
force: false,
|
|
brahcn: "main".to_string(),
|
|
};
|
|
cli_exec_from_dir("git checkout -b main".to_string(), full_path.clone())?;
|
|
cli_exec_from_dir(
|
|
"touch test.txt && git add . && git commit -m 'test commit'".to_string(),
|
|
full_path.clone(),
|
|
)?;
|
|
git.push(git_opts, push_options)?;
|
|
cli_exec_from_dir("git checkout main".to_string(), git.url.clone())?;
|
|
let result = cli_exec_from_dir("git log --format=%B -n 1 HEAD".to_string(), git.url)?;
|
|
assert_eq!(result, "test commit\n");
|
|
Ok(())
|
|
}
|
|
}
|