specfold_core/workspace/vcs/
history.rs1use super::repo::{git, nearest_repo};
4use std::path::Path;
5
6pub struct CommitText {
7 pub hash: String,
8 pub author: String,
9 pub message: String,
10}
11
12pub fn commits(root: &Path, range: &str) -> Option<Vec<CommitText>> {
14 let out = git(root, &["log", "--format=%H%x00%an <%ae>%x00%B%x1e", range])?;
15 let text = String::from_utf8_lossy(&out).into_owned();
16 Some(
17 text.split('\u{1e}')
18 .filter_map(|rec| {
19 let rec = rec.trim_start_matches('\n');
20 let mut parts = rec.splitn(3, '\0');
21 let hash = parts.next()?.to_string();
22 if hash.is_empty() {
23 return None;
24 }
25 Some(CommitText {
26 hash,
27 author: parts.next()?.to_string(),
28 message: parts.next().unwrap_or("").to_string(),
29 })
30 })
31 .collect(),
32 )
33}
34
35pub fn last_change(root: &Path, paths: &[String]) -> Option<String> {
37 let mut args: Vec<String> = vec!["log".into(), "-1".into(), "--format=%cs".into(), "--".into()];
38 args.extend(paths.iter().map(|p| if p.contains(['*', '?', '[']) { format!(":(glob){p}") } else { p.clone() }));
39 let refs: Vec<&str> = args.iter().map(String::as_str).collect();
40 let out = git(root, &refs)?;
41 let s = String::from_utf8_lossy(&out).trim().to_string();
42 if s.is_empty() { None } else { Some(s) }
43}
44
45pub fn commits_since(root: &Path, since: &str, paths: &[String], exclude: &[String]) -> Option<Vec<String>> {
48 git(root, &["cat-file", "-e", &format!("{since}^{{commit}}")])?;
49 let range = format!("{since}..HEAD");
50 let mut args: Vec<String> = vec!["log".into(), "--format=%h".into(), range, "--".into()];
51 args.extend(paths.iter().map(|p| if p.contains(['*', '?', '[']) { format!(":(glob){p}") } else { p.clone() }));
52 args.extend(exclude.iter().map(|e| format!(":(exclude,glob){e}")));
53 let refs: Vec<&str> = args.iter().map(String::as_str).collect();
54 let out = git(root, &refs)?;
55 Some(String::from_utf8_lossy(&out).lines().map(str::to_string).filter(|l| !l.is_empty()).collect())
56}
57
58pub struct Revision {
59 pub hash: String,
60 pub date: String,
61 pub author: String,
62 pub path: String,
64}
65
66pub fn history(root: &Path, path: &str) -> Option<Vec<Revision>> {
68 let (repo, rel, prefix) = nearest_repo(root, path);
69 let out = git(&repo, &["log", "--follow", "--format=%x1e%H%x00%cs%x00%an", "--name-only", "--", &rel])?;
70 let text = String::from_utf8_lossy(&out).into_owned();
71 let mut revs: Vec<Revision> = text
72 .split('\u{1e}')
73 .filter_map(|rec| {
74 let mut lines = rec.lines();
75 let head = lines.next()?;
76 let mut parts = head.split('\0');
77 let hash = parts.next()?.to_string();
78 let date = parts.next()?.to_string();
79 let author = parts.next().unwrap_or("").to_string();
80 let path = format!("{prefix}{}", lines.find(|l| !l.trim().is_empty())?.trim());
81 Some(Revision { hash, date, author, path })
82 })
83 .collect();
84 revs.reverse();
85 Some(revs)
86}