diff --git a/Cargo.lock b/Cargo.lock index a5328e0..c527d2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,10 +253,12 @@ dependencies = [ "clap", "conventional_commit_parser", "dotenv", + "pretty_assertions", "reqwest", "semver", "serde", "serde_yaml", + "tempdir", "tokio", "tracing", "tracing-subscriber", @@ -264,6 +266,12 @@ dependencies = [ "url", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -352,6 +360,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures-channel" version = "0.3.28" @@ -861,6 +875,16 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -879,6 +903,43 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -932,6 +993,15 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "reqwest" version = "0.11.18" @@ -1176,6 +1246,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.7.0" @@ -1648,3 +1728,9 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index ea3d5cd..20add98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,8 @@ serde_yaml = {version = "*"} serde = {version = "*", features = ["derive"]} semver = "1.0.18" conventional_commit_parser = "0.9.4" - +tempdir = "*" reqwest = {version = "*"} -tracing-test = "*" \ No newline at end of file +tracing-test = "*" +pretty_assertions = "*" \ No newline at end of file diff --git a/crates/cuddle-please/Cargo.toml b/crates/cuddle-please/Cargo.toml index 8501045..019a583 100644 --- a/crates/cuddle-please/Cargo.toml +++ b/crates/cuddle-please/Cargo.toml @@ -16,7 +16,9 @@ reqwest = {workspace = true, features = ["blocking", "json"]} url.workspace = true semver.workspace = true conventional_commit_parser.workspace = true +tempdir.workspace = true [dev-dependencies] tracing-test = {workspace = true, features = ["no-env-filter"]} +pretty_assertions.workspace = true \ No newline at end of file diff --git a/crates/cuddle-please/src/git_client.rs b/crates/cuddle-please/src/git_client.rs index ec3f456..3a2b826 100644 --- a/crates/cuddle-please/src/git_client.rs +++ b/crates/cuddle-please/src/git_client.rs @@ -10,6 +10,7 @@ impl VcsClient { pub fn new_noop() -> VcsClient { Self::Noop {} } + pub fn new_git(path: &Path) -> anyhow::Result { if !path.to_path_buf().join(".git").exists() { anyhow::bail!("git directory not found in: {}", path.display().to_string()) @@ -19,4 +20,57 @@ impl VcsClient { source: path.to_path_buf(), }) } + + pub fn checkout_branch(&self) -> anyhow::Result<()> { + match self { + VcsClient::Noop {} => {} + VcsClient::Git { .. } => { + if let Err(e) = self.exec_git(&["branch", "-D", "cuddle-please/release"]) { + tracing::debug!("failed to cleaned up local branch for force-push, this may be because it didn't exist before running this command"); + } + self.exec_git(&["checkout", "-b", "cuddle-please/release"])?; + } + } + + Ok(()) + } + + fn exec_git(&self, args: &[&str]) -> anyhow::Result<()> { + match self { + VcsClient::Noop {} => {} + VcsClient::Git { source } => { + let checkout_branch = std::process::Command::new("git") + .current_dir(source.as_path()) + .args(args) + .output()?; + + let stdout = std::str::from_utf8(&checkout_branch.stdout)?; + let stderr = std::str::from_utf8(&checkout_branch.stderr)?; + tracing::debug!(stdout = stdout, stderr = stderr, "git {}", args.join(" ")); + } + } + + Ok(()) + } + + pub fn commit_and_push(&self, version: impl Into, dry_run: bool) -> anyhow::Result<()> { + match self { + VcsClient::Noop {} => {} + VcsClient::Git { .. } => { + self.exec_git(&["add", "."])?; + self.exec_git(&[ + "commit", + "-m", + &format!("chore(release): {}", version.into()), + ])?; + + tracing::trace!("git push -u -f origin cuddle-please/release"); + if !dry_run { + self.exec_git(&["push", "-u", "-f", "origin", "cuddle-please/release"])?; + } + } + } + + Ok(()) + } } diff --git a/crates/cuddle-please/tests/git.rs b/crates/cuddle-please/tests/git.rs new file mode 100644 index 0000000..a84bd83 --- /dev/null +++ b/crates/cuddle-please/tests/git.rs @@ -0,0 +1,182 @@ +use std::path::Path; + +use cuddle_please::git_client::VcsClient; +use pretty_assertions::assert_eq; +use tracing_test::traced_test; + +#[test] +#[traced_test] +fn exec_git_into_branch() { + let tempdir = tempdir::TempDir::new("exec_git_into_branch").unwrap(); + + setup_git(tempdir.path()).unwrap(); + + add_commit(tempdir.path(), "first").unwrap(); + add_tag(tempdir.path(), "1.0.0").unwrap(); + + add_commit(tempdir.path(), "second").unwrap(); + add_tag(tempdir.path(), "1.0.1").unwrap(); + + let vcs = VcsClient::new_git(tempdir.path()).unwrap(); + vcs.checkout_branch().unwrap(); + + let output = std::process::Command::new("git") + .arg("branch") + .current_dir(tempdir.path()) + .output() + .unwrap(); + + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + + assert_eq!( + "* cuddle-please/release + main +", + stdout + ); + assert_eq!("", stderr); +} + +#[test] +#[traced_test] +fn add_files_to_commit() { + let tempdir = tempdir::TempDir::new("add_files_to_commit").unwrap(); + + setup_git(tempdir.path()).unwrap(); + + add_commit(tempdir.path(), "first").unwrap(); + add_tag(tempdir.path(), "1.0.0").unwrap(); + + let vcs = VcsClient::new_git(tempdir.path()).unwrap(); + vcs.checkout_branch().unwrap(); + + std::fs::File::create(tempdir.path().join("changelog")).unwrap(); + vcs.commit_and_push("with some file", true).unwrap(); + + let output = std::process::Command::new("git") + .arg("log") + .current_dir(tempdir.path()) + .output() + .unwrap(); + + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + + assert!(stdout.contains("chore(release): with some file")); + assert_eq!("", stderr); +} +#[test] +#[traced_test] +fn reset_branch() { + let tempdir = tempdir::TempDir::new("add_files_to_commit").unwrap(); + + setup_git(tempdir.path()).unwrap(); + + add_commit(tempdir.path(), "first").unwrap(); + add_tag(tempdir.path(), "1.0.0").unwrap(); + + let vcs = VcsClient::new_git(tempdir.path()).unwrap(); + vcs.checkout_branch().unwrap(); + + std::fs::File::create(tempdir.path().join("changelog-first")).unwrap(); + vcs.commit_and_push("v1.0.0", true).unwrap(); + + exec_git(tempdir.path(), &["checkout", "main"]).unwrap(); + vcs.checkout_branch().unwrap(); + + std::fs::File::create(tempdir.path().join("changelog-second")).unwrap(); + vcs.commit_and_push("v1.0.1", true).unwrap(); + + let output = std::process::Command::new("git") + .arg("log") + .current_dir(tempdir.path()) + .output() + .unwrap(); + + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + + assert!(stdout.contains("chore(release): v1.0.1")); + assert!(!stdout.contains("chore(release): v1.0.0")); + assert_eq!("", stderr); +} + +fn add_tag(path: &Path, arg: &str) -> anyhow::Result<()> { + add_everything(path)?; + + let output = std::process::Command::new("git") + .arg("tag") + .arg("-a") + .arg(arg) + .arg("-m") + .arg(arg) + .current_dir(path) + .output()?; + + let stdout = std::str::from_utf8(&output.stdout)?; + let stderr = std::str::from_utf8(&output.stderr)?; + tracing::debug!(stdout = stdout, stderr = stderr, "git init"); + + Ok(()) +} + +fn add_commit(path: &Path, arg: &str) -> anyhow::Result<()> { + std::fs::File::create(path.join(arg))?; + + add_everything(path)?; + + let output = std::process::Command::new("git") + .arg("commit") + .arg("-m") + .arg(arg) + .current_dir(path) + .output()?; + + let stdout = std::str::from_utf8(&output.stdout)?; + let stderr = std::str::from_utf8(&output.stderr)?; + tracing::debug!(stdout = stdout, stderr = stderr, "git init"); + + Ok(()) +} + +fn add_everything(path: &Path) -> anyhow::Result<()> { + let output = std::process::Command::new("git") + .arg("add") + .arg(".") + .current_dir(path) + .output()?; + + let stdout = std::str::from_utf8(&output.stdout)?; + let stderr = std::str::from_utf8(&output.stderr)?; + tracing::debug!(stdout = stdout, stderr = stderr, "git add ."); + + Ok(()) +} + +fn setup_git(path: &Path) -> anyhow::Result<()> { + let output = std::process::Command::new("git") + .arg("init") + .arg(".") + .current_dir(path) + .output()?; + + let stdout = std::str::from_utf8(&output.stdout)?; + let stderr = std::str::from_utf8(&output.stderr)?; + tracing::debug!(stdout = stdout, stderr = stderr, "git init"); + + Ok(()) +} + +fn exec_git(path: &Path, args: &[&str]) -> anyhow::Result<()> { + let checkout_branch = std::process::Command::new("git") + .current_dir(path) + .args(args) + .output()?; + + let stdout = std::str::from_utf8(&checkout_branch.stdout)?; + let stderr = std::str::from_utf8(&checkout_branch.stderr)?; + tracing::debug!(stdout = stdout, stderr = stderr, "git {}", args.join(" ")); + + Ok(()) +}