From caf61255eed3e0409d20a155ae76732558ff8dda Mon Sep 17 00:00:00 2001 From: containerscrew <131241415+containerscrew@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:53:08 +0200 Subject: [PATCH] Add diff and exclude dir features --- README.md | 29 +++++++++++++++++++++++++-- src/cli.rs | 19 ++++++++++++++++++ src/git_ops.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++---- src/main.rs | 18 ++++++++++++++--- 4 files changed, 110 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5ff8df5..c650624 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ I have a lot of personal/work repositories in my laptop. Sometimes you jump from Easy and simple. +# Badges + +| | | +|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Language | ![Rust](https://img.shields.io/badge/rust-%23000000.svg?style=for-the-badge&logo=rust&logoColor=white) | +| Release | [![Release](https://img.shields.io/github/release/containerscrew/gitrack)](https://github.com/containerscrew/gitrack/releases/latest) | +| Code | ![Code Size](https://img.shields.io/github/languages/code-size/containerscrew/gitrack) | +| CI - Build | [![Build](https://github.com/containerscrew/gitrack/actions/workflows/build.yml/badge.svg)](https://github.com/containerscrew/gitrack/actions/workflows/build.yml) | +| CI - Release | [![Build](https://github.com/containerscrew/gitrack/actions/workflows/release.yml/badge.svg)](https://github.com/containerscrew/gitrack/actions/workflows/release.yml) | +| CI - Test | [![Build](https://github.com/containerscrew/gitrack/actions/workflows/test.yml/badge.svg)](https://github.com/containerscrew/gitrack/actions/workflows/test.yml) | +| Meta | [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) [![License - MIT](https://img.shields.io/github/license/containerscrew/gitrack)](/LICENSE) | +| Codecov | [![Codecov](https://codecov.io/gh/containerscrew/gitrack/graph/badge.svg?token=4AI2U4PX4V)](https://codecov.io/gh/containerscrew/gitrack) | +| Downloads | [![Downloads](https://img.shields.io/github/downloads/containerscrew/gitrack/total.svg?logo=github)](https://somsubhra.github.io/github-release-stats/?username=containerscrew&repository=gitrack) | + # Supported Platforms | Arch | ARM64 | AMD64 | @@ -76,6 +90,18 @@ Number of workers/threads: gitrack -p /home/elliot -w 3 ``` +Diff file changes: + +```bash +gitrack -p /home/elliot/gitrack -d # diff is not compatible with -s (summarized) +``` + +Exclude directories: + +```bash +gitrack -p /home/elliot -s -e "/home/elliot/.cache" -e "/home/elliot/.local" +``` + # Threads > The use of threads is not really necessary in this type of tools, unless you have a very large file/folder system. Adding threads does not mean always better performance. I have included them in order to practice their use. **Max 5 threads, default 3** @@ -111,8 +137,7 @@ Scan specific folder with details: # TODO * Implement git commit scan for sensitive data using regex. Just for fun. Like gitleaks does. -* Support diff files. -* Control threads +* Exclude directories # Links diff --git a/src/cli.rs b/src/cli.rs index 8e805f2..83b732b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -37,4 +37,23 @@ pub struct Args { required = false )] pub workers: u16, + + #[arg( + short = 'd', + long = "diff", + help = "Show differences between changed files", + default_value_t = false, + required = false + )] + pub diff: bool, + + #[arg( + short = 'e', + long = "exclude-dir", + help = "Exclude directories to scan", + value_delimiter = ' ', + num_args = 1.., + required = false + )] + pub exclude: Option>, } diff --git a/src/git_ops.rs b/src/git_ops.rs index 5aef6ce..90d438d 100644 --- a/src/git_ops.rs +++ b/src/git_ops.rs @@ -1,15 +1,28 @@ -use git2::{Repository, StatusOptions}; +use colored::*; +use git2::{DiffOptions, Repository, StatusOptions}; +use std::io; +use std::io::ErrorKind; use std::path::{Path, PathBuf}; -use walkdir::WalkDir; +use walkdir::{DirEntry, WalkDir}; -pub fn find_git_repos(start_path: &Path) -> Vec { +fn is_excluded_dir(entry: &DirEntry, exclude_dirs: &[String]) -> bool { + exclude_dirs.iter().any(|dir| entry.path().starts_with(dir)) +} + +pub fn find_git_repos(start_path: &Path, exclude_dirs: &[String]) -> Vec { let mut git_repos = Vec::new(); - for entry in WalkDir::new(start_path).into_iter().filter_map(|e| e.ok()) { + + for entry in WalkDir::new(start_path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| !is_excluded_dir(e, exclude_dirs)) + { let path = entry.path(); if path.is_dir() && path.join(".git").exists() { git_repos.push(path.to_path_buf()); } } + git_repos } @@ -29,3 +42,35 @@ pub fn check_untracked_files(repo_path: &Path) -> Result, git2::Erro } Ok(untracked_files) } + +pub fn show_diff(repo_path: &Path, file: &str) -> io::Result { + let repo = Repository::open(repo_path).expect("Error opening repository"); + let mut diff_options = DiffOptions::new(); + diff_options.pathspec(file); + + let diff = repo + .diff_index_to_workdir(None, Some(&mut diff_options)) + .expect("Error diffing"); + let mut diff_output = Vec::new(); + + diff.print(git2::DiffFormat::Patch, |_, _, line| { + let content = String::from_utf8_lossy(line.content()).to_string(); + let colored_line = match line.origin() { + '-' => content.red().to_string(), // Deleted lines in red + '+' => content.green().to_string(), // Added lines in green + _ => content, // Unchanged lines + }; + diff_output.push(colored_line); + true + }) + .expect("Error printing diff"); + + if !diff_output.is_empty() { + Ok(diff_output.join("")) + } else { + Err(io::Error::new( + ErrorKind::NotFound, + format!("No differences found for {}", file), + )) + } +} diff --git a/src/main.rs b/src/main.rs index 46cb5d2..bb2a700 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use crate::git_ops::{check_untracked_files, find_git_repos}; +use crate::git_ops::{check_untracked_files, find_git_repos, show_diff}; use clap::Parser; use cli::Args; use colored::*; @@ -29,11 +29,17 @@ fn main() { // Initialize the CLI arguments let args = Args::parse(); - println!("{}", "----->  Starting gitrack  <-----".green()); + println!( + "{}", + "----->  Inspecting your untracked local Git files  <-----".green() + ); + println_orange!("-----> Scanning {}", args.path); // Find .git repos in the specified path let start_path = Path::new(&args.path); - let git_repos = find_git_repos(start_path); + // If user not specify exclude dirs, set it to empty + let exclude_dirs = args.exclude.as_deref().unwrap_or(&[]); + let git_repos = find_git_repos(start_path, exclude_dirs); // Create a thread pool with a limited number of threads let num_threads: usize = args.workers as usize; @@ -70,6 +76,12 @@ fn main() { if !args.summary { for file in untracked_files { println_light_orange!(" - {}", file); + if args.diff { + match show_diff(&repo_path, &file) { + Ok(diff) => println!("{}", diff), + Err(e) => eprintln!("{}: {}", "Error showing diff".red(), e), + } + } } } }