#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Commit {
pub short_hash: String,
pub subject: String,
}
#[must_use]
pub fn branch_leaf(repository: &gix::Repository) -> String {
repository.head_name().ok().flatten().map_or_else(
|| "HEAD".to_owned(),
|name| {
let short = name.shorten().to_string();
short.rsplit('/').next().unwrap_or(&short).to_owned()
},
)
}
pub fn commits_since(
repository: &gix::Repository,
base: &str,
) -> sysexits::Result<Vec<Commit>> {
let head = repository.head_commit().map_err(|reason| {
eprintln!("git-harvest: cannot read HEAD: {reason}");
sysexits::ExitCode::Usage
})?;
let base = repository.rev_parse_single(base).map_err(|reason| {
eprintln!("git-harvest: cannot resolve {base:?}: {reason}");
sysexits::ExitCode::Usage
})?;
let boundary =
repository
.merge_base(head.id(), base.detach())
.map_err(|reason| {
eprintln!(
"git-harvest: no shared history with the base: {reason}"
);
sysexits::ExitCode::Unavailable
})?;
let walk = repository
.rev_walk([head.id().detach()])
.with_boundary([boundary.detach()])
.all()
.map_err(|reason| {
eprintln!("git-harvest: cannot walk the history: {reason}");
sysexits::ExitCode::Software
})?;
let mut commits = Vec::new();
for step in walk {
let info = step.map_err(|reason| {
eprintln!("git-harvest: the history walk failed: {reason}");
sysexits::ExitCode::Software
})?;
if info.parent_ids.len() > 1 {
continue;
}
let commit = repository.find_commit(info.id).map_err(|reason| {
eprintln!("git-harvest: cannot read a commit: {reason}");
sysexits::ExitCode::Software
})?;
let message = commit.message().map_err(|reason| {
eprintln!("git-harvest: cannot read a commit message: {reason}");
sysexits::ExitCode::Software
})?;
commits.push(Commit {
short_hash: info.id.to_hex_with_len(7).to_string(),
subject: message.title.to_string().trim().to_owned(),
});
}
Ok(commits)
}
pub fn open() -> sysexits::Result<gix::Repository> {
gix::discover(".").map_err(|reason| {
eprintln!("git-harvest: not inside a Git repository: {reason}");
sysexits::ExitCode::Usage
})
}