|
| 1 | +#![allow(dead_code)] |
1 | 2 | use super::CommitId;
|
2 | 3 | use crate::{error::Result, sync::commit_files::get_commit_diff};
|
3 |
| -use git2::{Commit, Oid, Repository}; |
| 4 | +use bitflags::bitflags; |
| 5 | +use fuzzy_matcher::FuzzyMatcher; |
| 6 | +use git2::{Commit, Diff, Oid, Repository}; |
4 | 7 | use std::{
|
5 | 8 | cmp::Ordering,
|
6 | 9 | collections::{BinaryHeap, HashSet},
|
@@ -55,6 +58,123 @@ pub fn diff_contains_file(file_path: String) -> LogWalkerFilter {
|
55 | 58 | ))
|
56 | 59 | }
|
57 | 60 |
|
| 61 | +// TODO: |
| 62 | +// fuzzy-match: |
| 63 | +// message, authors, file-names in diff, diff-content |
| 64 | +// |
| 65 | +// substr matching: |
| 66 | +// commit-sha, date |
| 67 | +bitflags! { |
| 68 | + /// |
| 69 | + pub struct FilterSearchOptions: u32 { |
| 70 | + /// |
| 71 | + const MESSAGE = 0b0000_0001; |
| 72 | + /// |
| 73 | + const FILENAMES = 0b0000_0010; |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +/// |
| 78 | +pub struct LogFilterSearch { |
| 79 | + /// |
| 80 | + pub matcher: fuzzy_matcher::skim::SkimMatcherV2, |
| 81 | + /// |
| 82 | + pub search_pattern: String, |
| 83 | + /// |
| 84 | + pub options: FilterSearchOptions, |
| 85 | +} |
| 86 | + |
| 87 | +impl LogFilterSearch { |
| 88 | + /// |
| 89 | + pub fn new( |
| 90 | + search_pattern: String, |
| 91 | + options: FilterSearchOptions, |
| 92 | + ) -> Self { |
| 93 | + Self { |
| 94 | + matcher: fuzzy_matcher::skim::SkimMatcherV2::default(), |
| 95 | + search_pattern, |
| 96 | + options, |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + fn match_diff(&self, diff: &Diff<'_>) -> bool { |
| 101 | + diff.deltas().any(|delta| { |
| 102 | + if delta |
| 103 | + .new_file() |
| 104 | + .path() |
| 105 | + .and_then(|file| file.as_os_str().to_str()) |
| 106 | + .map(|file| { |
| 107 | + self.matcher |
| 108 | + .fuzzy_match( |
| 109 | + file, |
| 110 | + self.search_pattern.as_str(), |
| 111 | + ) |
| 112 | + .is_some() |
| 113 | + }) |
| 114 | + .unwrap_or_default() |
| 115 | + { |
| 116 | + return true; |
| 117 | + } |
| 118 | + |
| 119 | + delta |
| 120 | + .old_file() |
| 121 | + .path() |
| 122 | + .and_then(|file| file.as_os_str().to_str()) |
| 123 | + .map(|file| { |
| 124 | + self.matcher |
| 125 | + .fuzzy_match( |
| 126 | + file, |
| 127 | + self.search_pattern.as_str(), |
| 128 | + ) |
| 129 | + .is_some() |
| 130 | + }) |
| 131 | + .unwrap_or_default() |
| 132 | + }) |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +/// |
| 137 | +pub fn filter_commit_by_search( |
| 138 | + filter: LogFilterSearch, |
| 139 | +) -> LogWalkerFilter { |
| 140 | + Arc::new(Box::new( |
| 141 | + move |repo: &Repository, |
| 142 | + commit_id: &CommitId| |
| 143 | + -> Result<bool> { |
| 144 | + let commit = repo.find_commit((*commit_id).into())?; |
| 145 | + |
| 146 | + let msg_match = filter |
| 147 | + .options |
| 148 | + .contains(FilterSearchOptions::MESSAGE) |
| 149 | + .then(|| { |
| 150 | + commit.message().and_then(|msg| { |
| 151 | + filter.matcher.fuzzy_match( |
| 152 | + msg, |
| 153 | + filter.search_pattern.as_str(), |
| 154 | + ) |
| 155 | + }) |
| 156 | + }) |
| 157 | + .flatten() |
| 158 | + .is_some(); |
| 159 | + |
| 160 | + let file_match = filter |
| 161 | + .options |
| 162 | + .contains(FilterSearchOptions::FILENAMES) |
| 163 | + .then(|| { |
| 164 | + get_commit_diff( |
| 165 | + repo, *commit_id, None, None, None, |
| 166 | + ) |
| 167 | + .ok() |
| 168 | + }) |
| 169 | + .flatten() |
| 170 | + .map(|diff| filter.match_diff(&diff)) |
| 171 | + .unwrap_or_default(); |
| 172 | + |
| 173 | + Ok(msg_match || file_match) |
| 174 | + }, |
| 175 | + )) |
| 176 | +} |
| 177 | + |
58 | 178 | ///
|
59 | 179 | pub struct LogWalker<'a> {
|
60 | 180 | commits: BinaryHeap<TimeOrderedCommit<'a>>,
|
@@ -130,11 +250,13 @@ impl<'a> LogWalker<'a> {
|
130 | 250 | mod tests {
|
131 | 251 | use super::*;
|
132 | 252 | use crate::error::Result;
|
| 253 | + use crate::sync::tests::write_commit_file; |
133 | 254 | use crate::sync::RepoPath;
|
134 | 255 | use crate::sync::{
|
135 | 256 | commit, get_commits_info, stage_add_file,
|
136 | 257 | tests::repo_init_empty,
|
137 | 258 | };
|
| 259 | + use fuzzy_matcher::skim::SkimMatcherV2; |
138 | 260 | use pretty_assertions::assert_eq;
|
139 | 261 | use std::{fs::File, io::Write, path::Path};
|
140 | 262 |
|
@@ -246,4 +368,47 @@ mod tests {
|
246 | 368 |
|
247 | 369 | Ok(())
|
248 | 370 | }
|
| 371 | + |
| 372 | + #[test] |
| 373 | + fn test_logwalker_with_filter_search() { |
| 374 | + let (_td, repo) = repo_init_empty().unwrap(); |
| 375 | + |
| 376 | + write_commit_file(&repo, "foo", "a", "commit1"); |
| 377 | + let second_commit_id = write_commit_file( |
| 378 | + &repo, |
| 379 | + "baz", |
| 380 | + "a", |
| 381 | + "my commit msg (#2)", |
| 382 | + ); |
| 383 | + write_commit_file(&repo, "foo", "b", "commit3"); |
| 384 | + |
| 385 | + let log_filter = filter_commit_by_search(LogFilterSearch { |
| 386 | + options: FilterSearchOptions::MESSAGE, |
| 387 | + matcher: SkimMatcherV2::default(), |
| 388 | + search_pattern: String::from("my msg"), |
| 389 | + }); |
| 390 | + |
| 391 | + let mut items = Vec::new(); |
| 392 | + let mut walker = LogWalker::new(&repo, 100) |
| 393 | + .unwrap() |
| 394 | + .filter(Some(log_filter)); |
| 395 | + walker.read(&mut items).unwrap(); |
| 396 | + |
| 397 | + assert_eq!(items.len(), 1); |
| 398 | + assert_eq!(items[0], second_commit_id); |
| 399 | + |
| 400 | + let log_filter = filter_commit_by_search(LogFilterSearch { |
| 401 | + options: FilterSearchOptions::FILENAMES, |
| 402 | + matcher: SkimMatcherV2::default(), |
| 403 | + search_pattern: String::from("fo"), |
| 404 | + }); |
| 405 | + |
| 406 | + let mut items = Vec::new(); |
| 407 | + let mut walker = LogWalker::new(&repo, 100) |
| 408 | + .unwrap() |
| 409 | + .filter(Some(log_filter)); |
| 410 | + walker.read(&mut items).unwrap(); |
| 411 | + |
| 412 | + assert_eq!(items.len(), 2); |
| 413 | + } |
249 | 414 | }
|
0 commit comments