1use crate::core::GitError;
2use gix::ObjectId;
3use std::path::{Path, PathBuf};
4
5pub fn open_repo(path: &Path) -> Result<gix::Repository, GitError> {
7 gix::open(path).map_err(|e| match e {
8 gix::open::Error::NotARepository { path, .. } => GitError::NotARepo { path },
9 other => GitError::Gix(Box::new(other)),
10 })
11}
12
13pub fn resolve_commit(repo: &gix::Repository, reference: &str) -> Result<ObjectId, GitError> {
15 let obj = repo
16 .rev_parse_single(reference.as_bytes())
17 .map_err(|e| GitError::ResolveRef {
18 reference: reference.to_string(),
19 reason: e.to_string(),
20 })?;
21 Ok(obj.detach())
22}
23
24pub fn changed_files(
27 repo: &gix::Repository,
28 commit_id: ObjectId,
29) -> Result<Vec<PathBuf>, GitError> {
30 let commit = repo
31 .find_commit(commit_id)
32 .map_err(|e| GitError::Operation(format!("find commit: {e}")))?;
33 let tree = commit
34 .tree()
35 .map_err(|e| GitError::Operation(format!("get tree: {e}")))?;
36
37 let parent_tree = match commit.parent_ids().next() {
38 Some(parent_id) => {
39 let parent = repo
40 .find_commit(parent_id.detach())
41 .map_err(|e| GitError::Operation(format!("find parent: {e}")))?;
42 Some(
43 parent
44 .tree()
45 .map_err(|e| GitError::Operation(format!("parent tree: {e}")))?,
46 )
47 }
48 None => None,
49 };
50
51 let changes = repo
52 .diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), None)
53 .map_err(|e| GitError::Operation(format!("diff: {e}")))?;
54
55 let paths: Vec<PathBuf> = changes
56 .iter()
57 .map(|change| PathBuf::from(change.location().to_string()))
58 .collect();
59
60 Ok(paths)
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66 use std::process::Command;
67
68 fn init_repo_with_commit(dir: &Path) -> ObjectId {
69 Command::new("git")
70 .args(["init", "-b", "main"])
71 .current_dir(dir)
72 .output()
73 .unwrap();
74 Command::new("git")
75 .args(["config", "user.email", "test@test.com"])
76 .current_dir(dir)
77 .output()
78 .unwrap();
79 Command::new("git")
80 .args(["config", "user.name", "Test"])
81 .current_dir(dir)
82 .output()
83 .unwrap();
84
85 std::fs::write(dir.join("hello.txt"), "hello").unwrap();
86 Command::new("git")
87 .args(["add", "."])
88 .current_dir(dir)
89 .output()
90 .unwrap();
91 Command::new("git")
92 .args(["commit", "-m", "initial"])
93 .current_dir(dir)
94 .output()
95 .unwrap();
96
97 let repo = open_repo(dir).unwrap();
98 resolve_commit(&repo, "HEAD").unwrap()
99 }
100
101 #[test]
102 fn open_valid_repo() {
103 let dir = tempfile::tempdir().unwrap();
104 Command::new("git")
105 .args(["init", "-b", "main"])
106 .current_dir(dir.path())
107 .output()
108 .unwrap();
109 assert!(open_repo(dir.path()).is_ok());
110 }
111
112 #[test]
113 fn open_not_a_repo() {
114 let dir = tempfile::tempdir().unwrap();
115 assert!(open_repo(dir.path()).is_err());
116 }
117
118 #[test]
119 fn initial_commit_changed_files() {
120 let dir = tempfile::tempdir().unwrap();
121 let id = init_repo_with_commit(dir.path());
122 let repo = open_repo(dir.path()).unwrap();
123 let files = changed_files(&repo, id).unwrap();
124 assert_eq!(files, vec![PathBuf::from("hello.txt")]);
125 }
126}