1pub mod prelude {
2 pub use super::{CommitExt, Commits, DiffExt, RepositoryExt};
3}
4
5use std::ops::ControlFlow;
6
7use git2::{
8 Commit, Diff, DiffDelta, DiffFormat, DiffHunk, DiffLine, DiffOptions, ErrorCode, Repository,
9 Revwalk, Sort, Tree,
10};
11
12use crate::GitError;
13
14pub trait WalkOutput {
15 fn finished(self) -> Result<bool, GitError>;
18}
19
20impl WalkOutput for () {
21 fn finished(self) -> Result<bool, GitError> {
22 Ok(false)
23 }
24}
25
26impl WalkOutput for bool {
27 fn finished(self) -> Result<bool, GitError> {
28 Ok(self)
29 }
30}
31
32impl WalkOutput for ControlFlow<()> {
33 fn finished(self) -> Result<bool, GitError> {
34 match self {
35 ControlFlow::Continue(()) => Ok(false),
36 ControlFlow::Break(()) => Ok(true),
37 }
38 }
39}
40
41impl<T> WalkOutput for Result<T, GitError>
42where
43 T: WalkOutput,
44{
45 fn finished(self) -> Result<bool, GitError> {
46 self?.finished()
47 }
48}
49
50pub trait RepositoryExt {
51 fn commits(&self) -> Result<Commits<'_>, GitError>;
52 fn count_commits(&self) -> Result<usize, GitError>;
53
54 fn walk_commits<T, F>(&self, mut f: F) -> Result<(), GitError>
55 where
56 F: FnMut(Commit<'_>) -> T,
57 T: WalkOutput,
58 {
59 for commit in self.commits()? {
60 if f(commit?).finished()? {
61 break;
62 }
63 }
64 Ok(())
65 }
66}
67
68impl RepositoryExt for Repository {
69 fn commits(&self) -> Result<Commits<'_>, GitError> {
70 Commits::new(self)
71 }
72
73 fn count_commits(&self) -> Result<usize, GitError> {
74 Ok(revwalk(self)?.count())
75 }
76}
77
78pub struct Commits<'a> {
79 repo: &'a Repository,
80 revwalk: Revwalk<'a>,
81}
82
83impl<'a> Commits<'a> {
84 fn new(repo: &'a Repository) -> Result<Self, GitError> {
85 let revwalk = revwalk(repo)?;
86 Ok(Self { repo, revwalk })
87 }
88}
89
90impl<'a> Iterator for Commits<'a> {
91 type Item = Result<git2::Commit<'a>, GitError>;
92
93 fn next(&mut self) -> Option<Self::Item> {
94 let oid = match self.revwalk.next()? {
95 Ok(oid) => oid,
96 Err(err) => return Some(Err(err)),
97 };
98 let commit = match self.repo.find_commit(oid) {
99 Ok(commit) => commit,
100 Err(err) => return Some(Err(err)),
101 };
102 Some(Ok(commit))
103 }
104}
105
106fn revwalk(repo: &Repository) -> Result<Revwalk<'_>, GitError> {
107 let mut revwalk = repo.revwalk()?;
108 revwalk.set_sorting(Sort::REVERSE | Sort::TIME)?;
109 revwalk.push_head()?;
110 Ok(revwalk)
111}
112
113pub trait CommitExt {
114 fn walk_diffs<T, F>(&self, repo: &Repository, f: F) -> Result<(), GitError>
115 where
116 F: FnMut(Diff<'_>) -> T,
117 T: WalkOutput;
118
119 fn walk_changes<T, F>(
120 &self,
121 repo: &Repository,
122 format: DiffFormat,
123 mut f: F,
124 ) -> Result<(), GitError>
125 where
126 F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> T,
127 T: WalkOutput,
128 {
129 self.walk_diffs(repo, |diff| diff.walk_changes(format, &mut f))
130 }
131}
132
133impl CommitExt for Commit<'_> {
134 fn walk_diffs<T, F>(&self, repo: &Repository, mut f: F) -> Result<(), GitError>
135 where
136 F: FnMut(Diff<'_>) -> T,
137 T: WalkOutput,
138 {
139 let new_tree = self.tree()?;
140 if self.parent_count() == 0 {
141 walk_diff(repo, None, Some(&new_tree), f)?;
142 } else {
143 for parent in self.parents() {
144 let old_tree = parent.tree()?;
145 walk_diff(repo, Some(&old_tree), Some(&new_tree), &mut f)?;
146 }
147 }
148 Ok(())
149 }
150}
151
152fn walk_diff<T, F>(
153 repo: &Repository,
154 old_tree: Option<&Tree<'_>>,
155 new_tree: Option<&Tree<'_>>,
156 f: F,
157) -> Result<(), GitError>
158where
159 F: FnOnce(Diff<'_>) -> T,
160 T: WalkOutput,
161{
162 let mut opts = DiffOptions::new();
163 opts.show_binary(true);
164
165 let mut diff = repo.diff_tree_to_tree(old_tree, new_tree, Some(&mut opts))?;
166 diff.find_similar(None)?;
167
168 f(diff).finished()?;
169
170 Ok(())
171}
172
173pub trait DiffExt {
174 fn walk_changes<T, F>(&self, format: DiffFormat, f: F) -> Result<(), GitError>
175 where
176 F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> T,
177 T: WalkOutput;
178}
179
180impl DiffExt for Diff<'_> {
181 fn walk_changes<T, F>(&self, format: DiffFormat, mut f: F) -> Result<(), GitError>
182 where
183 F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> T,
184 T: WalkOutput,
185 {
186 let mut error = None;
187 let res = self.print(format, |delta, hunk, line| {
188 match f(delta, hunk, line).finished() {
189 Ok(stop) => !stop,
190 Err(err) => {
191 debug_assert!(error.is_none());
192 error = Some(err);
193 false
194 }
195 }
196 });
197 match res {
198 Ok(()) => Ok(()),
199 Err(err) if err.code() == ErrorCode::User => match error {
200 Some(err) => Err(err),
201 None => Ok(()),
202 },
203 Err(err) => {
204 debug_assert!(error.is_none());
205 Err(err)
206 }
207 }
208 }
209}