use anyhow::Context;
use git2::{ObjectType, RemoteCallbacks};
pub fn get_revision_for_tag(repository: &git2::Repository, tag: &str) -> anyhow::Result<String> {
let id = repository
.revparse_single(format!("refs/tags/{}", tag).as_str())
.context("Failed to find tag")?
.peel_to_commit()
.context("Failed to find commit")?
.id();
Ok(id.to_string())
}
pub fn tag(repository: &git2::Repository, tag: &str, message: &str) -> anyhow::Result<()> {
let signature = repository.signature().context("Failed to get signature")?;
let head = repository.head().context("Failed to get HEAD")?;
let commit = head
.peel(ObjectType::Commit)
.context("Failed to peel HEAD to commit")?;
let force = match get_revision_for_tag(repository, tag) {
Ok(revision) => {
if commit.id().to_string() == revision {
println!("Tag '{}' already exists for commit {}", tag, commit.id());
return Ok(());
} else {
println!(
"Updating existing tag '{}' to point to commit {}",
tag,
commit.id()
);
true
}
}
Err(_) => {
false
}
};
repository
.tag(tag, &commit, &signature, message, force)
.context("Failed to create tag")?;
Ok(())
}
pub fn push_tag(repository: &git2::Repository, token: &str, tag: &str) -> anyhow::Result<()> {
println!("Pushing tag '{}' to remote", tag);
let email_from_repo = |repository: &git2::Repository| -> anyhow::Result<String> {
let config = repository
.config()
.context("Failed to get repository config")?;
let email = config
.get_string("user.email")
.context("Failed to get user email")?;
Ok(email)
};
let email_from_global = || -> anyhow::Result<String> {
let config = git2::Config::open_default()?;
let email = config
.get_string("user.email")
.context("Failed to get global user email")?;
Ok(email)
};
let email = email_from_repo(repository)
.or_else(|_| email_from_global())
.context("Failed to get user email")?;
let mut remote = repository
.find_remote("origin")
.context("Failed to find remote 'origin'")?;
let mut push_opts = git2::PushOptions::new();
let mut cb = RemoteCallbacks::new();
cb.credentials(|_url, _username, _allowed_types| {
let created = git2::Cred::userpass_plaintext(&email, token)?;
Ok(created)
});
push_opts.remote_callbacks(cb);
remote
.push(
&[format!("refs/tags/{tag}:refs/tags/{tag}")],
Some(&mut push_opts),
)
.context("Failed to push tag to remote")?;
Ok(())
}
pub fn get_version_from_cliff_output(output: &[u8]) -> anyhow::Result<String> {
let value = serde_json::from_slice::<Vec<serde_json::Value>>(output)
.context("Unexpected output from git-cliff")?;
Ok(value
.first()
.context("No value in git-cliff output list")?
.as_object()
.context("Expected an object in git-cliff output list")?
.get("version")
.context("Expected 'version' in git-cliff output")?
.as_str()
.context("Expected a string as the version in git-cliff output")?
.to_string())
}
pub fn get_current_version_from_cargo_toml(content: &str) -> anyhow::Result<String> {
let cargo_toml = toml::from_str::<toml::Value>(content).context("Invalid TOML")?;
let get_version_from_table = |table: &toml::Value| -> anyhow::Result<String> {
Ok(table
.as_table()
.context("Expected root to be a table")?
.get("package")
.context("Expected 'package' in Cargo.toml")?
.as_table()
.context("Expected 'package' to be a table")?
.get("version")
.context("Expected 'version' in package table")?
.as_str()
.context("Expected a string as the version in package table")?
.to_string())
};
match cargo_toml
.as_table()
.context("Expected root to be a table")?
.get("workspace")
{
Some(workspace) => get_version_from_table(workspace),
None => get_version_from_table(&cargo_toml),
}
}