use std::path::Path;
use std::process::Command;
use semver::Version;
use crate::Result;
pub fn release(root: &Path, version: &Version) -> Result<()> {
let tag = format!("v{version}");
if !run(root, &["tag", "-a", &tag, "-m", &tag])? {
return Err(format!("`git tag -a {tag}` failed; nothing was pushed"));
}
for args in [&["push"][..], &["push", "--tags"][..]] {
if !run(root, args)? {
let step = format!("git {}", args.join(" "));
return Err(format!(
"`{step}` failed; {tag} is a tag on this machine only"
));
}
}
Ok(())
}
fn run(root: &Path, args: &[&str]) -> Result<bool> {
Command::new("git")
.args(args)
.current_dir(root)
.status()
.map(|done| done.success())
.map_err(|err| format!("cannot run `git {}`: {err}", args[0]))
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
const CONFIG: [&str; 8] = [
"-c",
"user.name=semver-sync",
"-c",
"user.email=semver-sync@example.com",
"-c",
"commit.gpgsign=false",
"-c",
"tag.gpgSign=false",
];
fn git(dir: &Path, args: &[&str]) -> String {
let done = Command::new("git")
.args(CONFIG)
.args(args)
.current_dir(dir)
.output()
.expect("run git");
assert!(
done.status.success(),
"`git {}` failed: {}",
args.join(" "),
String::from_utf8_lossy(&done.stderr)
);
String::from_utf8_lossy(&done.stdout).trim_end().to_string()
}
fn a_repo_with_somewhere_to_push(dir: &Path) -> (PathBuf, PathBuf) {
let repo = dir.join("repo");
let remote = dir.join("remote.git");
std::fs::create_dir_all(&repo).expect("create the repo dir");
let remote_name = remote.to_string_lossy().to_string();
git(dir, &["init", "--bare", "-q", &remote_name]);
git(&repo, &["init", "-q", "-b", "main"]);
std::fs::write(repo.join("Cargo.toml"), "[package]\n").expect("commit a file");
git(&repo, &["add", "."]);
git(&repo, &["commit", "-q", "-m", "one commit"]);
git(&repo, &["remote", "add", "origin", &remote_name]);
git(&repo, &["push", "-q", "--set-upstream", "origin", "main"]);
(repo, remote)
}
fn version() -> Version {
Version::parse("1.2.3").expect("a version")
}
#[test]
fn releasing_tags_the_version_and_sends_the_tag() {
let dirs = tempfile::tempdir().expect("temp dir");
let (repo, remote) = a_repo_with_somewhere_to_push(dirs.path());
release(&repo, &version()).expect("release");
assert_eq!(git(&remote, &["tag", "--list", "v1.2.3"]), "v1.2.3");
assert_eq!(git(&repo, &["cat-file", "-t", "v1.2.3"]), "tag");
assert_eq!(
git(
&repo,
&["for-each-ref", "refs/tags/v1.2.3", "--format=%(contents)"]
),
"v1.2.3"
);
}
#[test]
fn a_repo_with_nowhere_to_push_leaves_the_tag_local() {
let dirs = tempfile::tempdir().expect("temp dir");
let (repo, _) = a_repo_with_somewhere_to_push(dirs.path());
git(&repo, &["remote", "remove", "origin"]);
let err = release(&repo, &version()).expect_err("a push with no remote cannot work");
assert!(err.contains("git push"), "{err}");
assert_eq!(git(&repo, &["tag", "--list", "v1.2.3"]), "v1.2.3");
}
}