1use git2::{DiffOptions, Repository, StatusOptions};
2use crate::{Error, Result, GitConfig};
3
4pub struct GitRepo {
5 repo: Repository,
6 config: GitConfig,
7}
8
9impl GitRepo {
10 pub fn new(config: GitConfig) -> Result<Self> {
11 Ok(Self {
12 repo: Repository::open(&config.repo_path)?,
13 config,
14 })
15 }
16
17 pub fn get_diff(&self) -> Result<String> {
18 let mut diff_options = DiffOptions::new();
19 diff_options.include_untracked(self.config.include_untracked);
20
21 let diff = if self.is_initial_commit()? {
22 let empty_tree = self.repo.find_tree(self.repo.treebuilder(None)?.write()?)?;
24 let mut index = self.repo.index()?;
25 index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
26 index.write()?;
27 let tree_id = index.write_tree()?;
28 let tree = self.repo.find_tree(tree_id)?;
29 self.repo.diff_tree_to_tree(Some(&empty_tree), Some(&tree), Some(&mut diff_options))?
30 } else {
31 let head_tree = self.repo.head()?.peel_to_tree()?;
33 let mut index = self.repo.index()?;
34 index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
35 index.write()?;
36 let tree_id = index.write_tree()?;
37 let tree = self.repo.find_tree(tree_id)?;
38 self.repo.diff_tree_to_tree(Some(&head_tree), Some(&tree), Some(&mut diff_options))?
39 };
40
41 let mut diff_string = String::new();
42 diff.print(git2::DiffFormat::Patch, |_, _, line| {
43 diff_string.push_str(&String::from_utf8_lossy(line.content()));
44 true
45 })?;
46
47 if diff_string.is_empty() {
48 return Err(Error::NoChanges);
49 }
50
51 Ok(diff_string)
52 }
53
54 fn is_initial_commit(&self) -> Result<bool> {
55 Ok(self.repo.head().is_err())
56 }
57
58 pub fn has_changes(&self) -> Result<bool> {
59 let mut status_options = StatusOptions::new();
60 status_options.include_untracked(self.config.include_untracked);
61
62 let statuses = self.repo.statuses(Some(&mut status_options))?;
63 Ok(!statuses.is_empty())
64 }
65
66 pub fn commit(&self, message: &str) -> Result<()> {
67 self.stage_all()?;
69
70 let mut index = self.repo.index()?;
71 let tree_id = index.write_tree()?;
72 let tree = self.repo.find_tree(tree_id)?;
73
74 let signature = self.repo.signature()?;
75 let parent = match self.repo.head() {
76 Ok(head) => Some(head.peel_to_commit()?),
77 Err(_) => None,
78 };
79
80 let parents = parent.as_ref().map(|p| vec![p]).unwrap_or_default();
81 self.repo.commit(
82 Some("HEAD"),
83 &signature,
84 &signature,
85 message,
86 &tree,
87 parents.as_slice(),
88 )?;
89
90 Ok(())
91 }
92
93 pub fn stage_all(&self) -> Result<()> {
94 let mut index = self.repo.index()?;
95 index.add_all(["*"].iter(), git2::IndexAddOption::DEFAULT, None)?;
96 index.write()?;
97 Ok(())
98 }
99}