use std::collections::{HashSet, VecDeque};
use crate::objects::{parse_commit, parse_tag, ObjectId, ObjectKind};
use crate::repo::Repository;
#[must_use]
pub fn get_tag_annotation(repo: &Repository, oid: &ObjectId, n: u32) -> Option<String> {
if n == 0 {
return None;
}
let obj = repo.odb.read(oid).ok()?;
let tag = parse_tag(&obj.data).ok()?;
if tag.message.trim().is_empty() {
return None;
}
let lines: Vec<&str> = tag
.message
.lines()
.filter(|l| !l.trim().is_empty())
.take(n as usize)
.collect();
if lines.is_empty() {
return None;
}
Some(lines.join(" "))
}
#[must_use]
pub fn tag_contains(repo: &Repository, tag_oid: &ObjectId, target: &ObjectId) -> bool {
let commit_oid = match peel_to_commit(repo, tag_oid) {
Some(oid) => oid,
None => return false,
};
if &commit_oid == target {
return true;
}
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(commit_oid);
while let Some(oid) = queue.pop_front() {
if !visited.insert(oid) {
continue;
}
if &oid == target {
return true;
}
if let Ok(obj) = repo.odb.read(&oid) {
if obj.kind == ObjectKind::Commit {
if let Ok(commit) = parse_commit(&obj.data) {
for parent in commit.parents {
if !visited.contains(&parent) {
queue.push_back(parent);
}
}
}
}
}
}
false
}
#[must_use]
pub fn tag_points_at(repo: &Repository, tag_oid: &ObjectId, target: &ObjectId) -> bool {
if tag_oid == target {
return true;
}
let mut current = *tag_oid;
for _ in 0..10 {
let obj = match repo.odb.read(¤t) {
Ok(o) => o,
Err(_) => return false,
};
match obj.kind {
ObjectKind::Tag => {
let tag = match parse_tag(&obj.data) {
Ok(t) => t,
Err(_) => return false,
};
if &tag.object == target {
return true;
}
current = tag.object;
}
_ => return false,
}
}
false
}
#[must_use]
pub fn peel_to_commit(repo: &Repository, oid: &ObjectId) -> Option<ObjectId> {
let mut current = *oid;
for _ in 0..10 {
let obj = repo.odb.read(¤t).ok()?;
match obj.kind {
ObjectKind::Commit => return Some(current),
ObjectKind::Tag => {
let tag = parse_tag(&obj.data).ok()?;
current = tag.object;
}
_ => return None,
}
}
None
}
#[must_use]
pub fn creator_date(repo: &Repository, oid: &ObjectId) -> i64 {
let obj = match repo.odb.read(oid) {
Ok(o) => o,
Err(_) => return 0,
};
match obj.kind {
ObjectKind::Tag => {
if let Ok(tag) = parse_tag(&obj.data) {
if let Some(ref tagger) = tag.tagger {
return parse_epoch_from_ident(tagger);
}
}
0
}
ObjectKind::Commit => {
if let Ok(commit) = parse_commit(&obj.data) {
parse_epoch_from_ident(&commit.committer)
} else {
0
}
}
_ => 0,
}
}
#[must_use]
pub fn parse_epoch_from_ident(ident: &str) -> i64 {
let parts: Vec<&str> = ident.rsplitn(3, ' ').collect();
if parts.len() >= 2 {
parts[1].parse().unwrap_or(0)
} else {
0
}
}
#[must_use]
pub fn compare_version(a: &str, b: &str) -> std::cmp::Ordering {
let seg_a = version_segments(a);
let seg_b = version_segments(b);
for (sa, sb) in seg_a.iter().zip(seg_b.iter()) {
let ord = match (sa.parse::<u64>(), sb.parse::<u64>()) {
(Ok(na), Ok(nb)) => na.cmp(&nb),
_ => sa.cmp(sb),
};
if ord != std::cmp::Ordering::Equal {
return ord;
}
}
seg_a.len().cmp(&seg_b.len())
}
#[must_use]
pub fn version_segments(s: &str) -> Vec<&str> {
s.split(['.', '-']).filter(|seg| !seg.is_empty()).collect()
}
#[must_use]
pub fn strip_comments(s: &str) -> String {
let mut lines: Vec<String> = Vec::new();
for line in s.lines() {
if line.starts_with('#') {
continue;
}
lines.push(line.trim_end().to_string());
}
while lines.first().map(|l| l.is_empty()).unwrap_or(false) {
lines.remove(0);
}
while lines.last().map(|l| l.is_empty()).unwrap_or(false) {
lines.pop();
}
if lines.is_empty() {
return String::new();
}
let mut result = Vec::new();
let mut last_blank = false;
for line in &lines {
let is_blank = line.is_empty();
if is_blank && last_blank {
continue; }
result.push(line.clone());
last_blank = is_blank;
}
result.join("\n") + "\n"
}
#[must_use]
pub fn glob_matches(pattern: &str, name: &str) -> bool {
glob_match_bytes(pattern.as_bytes(), name.as_bytes())
}
fn glob_match_bytes(pat: &[u8], text: &[u8]) -> bool {
match (pat.first(), text.first()) {
(None, None) => true,
(Some(&b'*'), _) => {
let pat_rest = pat
.iter()
.position(|&b| b != b'*')
.map_or(&pat[pat.len()..], |i| &pat[i..]);
if pat_rest.is_empty() {
return true;
}
for i in 0..=text.len() {
if glob_match_bytes(pat_rest, &text[i..]) {
return true;
}
}
false
}
(Some(&b'?'), Some(_)) => glob_match_bytes(&pat[1..], &text[1..]),
(Some(p), Some(t)) if p == t => glob_match_bytes(&pat[1..], &text[1..]),
_ => false,
}
}