gnostr_asyncgit/sync/
commit_filter.rs1use std::sync::Arc;
2
3use bitflags::bitflags;
4use fuzzy_matcher::FuzzyMatcher;
5use git2::{Diff, Repository};
6
7use super::{commit_files::get_commit_diff, CommitId};
8use crate::error::Result;
9
10pub type SharedCommitFilterFn =
12 Arc<Box<dyn Fn(&Repository, &CommitId) -> Result<bool> + Send + Sync>>;
13
14pub fn diff_contains_file(file_path: String) -> SharedCommitFilterFn {
16 Arc::new(Box::new(
17 move |repo: &Repository, commit_id: &CommitId| -> Result<bool> {
18 let diff = get_commit_diff(repo, *commit_id, Some(file_path.clone()), None, None)?;
19
20 let contains_file = diff.deltas().len() > 0;
21
22 Ok(contains_file)
23 },
24 ))
25}
26
27bitflags! {
28 #[derive(Debug, Clone, Copy)]
30 pub struct SearchFields: u32 {
31 const MESSAGE_SUMMARY = 1 << 0;
33 const MESSAGE_BODY = 1 << 1;
35 const FILENAMES = 1 << 2;
37 const AUTHORS = 1 << 3;
39 }
46}
47
48impl Default for SearchFields {
49 fn default() -> Self {
50 Self::MESSAGE_SUMMARY
51 }
52}
53
54bitflags! {
55 #[derive(Debug, Clone, Copy)]
57 pub struct SearchOptions: u32 {
58 const CASE_SENSITIVE = 1 << 0;
60 const FUZZY_SEARCH = 1 << 1;
62 }
63}
64
65impl Default for SearchOptions {
66 fn default() -> Self {
67 Self::empty()
68 }
69}
70
71#[derive(Default, Debug, Clone)]
73pub struct LogFilterSearchOptions {
74 pub search_pattern: String,
76 pub fields: SearchFields,
78 pub options: SearchOptions,
80}
81
82#[derive(Default)]
84pub struct LogFilterSearch {
85 pub matcher: fuzzy_matcher::skim::SkimMatcherV2,
87 pub options: LogFilterSearchOptions,
89}
90
91impl LogFilterSearch {
92 pub fn new(options: LogFilterSearchOptions) -> Self {
94 let mut options = options;
95 if !options.options.contains(SearchOptions::CASE_SENSITIVE) {
96 options.search_pattern = options.search_pattern.to_lowercase();
97 }
98 Self {
99 matcher: fuzzy_matcher::skim::SkimMatcherV2::default(),
100 options,
101 }
102 }
103
104 fn match_diff(&self, diff: &Diff<'_>) -> bool {
105 diff.deltas().any(|delta| {
106 if delta
107 .new_file()
108 .path()
109 .and_then(|file| file.as_os_str().to_str())
110 .is_some_and(|file| self.match_text(file))
111 {
112 return true;
113 }
114
115 delta
116 .old_file()
117 .path()
118 .and_then(|file| file.as_os_str().to_str())
119 .is_some_and(|file| self.match_text(file))
120 })
121 }
122
123 pub fn match_text(&self, text: &str) -> bool {
125 if self.options.options.contains(SearchOptions::FUZZY_SEARCH) {
126 self.matcher
127 .fuzzy_match(text, self.options.search_pattern.as_str())
128 .is_some()
129 } else if self.options.options.contains(SearchOptions::CASE_SENSITIVE) {
130 text.contains(self.options.search_pattern.as_str())
131 } else {
132 text.to_lowercase()
133 .contains(self.options.search_pattern.as_str())
134 }
135 }
136}
137
138pub fn filter_commit_by_search(filter: LogFilterSearch) -> SharedCommitFilterFn {
140 Arc::new(Box::new(
141 move |repo: &Repository, commit_id: &CommitId| -> Result<bool> {
142 let commit = repo.find_commit((*commit_id).into())?;
143
144 let msg_summary_match = filter
145 .options
146 .fields
147 .contains(SearchFields::MESSAGE_SUMMARY)
148 .then(|| commit.summary().map(|msg| filter.match_text(msg)))
149 .flatten()
150 .unwrap_or_default();
151
152 let msg_body_match = filter
153 .options
154 .fields
155 .contains(SearchFields::MESSAGE_BODY)
156 .then(|| commit.body().map(|msg| filter.match_text(msg)))
157 .flatten()
158 .unwrap_or_default();
159
160 let file_match = filter
161 .options
162 .fields
163 .contains(SearchFields::FILENAMES)
164 .then(|| get_commit_diff(repo, *commit_id, None, None, None).ok())
165 .flatten()
166 .is_some_and(|diff| filter.match_diff(&diff));
167
168 let authors_match = filter
169 .options
170 .fields
171 .contains(SearchFields::AUTHORS)
172 .then(|| {
173 let name_match = commit
174 .author()
175 .name()
176 .is_some_and(|name| filter.match_text(name));
177 let mail_match = commit
178 .author()
179 .email()
180 .is_some_and(|name| filter.match_text(name));
181
182 name_match || mail_match
183 })
184 .unwrap_or_default();
185
186 Ok(msg_summary_match || msg_body_match || file_match || authors_match)
187 },
188 ))
189}