Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ flamegraph.svg
/.markdownlint.json
/.rustfmt.toml
/.yamllint.yaml
tmp/
tmp/

docs/developer/plans
66 changes: 65 additions & 1 deletion qlty-cli/src/commands/coverage/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,14 @@ impl Publish {
}

self.print_section_header(" COVERAGE DATA ");

if let Some(ref git_path) = report.git_repo_path {
eprintln!(" Git Repository: {}", style(git_path).dim());
} else {
eprintln!(" Git Repository: {}", style("not found").dim());
}
eprintln!();

if report.auto_path_fixing_enabled {
eprintln!(" Auto-path fixing: Enabled");
}
Expand Down Expand Up @@ -497,7 +505,7 @@ impl Publish {
let (paths_to_show, show_all) = if self.verbose {
(missing_files.len(), true)
} else {
(std::cmp::min(20, missing_files.len()), false)
(std::cmp::min(50, missing_files.len()), false)
};

eprintln!("\n {}\n", style("Missing code files:").bold().yellow());
Expand Down Expand Up @@ -552,6 +560,62 @@ impl Publish {
);
}

if !report.untracked_files.is_empty() && report.git_repo_path.is_some() {
let mut untracked = report.untracked_files.iter().collect::<Vec<_>>();
untracked.sort();

eprintln!(
" {} {} on disk but {} in Git",
style(untracked.len().to_formatted_string(&Locale::en)).red(),
if untracked.len() == 1 {
"path exists"
} else {
"paths exist"
},
style("not tracked").red()
);

let (paths_to_show, show_all) = if self.verbose {
(untracked.len(), true)
} else {
(std::cmp::min(50, untracked.len()), false)
};

eprintln!(
"\n {}\n",
style("Untracked code files (not in Git):").bold().red()
);

for path in untracked.iter().take(paths_to_show) {
eprintln!(
" {} {}",
style("*").red(),
style(path.to_string()).red()
);
}

if !show_all && paths_to_show < untracked.len() {
let remaining = untracked.len() - paths_to_show;
eprintln!(
" {} {}",
style(format!(
"... and {} more",
remaining.to_formatted_string(&Locale::en)
))
.dim()
.red(),
style("(Use --verbose to see all)").dim()
);
}

eprintln!();
eprintln!(
" {} Files not tracked in Git may not appear in coverage reports.",
style("NOTE:").bold()
);
eprintln!();
}

eprintln!();

// Get formatted numbers first
Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/basic.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: [CWD]/

Auto-path fixing: Enabled
1 unique code file path
All code files in the coverage data were found on disk (count: 1).
Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/discover_java_src_dirs.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: [CWD]/

Auto-path fixing: Enabled
1 unique code file path
All code files in the coverage data were found on disk (count: 1).
Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/files_exist.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: [CWD]/

Auto-path fixing: Enabled
WARNING: 1 code file path was excluded by configuration rules

Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/ignore_patterns.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: [CWD]/

Auto-path fixing: Enabled
2 unique code file paths
2 paths are missing on disk (100.0%)
Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/json.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: [CWD]/

1 unique code file path
1 path is missing on disk (100.0%)

Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/override_commit_time.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: [CWD]/

Auto-path fixing: Enabled
1 unique code file path
1 path is missing on disk (100.0%)
Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/override_git_tag.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: /Users/bhelmkamp/p/qltysh/qlty/

Auto-path fixing: Enabled
1 unique code file path
1 path is missing on disk (100.0%)
Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/overrides.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ WARNING: --transform-add-prefix is deprecated, use --add-prefix instead

COVERAGE DATA

Git Repository: [CWD]/

1 unique code file path
1 path is missing on disk (100.0%)

Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/coverage/publish_validate.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: [CWD]/

Auto-path fixing: Enabled
2 unique code file paths
1 path is missing on disk (50.0%)
Expand Down
2 changes: 2 additions & 0 deletions qlty-cli/tests/cmd/without_git/basic_coverage.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ https://qlty.sh/d/coverage

COVERAGE DATA

Git Repository: not found

1 unique code file path
1 path is missing on disk (100.0%)

Expand Down
98 changes: 97 additions & 1 deletion qlty-coverage/src/git.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{Context as _, Result};
use git2::Repository;
use tracing::{error, warn};
use std::collections::HashSet;
use tracing::{error, info, warn};

#[derive(Debug, Clone)]
pub struct CommitMetadata {
Expand Down Expand Up @@ -68,3 +69,98 @@ pub fn retrieve_commit_metadata() -> Result<Option<CommitMetadata>> {
commit_message,
}))
}

#[derive(Debug, Clone)]
pub struct GitTrackingInfo {
pub repo_root: String,
pub tracked_files: HashSet<String>,
}

impl GitTrackingInfo {
pub fn is_tracked(&self, relative_path: &str) -> bool {
let normalized = relative_path.replace('\\', "/");
self.tracked_files.contains(&normalized)
}
}

pub fn get_git_tracking_info() -> Option<GitTrackingInfo> {
if std::env::var("QLTY_COVERAGE_TESTING_WITHOUT_GIT").is_ok() {
return None;
}

let repo = match Repository::discover(".") {
Ok(repo) => repo,
Err(_) => {
info!("No Git repository found");
return None;
}
};

let repo_root = match repo.workdir() {
Some(path) => match path.to_str() {
Some(s) => s.to_string(),
None => {
warn!("Git repository path is not valid UTF-8");
return None;
}
},
None => {
warn!("Git repository has no working directory (bare repository)");
return None;
}
};

let index = match repo.index() {
Ok(index) => index,
Err(err) => {
warn!("Failed to read Git index: {:?}", err);
return None;
}
};

let mut tracked_files = HashSet::new();
for entry in index.iter() {
if let Ok(path) = std::str::from_utf8(&entry.path) {
tracked_files.insert(path.to_string());
}
}

info!("Git repository found at: {}", repo_root);

Some(GitTrackingInfo {
repo_root,
tracked_files,
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_is_tracked_returns_false_for_untracked() {
let info = GitTrackingInfo {
repo_root: "/tmp/test".to_string(),
tracked_files: HashSet::from(["src/main.rs".to_string()]),
};
assert!(!info.is_tracked("src/untracked.rs"));
}

#[test]
fn test_is_tracked_returns_true_for_tracked() {
let info = GitTrackingInfo {
repo_root: "/tmp/test".to_string(),
tracked_files: HashSet::from(["src/main.rs".to_string()]),
};
assert!(info.is_tracked("src/main.rs"));
}

#[test]
fn test_is_tracked_normalizes_windows_paths() {
let info = GitTrackingInfo {
repo_root: "/tmp/test".to_string(),
tracked_files: HashSet::from(["src/main.rs".to_string()]),
};
assert!(info.is_tracked("src\\main.rs"));
}
}
17 changes: 17 additions & 0 deletions qlty-coverage/src/publish/processor.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::git::get_git_tracking_info;
use crate::publish::{metrics::CoverageMetrics, Plan, Report, Results};
use anyhow::Result;
use qlty_types::tests::v1::FileCoverage;
Expand Down Expand Up @@ -34,14 +35,23 @@ impl Processor {
.filter_map(|file_coverage| self.transform(file_coverage.to_owned()))
.collect::<Vec<_>>();

let git_tracking = get_git_tracking_info();
let git_repo_path = git_tracking.as_ref().map(|info| info.repo_root.clone());

let mut found_files = HashSet::new();
let mut missing_files = HashSet::new();
let mut untracked_files = HashSet::new();

if self.plan.skip_missing_files {
transformed_file_coverages.retain(|file_coverage| {
match PathBuf::from(&file_coverage.path).try_exists() {
Ok(true) => {
found_files.insert(file_coverage.path.clone());
if let Some(ref tracking) = git_tracking {
if !tracking.is_tracked(&file_coverage.path) {
untracked_files.insert(file_coverage.path.clone());
}
}
true
}
_ => {
Expand All @@ -55,6 +65,11 @@ impl Processor {
match PathBuf::from(&file_coverage.path).try_exists() {
Ok(true) => {
found_files.insert(file_coverage.path.clone());
if let Some(ref tracking) = git_tracking {
if !tracking.is_tracked(&file_coverage.path) {
untracked_files.insert(file_coverage.path.clone());
}
}
}
_ => {
missing_files.insert(file_coverage.path.clone());
Expand All @@ -74,6 +89,8 @@ impl Processor {
totals,
missing_files,
found_files,
untracked_files,
git_repo_path,
excluded_files_count: ignored_paths_count,
auto_path_fixing_enabled: self.plan.auto_path_fixing_enabled,
})
Expand Down
6 changes: 6 additions & 0 deletions qlty-coverage/src/publish/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ pub struct Report {
#[serde(skip_serializing)]
pub missing_files: HashSet<String>,

#[serde(skip_serializing)]
pub untracked_files: HashSet<String>,

#[serde(skip_serializing)]
pub git_repo_path: Option<String>,

pub totals: CoverageMetrics,
pub excluded_files_count: usize,

Expand Down
4 changes: 2 additions & 2 deletions qlty-coverage/src/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ mod tests {
use std::fs::{self, File};
use tempfile::tempdir;

// Helper function to create a test Report instance
fn create_test_report(file_coverages: Vec<FileCoverage>) -> Report {
// Create a minimal valid Report
Report {
metadata: CoverageMetadata::default(),
report_files: vec![ReportFile::default()],
file_coverages,
found_files: HashSet::new(),
missing_files: HashSet::new(),
untracked_files: HashSet::new(),
git_repo_path: None,
totals: Default::default(),
excluded_files_count: 0,
auto_path_fixing_enabled: false,
Expand Down
Loading