Skip to content

Commit 3608480

Browse files
committed
use lines-changed-only when getting file changes
move helper function to gh-specific API
1 parent 1748f3f commit 3608480

File tree

10 files changed

+208
-145
lines changed

10 files changed

+208
-145
lines changed

Diff for: cpp-linter/src/cli/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -407,13 +407,20 @@ pub fn convert_extra_arg_val(args: &ArgMatches) -> Vec<String> {
407407
mod test {
408408
use clap::ArgMatches;
409409

410-
use super::{convert_extra_arg_val, get_arg_parser};
410+
use super::{convert_extra_arg_val, get_arg_parser, Cli};
411411

412412
fn parser_args(input: Vec<&str>) -> ArgMatches {
413413
let arg_parser = get_arg_parser();
414414
arg_parser.get_matches_from(input)
415415
}
416416

417+
#[test]
418+
fn ignore_blank_extensions() {
419+
let args = parser_args(vec!["cpp-linter", "-e", "c,,h"]);
420+
let cli = Cli::from(&args);
421+
assert!(!cli.extensions.contains(&"".to_string()));
422+
}
423+
417424
#[test]
418425
fn extra_arg_0() {
419426
let args = parser_args(vec!["cpp-linter"]);

Diff for: cpp-linter/src/cli/structs.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ impl LinesChangedOnly {
2525
_ => LinesChangedOnly::Off,
2626
}
2727
}
28+
29+
pub fn is_change_valid(&self, added_lines: bool, diff_chunks: bool) -> bool {
30+
match self {
31+
LinesChangedOnly::Off => true,
32+
LinesChangedOnly::Diff => diff_chunks,
33+
LinesChangedOnly::On => added_lines,
34+
}
35+
}
2836
}
2937

3038
impl Display for LinesChangedOnly {
@@ -91,7 +99,14 @@ impl From<&ArgMatches> for Cli {
9199
let extensions = args
92100
.get_many::<String>("extensions")
93101
.unwrap()
94-
.map(|s| s.to_string())
102+
.filter_map(|s| {
103+
if s.is_empty() {
104+
// filter out blank extensions here
105+
None
106+
} else {
107+
Some(s.to_string())
108+
}
109+
})
95110
.collect::<Vec<_>>();
96111

97112
Self {

Diff for: cpp-linter/src/common_fs/file_filter.rs

+9-15
Original file line numberDiff line numberDiff line change
@@ -129,23 +129,17 @@ impl FileFilter {
129129
/// - Is `entry` specified in the list of explicitly `not_ignored` paths? (supersedes
130130
/// specified `ignored` paths)
131131
pub fn is_source_or_ignored(&self, entry: &Path) -> bool {
132-
let extension = entry.extension();
133-
if extension.is_none() {
132+
let extension = entry
133+
.extension()
134+
.unwrap_or_default() // allow for matching files with no extension
135+
.to_string_lossy()
136+
.to_string();
137+
if !self.extensions.contains(&extension) {
134138
return false;
135139
}
136-
let mut is_ignored = true;
137-
for ext in &self.extensions {
138-
if ext == &extension.unwrap().to_os_string().into_string().unwrap() {
139-
is_ignored = false;
140-
break;
141-
}
142-
}
143-
if !is_ignored {
144-
let is_in_ignored = self.is_file_in_list(entry, true);
145-
let is_in_not_ignored = self.is_file_in_list(entry, false);
146-
if is_in_not_ignored || !is_in_ignored {
147-
return true;
148-
}
140+
let is_in_not_ignored = self.is_file_in_list(entry, false);
141+
if is_in_not_ignored || !self.is_file_in_list(entry, true) {
142+
return true;
149143
}
150144
false
151145
}

Diff for: cpp-linter/src/git.rs

+49-11
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ use anyhow::{Context, Result};
1515
use git2::{Diff, Error, Patch, Repository};
1616

1717
// project specific modules/crates
18-
use crate::common_fs::{FileFilter, FileObj};
18+
use crate::{
19+
cli::LinesChangedOnly,
20+
common_fs::{FileFilter, FileObj},
21+
};
1922

2023
/// This (re-)initializes the repository located in the specified `path`.
2124
///
@@ -100,7 +103,11 @@ fn parse_patch(patch: &Patch) -> (Vec<u32>, Vec<RangeInclusive<u32>>) {
100103
///
101104
/// The specified list of `extensions`, `ignored` and `not_ignored` files are used as
102105
/// filters to expedite the process and only focus on the data cpp_linter can use.
103-
pub fn parse_diff(diff: &git2::Diff, file_filter: &FileFilter) -> Vec<FileObj> {
106+
pub fn parse_diff(
107+
diff: &git2::Diff,
108+
file_filter: &FileFilter,
109+
lines_changed_only: &LinesChangedOnly,
110+
) -> Vec<FileObj> {
104111
let mut files: Vec<FileObj> = Vec::new();
105112
for file_idx in 0..diff.deltas().count() {
106113
let diff_delta = diff.get_delta(file_idx).unwrap();
@@ -112,7 +119,10 @@ pub fn parse_diff(diff: &git2::Diff, file_filter: &FileFilter) -> Vec<FileObj> {
112119
{
113120
let (added_lines, diff_chunks) =
114121
parse_patch(&Patch::from_diff(diff, file_idx).unwrap().unwrap());
115-
files.push(FileObj::from(file_path, added_lines, diff_chunks));
122+
if lines_changed_only.is_change_valid(!added_lines.is_empty(), !diff_chunks.is_empty())
123+
{
124+
files.push(FileObj::from(file_path, added_lines, diff_chunks));
125+
}
116126
}
117127
}
118128
files
@@ -125,12 +135,20 @@ pub fn parse_diff(diff: &git2::Diff, file_filter: &FileFilter) -> Vec<FileObj> {
125135
/// log warning and error are output when this occurs. Please report this instance for
126136
/// troubleshooting/diagnosis as this likely means the diff is malformed or there is a
127137
/// bug in libgit2 source.
128-
pub fn parse_diff_from_buf(buff: &[u8], file_filter: &FileFilter) -> Vec<FileObj> {
138+
pub fn parse_diff_from_buf(
139+
buff: &[u8],
140+
file_filter: &FileFilter,
141+
lines_changed_only: &LinesChangedOnly,
142+
) -> Vec<FileObj> {
129143
if let Ok(diff_obj) = &Diff::from_buffer(buff) {
130-
parse_diff(diff_obj, file_filter)
144+
parse_diff(diff_obj, file_filter, lines_changed_only)
131145
} else {
132146
log::warn!("libgit2 failed to parse the diff");
133-
brute_force_parse_diff::parse_diff(&String::from_utf8_lossy(buff), file_filter)
147+
brute_force_parse_diff::parse_diff(
148+
&String::from_utf8_lossy(buff),
149+
file_filter,
150+
lines_changed_only,
151+
)
134152
}
135153
}
136154

@@ -146,7 +164,10 @@ mod brute_force_parse_diff {
146164
use regex::Regex;
147165
use std::{ops::RangeInclusive, path::PathBuf};
148166

149-
use crate::common_fs::{FileFilter, FileObj};
167+
use crate::{
168+
cli::LinesChangedOnly,
169+
common_fs::{FileFilter, FileObj},
170+
};
150171

151172
fn get_filename_from_front_matter(front_matter: &str) -> Option<&str> {
152173
let diff_file_name = Regex::new(r"(?m)^\+\+\+\sb?/(.*)$").unwrap();
@@ -209,7 +230,11 @@ mod brute_force_parse_diff {
209230
(additions, diff_chunks)
210231
}
211232

212-
pub fn parse_diff(diff: &str, file_filter: &FileFilter) -> Vec<FileObj> {
233+
pub fn parse_diff(
234+
diff: &str,
235+
file_filter: &FileFilter,
236+
lines_changed_only: &LinesChangedOnly,
237+
) -> Vec<FileObj> {
213238
log::error!("Using brute force diff parsing!");
214239
let mut results = Vec::new();
215240
let diff_file_delimiter = Regex::new(r"(?m)^diff --git a/.*$").unwrap();
@@ -230,7 +255,11 @@ mod brute_force_parse_diff {
230255
let file_path = PathBuf::from(file_name);
231256
if file_filter.is_source_or_ignored(&file_path) {
232257
let (added_lines, diff_chunks) = parse_patch(&file_diff[hunk_start..]);
233-
results.push(FileObj::from(file_path, added_lines, diff_chunks));
258+
if lines_changed_only
259+
.is_change_valid(!added_lines.is_empty(), !diff_chunks.is_empty())
260+
{
261+
results.push(FileObj::from(file_path, added_lines, diff_chunks));
262+
}
234263
}
235264
}
236265
// } else {
@@ -247,6 +276,7 @@ mod brute_force_parse_diff {
247276

248277
use super::parse_diff;
249278
use crate::{
279+
cli::LinesChangedOnly,
250280
common_fs::{FileFilter, FileObj},
251281
git::parse_diff_from_buf,
252282
};
@@ -274,6 +304,7 @@ rename to /tests/demo/some source.c
274304
let files = parse_diff_from_buf(
275305
diff_buf,
276306
&FileFilter::new(&["target".to_string()], vec!["c".to_string()]),
307+
&LinesChangedOnly::Off,
277308
);
278309
assert!(!files.is_empty());
279310
assert!(files
@@ -289,6 +320,7 @@ rename to /tests/demo/some source.c
289320
let files = parse_diff_from_buf(
290321
diff_buf,
291322
&FileFilter::new(&["target".to_string()], vec!["c".to_string()]),
323+
&LinesChangedOnly::Off,
292324
);
293325
assert!(!files.is_empty());
294326
}
@@ -301,8 +333,13 @@ rename to /tests/demo/some source.c
301333
parse_diff_from_buf(
302334
buf.as_bytes(),
303335
&FileFilter::new(&ignore, extensions.to_owned()),
336+
&LinesChangedOnly::Off,
337+
),
338+
parse_diff(
339+
buf,
340+
&FileFilter::new(&ignore, extensions.to_owned()),
341+
&LinesChangedOnly::Off,
304342
),
305-
parse_diff(buf, &FileFilter::new(&ignore, extensions.to_owned())),
306343
)
307344
}
308345

@@ -377,6 +414,7 @@ mod test {
377414
use tempfile::{tempdir, TempDir};
378415

379416
use crate::{
417+
cli::LinesChangedOnly,
380418
common_fs::FileFilter,
381419
rest_api::{github::GithubApiClient, RestApiClient},
382420
};
@@ -406,7 +444,7 @@ mod test {
406444
env::set_var("CI", "false"); // avoid use of REST API when testing in CI
407445
rest_api_client
408446
.unwrap()
409-
.get_list_of_changed_files(&file_filter)
447+
.get_list_of_changed_files(&file_filter, &LinesChangedOnly::Off)
410448
.await
411449
.unwrap()
412450
}

Diff for: cpp-linter/src/rest_api/github/mod.rs

+16-60
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,19 @@ use reqwest::{
1414
header::{HeaderMap, HeaderValue, AUTHORIZATION},
1515
Client, Method, Url,
1616
};
17-
use serde_json;
1817

1918
// project specific modules/crates
2019
use super::{RestApiClient, RestApiRateLimitHeaders};
2120
use crate::clang_tools::clang_format::tally_format_advice;
2221
use crate::clang_tools::clang_tidy::tally_tidy_advice;
2322
use crate::clang_tools::ClangVersions;
24-
use crate::cli::{FeedbackInput, ThreadComments};
23+
use crate::cli::{FeedbackInput, LinesChangedOnly, ThreadComments};
2524
use crate::common_fs::{FileFilter, FileObj};
2625
use crate::git::{get_diff, open_repo, parse_diff, parse_diff_from_buf};
2726

2827
// private submodules.
2928
mod serde_structs;
3029
mod specific_api;
31-
use serde_structs::{GithubChangedFile, PushEventFiles};
3230

3331
/// A structure to work with Github REST API.
3432
pub struct GithubApiClient {
@@ -121,7 +119,11 @@ impl RestApiClient for GithubApiClient {
121119
Ok(headers)
122120
}
123121

124-
async fn get_list_of_changed_files(&self, file_filter: &FileFilter) -> Result<Vec<FileObj>> {
122+
async fn get_list_of_changed_files(
123+
&self,
124+
file_filter: &FileFilter,
125+
lines_changed_only: &LinesChangedOnly,
126+
) -> Result<Vec<FileObj>> {
125127
if env::var("CI").is_ok_and(|val| val.as_str() == "true")
126128
&& self.repo.is_some()
127129
&& self.sha.is_some()
@@ -153,9 +155,13 @@ impl RestApiClient for GithubApiClient {
153155
0,
154156
)
155157
.await
156-
.with_context(|| "Failed to get list of changed files from GitHub server.")?;
158+
.with_context(|| "Failed to get list of changed files.")?;
157159
if response.status().is_success() {
158-
Ok(parse_diff_from_buf(&response.bytes().await?, file_filter))
160+
Ok(parse_diff_from_buf(
161+
&response.bytes().await?,
162+
file_filter,
163+
lines_changed_only,
164+
))
159165
} else {
160166
let endpoint = if is_pr {
161167
Url::parse(format!("{}/files", url.as_str()).as_str())?
@@ -164,69 +170,19 @@ impl RestApiClient for GithubApiClient {
164170
};
165171
Self::log_response(response, "Failed to get full diff for event").await;
166172
log::debug!("Trying paginated request to {}", endpoint.as_str());
167-
self.get_changed_files_paginated(endpoint, file_filter)
173+
self.get_changed_files_paginated(endpoint, file_filter, lines_changed_only)
168174
.await
169175
}
170176
} else {
171177
// get diff from libgit2 API
172178
let repo = open_repo(".").with_context(|| {
173179
"Please ensure the repository is checked out before running cpp-linter."
174180
})?;
175-
let list = parse_diff(&get_diff(&repo)?, file_filter);
181+
let list = parse_diff(&get_diff(&repo)?, file_filter, lines_changed_only);
176182
Ok(list)
177183
}
178184
}
179185

180-
async fn get_changed_files_paginated(
181-
&self,
182-
url: Url,
183-
file_filter: &FileFilter,
184-
) -> Result<Vec<FileObj>> {
185-
let mut url = Some(Url::parse_with_params(url.as_str(), &[("page", "1")])?);
186-
let mut files = vec![];
187-
while let Some(ref endpoint) = url {
188-
let request =
189-
Self::make_api_request(&self.client, endpoint.as_str(), Method::GET, None, None)?;
190-
let response = Self::send_api_request(
191-
self.client.clone(),
192-
request,
193-
self.rate_limit_headers.clone(),
194-
0,
195-
)
196-
.await;
197-
if let Ok(response) = response {
198-
url = Self::try_next_page(response.headers());
199-
let files_list = if self.event_name != "pull_request" {
200-
let json_value: PushEventFiles = serde_json::from_str(&response.text().await?)
201-
.with_context(|| {
202-
"Failed to deserialize list of changed files from json response"
203-
})?;
204-
json_value.files
205-
} else {
206-
serde_json::from_str::<Vec<GithubChangedFile>>(&response.text().await?)
207-
.with_context(|| {
208-
"Failed to deserialize list of file changes from Pull Request event."
209-
})?
210-
};
211-
for file in files_list {
212-
if let Some(patch) = file.patch {
213-
let diff = format!(
214-
"diff --git a/{old} b/{new}\n--- a/{old}\n+++ b/{new}\n{patch}",
215-
old = file.previous_filename.unwrap_or(file.filename.clone()),
216-
new = file.filename,
217-
);
218-
if let Some(file_obj) =
219-
parse_diff_from_buf(diff.as_bytes(), file_filter).first()
220-
{
221-
files.push(file_obj.to_owned());
222-
}
223-
}
224-
}
225-
}
226-
}
227-
Ok(files)
228-
}
229-
230186
async fn post_feedback(
231187
&self,
232188
files: &[Arc<Mutex<FileObj>>],
@@ -319,7 +275,7 @@ mod test {
319275
clang_tidy::{TidyAdvice, TidyNotification},
320276
ClangVersions,
321277
},
322-
cli::FeedbackInput,
278+
cli::{FeedbackInput, LinesChangedOnly},
323279
common_fs::{FileFilter, FileObj},
324280
logger,
325281
rest_api::{RestApiClient, USER_OUTREACH},
@@ -478,7 +434,7 @@ mod test {
478434
env::set_current_dir(tmp_dir.path()).unwrap();
479435
let rest_client = GithubApiClient::new().unwrap();
480436
let files = rest_client
481-
.get_list_of_changed_files(&FileFilter::new(&[], vec![]))
437+
.get_list_of_changed_files(&FileFilter::new(&[], vec![]), &LinesChangedOnly::Off)
482438
.await;
483439
assert!(files.is_err())
484440
}

0 commit comments

Comments
 (0)