use crate::cli::UI;
use crate::ops::oplog;
use anyhow::Result;
use std::path::Path;
pub fn list(path: &Path, sort: Option<&str>, pattern: Option<&str>, ui: &UI) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let tag_names = repo.tag_names(None)?;
let mut tags: Vec<String> = tag_names.iter().flatten().map(|s| s.to_string()).collect();
if let Some(pat) = pattern {
tags.retain(|t| glob_match(pat, t));
}
match sort {
Some("v:refname") => tags.sort_by(|a, b| version_cmp(a, b)),
Some("-v:refname") => tags.sort_by(|a, b| version_cmp(b, a)),
Some("-refname") => tags.sort_by(|a, b| b.cmp(a)),
Some("refname") => tags.sort(),
_ => {} }
for tag in &tags {
ui.list_item(tag);
}
Ok(())
}
fn glob_match(pattern: &str, text: &str) -> bool {
let p: Vec<char> = pattern.chars().collect();
let t: Vec<char> = text.chars().collect();
glob_match_inner(&p, &t, 0, 0)
}
fn glob_match_inner(pattern: &[char], text: &[char], mut pi: usize, mut ti: usize) -> bool {
while pi < pattern.len() {
match pattern[pi] {
'*' => {
while pi < pattern.len() && pattern[pi] == '*' {
pi += 1;
}
if pi == pattern.len() {
return true;
}
for i in ti..=text.len() {
if glob_match_inner(pattern, text, pi, i) {
return true;
}
}
return false;
}
'?' => {
if ti >= text.len() {
return false;
}
pi += 1;
ti += 1;
}
c => {
if ti >= text.len() || text[ti] != c {
return false;
}
pi += 1;
ti += 1;
}
}
}
ti == text.len()
}
fn version_cmp(a: &str, b: &str) -> std::cmp::Ordering {
let a = a.strip_prefix('v').unwrap_or(a);
let b = b.strip_prefix('v').unwrap_or(b);
let a_parts: Vec<&str> = a.split(['.', '-']).collect();
let b_parts: Vec<&str> = b.split(['.', '-']).collect();
for (ap, bp) in a_parts.iter().zip(b_parts.iter()) {
let ord = match (ap.parse::<u64>(), bp.parse::<u64>()) {
(Ok(an), Ok(bn)) => an.cmp(&bn),
_ => ap.cmp(bp),
};
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
a_parts.len().cmp(&b_parts.len())
}
pub fn create(
path: &Path,
name: &str,
message: Option<&str>,
target: Option<&str>,
ui: &UI,
) -> Result<()> {
oplog::with_oplog(path, "tag", &format!("create '{}'", name), || {
create_inner(path, name, message, target, ui)
})
}
fn create_inner(
path: &Path,
name: &str,
message: Option<&str>,
target: Option<&str>,
ui: &UI,
) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
let obj = if let Some(t) = target {
repo.revparse_single(t)?
} else {
repo.head()?.peel(git2::ObjectType::Commit)?
};
if let Some(msg) = message {
let sig = repo.signature()?;
repo.tag(name, &obj, &sig, msg, false)?;
ui.success(format!("Created annotated tag '{}'", name));
} else {
repo.tag_lightweight(name, &obj, false)?;
ui.success(format!("Created tag '{}'", name));
}
Ok(())
}
pub fn delete(path: &Path, name: &str, ui: &UI) -> Result<()> {
oplog::with_oplog(path, "tag", &format!("delete '{}'", name), || {
delete_inner(path, name, ui)
})
}
fn delete_inner(path: &Path, name: &str, ui: &UI) -> Result<()> {
let repo = crate::ops::open_repo(path)?;
repo.tag_delete(name)?;
ui.success(format!("Deleted tag '{}'", name));
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version_cmp() {
assert_eq!(version_cmp("v1.0.0", "v1.0.1"), std::cmp::Ordering::Less);
assert_eq!(version_cmp("v1.0.1", "v1.0.0"), std::cmp::Ordering::Greater);
assert_eq!(version_cmp("v1.0.0", "v1.0.0"), std::cmp::Ordering::Equal);
assert_eq!(version_cmp("v2.0.0", "v10.0.0"), std::cmp::Ordering::Less);
assert_eq!(version_cmp("1.0.0", "v1.0.0"), std::cmp::Ordering::Equal);
assert_eq!(version_cmp("v0.4.2", "v0.4.10"), std::cmp::Ordering::Less);
}
#[test]
fn test_glob_match_basic() {
assert!(glob_match("*", "anything"));
assert!(glob_match("v*", "v1.0.0"));
assert!(glob_match("v0.*", "v0.4.5"));
assert!(!glob_match("v1.*", "v0.4.5"));
assert!(glob_match("v0.4.*", "v0.4.5"));
assert!(!glob_match("v0.4.*", "v0.5.0"));
}
#[test]
fn test_glob_match_question_mark() {
assert!(glob_match("v?.0.0", "v1.0.0"));
assert!(!glob_match("v?.0.0", "v10.0.0"));
}
#[test]
fn test_glob_match_exact() {
assert!(glob_match("v1.0.0", "v1.0.0"));
assert!(!glob_match("v1.0.0", "v1.0.1"));
}
#[test]
fn test_glob_match_empty() {
assert!(glob_match("*", ""));
assert!(glob_match("", ""));
assert!(!glob_match("a", ""));
}
}