///|
/// Raw output of the git commands, as `gen` captured them.
///
/// Split from `GitInfo` so the "what does this string mean" part can be tested
/// without a git repository: `gen` runs the commands, `gitinfo` only reads.
pub(all) struct RawGitInfo {
branch : String
tag : String
describe : String
commit : String
short_commit : String
commit_date : String
author : String
email : String
/// `status --porcelain --untracked-files=no`: empty means clean.
status : String
/// `rev-list --left-right --count HEAD...@{upstream}`, `\t`.
/// Empty when there is no upstream.
upstream_count : String
}
///|
pub fn RawGitInfo::empty() -> RawGitInfo {
{
branch: "",
tag: "",
describe: "",
commit: "",
short_commit: "",
commit_date: "",
author: "",
email: "",
status: "",
upstream_count: "",
}
}
///|
/// Whitespace off both ends, everything else kept: author names may contain
/// spaces, and an empty output is normal (no tag, no upstream).
///
/// Takes a `StringView` so `split` results do not have to be copied first.
fn trim(s : StringView) -> String {
let chars = s.to_array()
let mut end = chars.length()
while end > 0 && is_space_char(chars[end - 1]) {
end = end - 1
}
let mut start = 0
while start < end && is_space_char(chars[start]) {
start = start + 1
}
let buf = StringBuilder()
for i in start.. Bool {
ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}
///|
/// A run of ASCII digits, or 0 for anything else — the output comes from git,
/// so a sign or a stray word means "no answer", not a number to salvage.
fn digit_count(field : StringView) -> Int32 {
if field.is_empty() {
return 0
}
for ch in field {
guard ch >= '0' && ch <= '9' else { return 0 }
}
@string.parse_int(field) catch {
_ => 0
}
}
///|
/// Parse `\t`; anything unparsable is `(0, 0)`.
fn parse_counts(raw : String) -> (Int32, Int32) {
let fields = trim(raw)
.split("\t")
.map(fn(part) { trim(part) })
.filter(fn(part) { !part.is_empty() })
.collect()
if fields.length() != 2 {
return (0, 0)
}
(digit_count(fields[0]), digit_count(fields[1]))
}
///|
/// Interpret the captured output. `available` is decided by the caller: only it
/// knows whether git actually ran.
pub fn RawGitInfo::to_git_info(self : RawGitInfo) -> GitInfo {
let (ahead, behind) = parse_counts(self.upstream_count)
{
available: true,
branch: trim(self.branch),
tag: trim(self.tag),
describe: if trim(self.describe).is_empty() {
// `describe --always` always prints something, but a generator run from
// a foreign shell could hand us blanks; short_commit is the fallback.
trim(self.short_commit)
} else {
trim(self.describe)
},
commit: trim(self.commit),
short_commit: trim(self.short_commit),
commit_date: trim(self.commit_date),
author: trim(self.author),
email: trim(self.email),
dirty: !trim(self.status).is_empty(),
ahead,
behind,
}
}