From b3a64d75a9bb648971eb95ffc823c6ff623a637a Mon Sep 17 00:00:00 2001 From: Sheldon Date: Thu, 12 Feb 2026 11:05:15 +0800 Subject: [PATCH 1/2] fix: remove x86_64-unknown-linux-musl target to avoid OpenSSL dependency issues --- dist-workspace.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist-workspace.toml b/dist-workspace.toml index f5284f1..bfd6c67 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -14,7 +14,7 @@ tap = "davelet/homebrew-gim" # A GitHub repo to push Scoop manifests to scoop-bucket = "davelet/scoop-gim" # Target platforms to build apps for (Rust target-triple syntax) -targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-pc-windows-msvc", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"] +targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-pc-windows-msvc", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] # Path that installers should place binaries in install-path = "CARGO_HOME" # Publish jobs to run in CI From 7f3b69b540bfb4ec2ceed064c20fd2218f87aad9 Mon Sep 17 00:00:00 2001 From: Sheldon Date: Tue, 17 Mar 2026 11:08:36 +0800 Subject: [PATCH 2/2] feat: Add max files limit for diff selection src/cli/command.rs: Add max_files CLI option to GimCli and Config subcommand (8) src/commands/config.rs: Add max_diff_files config functions and constant (52) src/core/diff.rs: Add file type classification and max files limit for diff selection (176) src/core/git.rs: Add git numstat and file-specific diff functions (89) src/main.rs: Add max_files handling in main CLI logic (14) --- Cargo.lock | 2 +- Cargo.toml | 2 +- docs/content/changelog.md | 12 +- docs/content/cli.md | 40 ++++++- docs/content/user_config.md | 34 +++++- src/cli/command.rs | 8 ++ src/commands/commit.rs | 7 +- src/commands/config.rs | 58 ++++++++- src/commands/mod.rs | 6 +- src/commands/update.rs | 5 +- src/config/constants.rs | 1 + src/config/mod.rs | 2 +- src/core/diff.rs | 229 ++++++++++++++++++++++++++++++++++-- src/core/git.rs | 93 ++++++++++++++- src/core/mod.rs | 4 +- src/main.rs | 36 ++++-- 16 files changed, 500 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17ae757..580b833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,7 +441,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git-intelligence-message" -version = "2.1.0" +version = "2.1.1" dependencies = [ "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index 8d8348f..3536c2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-intelligence-message" -version = "2.1.0" +version = "2.1.1" edition = "2024" description = "An advanced Git commit message generation utility with AI assistance" authors = ["Sheldon.Wei"] diff --git a/docs/content/changelog.md b/docs/content/changelog.md index 7f01ca8..e757f0b 100644 --- a/docs/content/changelog.md +++ b/docs/content/changelog.md @@ -1,5 +1,15 @@ # Changelog +## [2.1.1] - 2025-03-17 + +- **Smart File Limiting**: Added `--max-files` / `-n` option to limit the number of changed files sent to AI + - Configurable via `gim config --max-files ` (default: 10) + - Intelligent file selection: prioritizes files with most changes + - Auto-filters to code files only when code changes exceed 50% of total + - Supports file type classification (Code, Config, Doc, Other) +- Priority order: CLI argument > config file > default value + + ## [2.1.0] - 2026-02-12 - **M Chip Support for Mac**: Release build for M chip Macs. @@ -62,4 +72,4 @@ - View both diff and subject prompt templates - Edit prompt files with `--edit` flag - Support for custom editors with `--editor` option - - Short aliases for prompt types (d/diff/diff_prompt, s/subject/subject_prompt) \ No newline at end of file + - Short aliases for prompt types (d/diff/diff_prompt, s/subject/subject_prompt) diff --git a/docs/content/cli.md b/docs/content/cli.md index e20c9d7..62c02d3 100644 --- a/docs/content/cli.md +++ b/docs/content/cli.md @@ -33,6 +33,7 @@ gim -ap - `-t, --title `: Specify the commit message title - `-a, --auto-add`: Automatically stage all modifications - `-p, --update`: Amend the most recent commit +- `-n, --max-files `: Maximum number of changed files to send to AI (overrides config, default: 10) - `-v, --verbose`: Show detailed information (will be suppressed in quiet mode) - `-q, --quiet`: Suppress normal output (quiet mode) - `--diff-prompt `: Custom diff prompt to override the default AI prompt for analyzing changes @@ -86,4 +87,41 @@ The prompts are used in the following priority order: 3. **Config directory** - Global prompt files 4. **Built-in defaults** - Fallback prompts -This allows you to have project-specific prompts in a `.gim` directory, but override them temporarily with command line arguments when needed. \ No newline at end of file +This allows you to have project-specific prompts in a `.gim` directory, but override them temporarily with command line arguments when needed. + +## Limiting Files Sent to AI (`--max-files` / `-n`) + +When you have many changed files, sending all of them to AI can be overwhelming and costly. The `--max-files` option limits how many files are included in the diff sent to AI. + +**Usage examples:** + +```bash +# Limit to 5 most significant files +gim -n 5 + +# Combine with other options +gim -a -n 3 + +# Override config setting temporarily +gim --max-files 20 +``` + +**Smart File Selection:** + +When the number of changed files exceeds the limit, GIM uses intelligent selection: + +1. Files are ranked by the number of lines changed (additions + deletions) +2. If code file changes exceed 50% of total changes, only code files are kept (config and doc files are filtered out) +3. The top N most significant files are selected + +**Supported file types:** + +- **Code**: `.rs`, `.go`, `.py`, `.js`, `.ts`, `.java`, `.c`, `.cpp`, and many more +- **Config**: `.xml`, `.toml`, `.yaml`, `.json`, `.ini`, `.env`, etc. +- **Doc**: `.md`, `.txt`, `.rst`, `.adoc`, etc. + +**Priority Order:** + +1. **Command line argument** (`-n` / `--max-files`) - Highest priority +2. **Config file** (`gim config --max-files `) +3. **Default value** (10 files) \ No newline at end of file diff --git a/docs/content/user_config.md b/docs/content/user_config.md index 186aee0..1d3d13e 100644 --- a/docs/content/user_config.md +++ b/docs/content/user_config.md @@ -1,4 +1,4 @@ -This feature was introduced in version `1.4.0`. Currently, only one parameter is configurable. +This feature was introduced in version `1.4.0`. # lines-limit @@ -6,7 +6,39 @@ This feature was introduced in version `1.4.0`. Currently, only one parameter is You can configure this parameter using `gim config --lines-limit `. +# max-files + +`max-files` is an integer that limits the maximum number of changed files to send to AI. When the number of changed files exceeds this limit, GIM will intelligently select the most significant files based on: + +1. **Lines changed**: Files with more changes are prioritized +2. **File type filtering**: If code changes exceed 50% of total changes, only code files are included (filtering out config, docs, etc.) + +The default value is `10`. + +You can configure this parameter using: + +```bash +gim config --max-files +``` + +You can also override this value temporarily using the `-n` or `--max-files` CLI option: + +```bash +# Limit to 5 files for this commit +gim -n 5 + +# Or use the long form +gim --max-files 5 +``` + +**Supported file type classifications:** + +- **Code**: `.rs`, `.go`, `.py`, `.js`, `.ts`, `.java`, `.c`, `.cpp`, etc. +- **Config**: `.xml`, `.toml`, `.yaml`, `.json`, `.ini`, `.env`, etc. +- **Doc**: `.md`, `.txt`, `.rst`, `.adoc`, etc. + # show-location + Since version `1.7.0`, you can use `--show-location` flag to show config file location. And it opens the default file manager to the config file location. diff --git a/src/cli/command.rs b/src/cli/command.rs index 336dbba..5d4c4d7 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -38,6 +38,10 @@ pub struct GimCli { /// Custom subject prompt to override the default #[arg(long)] pub subject_prompt: Option, + + /// Maximum number of changed files to send to AI (overrides config) + #[arg(short = 'n', long)] + pub max_files: Option, } /// Enum representing all supported subcommands for the gim CLI. @@ -102,6 +106,10 @@ pub enum GimCommands { #[arg(long)] lines_limit: Option, + /// Maximum number of changed files to send to AI + #[arg(long)] + max_files: Option, + /// Print config file's location #[arg(long, default_value_t = false)] show_location: bool, diff --git a/src/commands/commit.rs b/src/commands/commit.rs index 3c3c884..b3b22ea 100644 --- a/src/commands/commit.rs +++ b/src/commands/commit.rs @@ -104,7 +104,7 @@ pub async fn generate_commit_message( pub fn execute_commit(subject: &str, message: &str, overwrite: bool) { if crate::core::git::git_commit(subject, message, overwrite) { output::print_normal( - "✅ Successfully committed changes! If you were discontent with the commit message and want to polish or revise it, run 'gim -p' or 'git commit --amend'" + "✅ Successfully committed changes! If you were discontent with the commit message and want to polish or revise it, run 'gim -p' or 'git commit --amend'", ); } else { eprintln!("Error: Failed to commit changes"); @@ -121,7 +121,10 @@ pub fn execute_commit(subject: &str, message: &str, overwrite: bool) { /// # Returns /// /// * `Ok(())` if within limit, exits with error if exceeds. -pub fn check_diff_limit(diff_content: &str, diff_limit: usize) -> Result<(), Box> { +pub fn check_diff_limit( + diff_content: &str, + diff_limit: usize, +) -> Result<(), Box> { if diff_content.lines().count() > diff_limit { eprintdoc!( r" diff --git a/src/commands/config.rs b/src/commands/config.rs index af5c48b..6340687 100644 --- a/src/commands/config.rs +++ b/src/commands/config.rs @@ -1,10 +1,11 @@ use std::io::{ErrorKind, Result}; use toml::{Value, map::Map}; -use crate::config::constants::{CUSTOM_SECTION_NAME, DIFF_SIZE_LIMIT}; +use crate::config::constants::{CUSTOM_SECTION_NAME, DIFF_SIZE_LIMIT, MAX_DIFF_FILES}; use crate::utils::output; static NAME: &str = "lines_limit"; +static MAX_FILES_NAME: &str = "max_diff_files"; pub fn get_lines_limit() -> usize { let lines_limit = gim_config::config::get_config_value(CUSTOM_SECTION_NAME, NAME); @@ -47,13 +48,64 @@ pub fn set_lines_limit(lines_limit: usize) -> Result<()> { } return Err(e); } - output::print_normal( - &format!("set custom config '{}' done, value: {:?}", + output::print_normal(&format!( + "set custom config '{}' done, value: {:?}", NAME, lines_limit )); Ok(()) } +pub fn get_max_diff_files() -> usize { + let max_files = gim_config::config::get_config_value(CUSTOM_SECTION_NAME, MAX_FILES_NAME); + if let Err(e) = max_files { + output::print_verbose(&format!( + "get custom config '{}' error: {:?}, return default: {}", + MAX_FILES_NAME, e, MAX_DIFF_FILES + )); + return MAX_DIFF_FILES; + } + let max_files = max_files.ok(); + if let Some(limit) = max_files { + output::print_verbose(&format!( + "get custom config '{}' value: {:?}", + MAX_FILES_NAME, limit + )); + return limit.as_integer().unwrap_or(MAX_DIFF_FILES as i64) as usize; + } + MAX_DIFF_FILES +} + +pub fn set_max_diff_files(max_files: usize) -> Result<()> { + let set = gim_config::config::update_config_value( + CUSTOM_SECTION_NAME, + MAX_FILES_NAME, + Value::Integer(max_files as i64), + ); + if let Err(e) = set { + output::print_verbose(&format!( + "set custom config '{}' error: {:?}", + MAX_FILES_NAME, e + )); + if e.kind() == ErrorKind::NotFound { + if e.to_string() == format!("Section '{}' not found", CUSTOM_SECTION_NAME) { + let mut config = gim_config::config::get_config().unwrap(); + let map = config.as_table_mut().unwrap(); + + let mut update_table = Map::new(); + update_table.insert(MAX_FILES_NAME.to_string(), Value::Integer(max_files as i64)); + map.insert(CUSTOM_SECTION_NAME.to_string(), Value::Table(update_table)); + return gim_config::config::save_config(&mut config); + } + } + return Err(e); + } + output::print_normal(&format!( + "set custom config '{}' done, value: {:?}", + MAX_FILES_NAME, max_files + )); + Ok(()) +} + /// Gets and prints the config file location. /// /// # Returns diff --git a/src/commands/mod.rs b/src/commands/mod.rs index cbb644c..0534af0 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,5 @@ -pub mod update; -pub mod prompt; pub mod ai; -pub mod config; pub mod commit; +pub mod config; +pub mod prompt; +pub mod update; diff --git a/src/commands/update.rs b/src/commands/update.rs index 2a82759..2f2b40e 100644 --- a/src/commands/update.rs +++ b/src/commands/update.rs @@ -83,7 +83,10 @@ fn get_latest_version_by_homebrew() -> Result FileType { + let extension = path.split('.').last().unwrap_or("").to_lowercase(); + + match extension.as_str() { + // Code files + "rs" | "go" | "py" | "js" | "ts" | "jsx" | "tsx" | "java" | "c" | "cpp" | "cc" | "h" + | "hpp" | "cs" | "php" | "rb" | "swift" | "kt" | "scala" | "m" | "mm" | "dart" | "ex" + | "exs" | "erl" | "hrl" | "clj" | "cljs" | "hs" | "ml" | "mli" | "fs" | "fsx" | "r" + | "jl" | "lua" | "pl" | "pm" | "sh" | "bash" | "zsh" | "fish" | "vim" | "el" => { + FileType::Code + } + + // Config files + "xml" | "toml" | "yaml" | "yml" | "json" | "ini" | "cfg" | "conf" | "config" + | "properties" | "env" | "lock" => FileType::Config, + + // Documentation files + "md" | "txt" | "rst" | "adoc" | "org" | "tex" => FileType::Doc, + + _ => FileType::Other, + } +} + +/// Parses git numstat output into FileChange objects. +fn parse_diff_stats(numstat: &str, name_status: &str) -> Vec { + let mut changes = Vec::new(); + let mut status_map = std::collections::HashMap::new(); + + // Parse name-status to get file status (A/M/D) + for line in name_status.lines() { + if let Some((status, filename)) = line.split_once('\t') { + status_map.insert(filename.to_string(), status.chars().next().unwrap_or('M')); + } + } + + // Parse numstat + for line in numstat.lines() { + let parts: Vec<&str> = line.split('\t').collect(); + if parts.len() >= 3 { + let added = parts[0].parse::().unwrap_or(0); + let deleted = parts[1].parse::().unwrap_or(0); + let path = parts[2].to_string(); + let lines_changed = added + deleted; + let status = *status_map.get(&path).unwrap_or(&'M'); + + // Skip deleted files (they don't contribute to the diff content) + if status != 'D' && lines_changed > 0 { + changes.push(FileChange { + path: path.clone(), + lines_changed, + file_type: classify_file_type(&path), + }); + } + } + } + + changes +} + +/// Selects files to include based on limits and priorities. +fn select_files(changes: Vec, max_files: usize) -> Vec { + if changes.is_empty() { + return Vec::new(); + } + + // Calculate total lines and code file lines + let total_lines: usize = changes.iter().map(|c| c.lines_changed).sum(); + let code_lines: usize = changes + .iter() + .filter(|c| c.file_type == FileType::Code) + .map(|c| c.lines_changed) + .sum(); + + // If code changes are more than 50%, only keep code files + let mut filtered_changes: Vec = if code_lines * 2 > total_lines { + output::print_verbose(&format!( + "Code changes ({} lines) exceed 50% of total ({} lines), filtering to code files only", + code_lines, total_lines + )); + changes.into_iter() + .filter(|c| c.file_type == FileType::Code) + .collect() + } else { + output::print_verbose(&format!( + "Code changes ({} lines) do not exceed 50% of total ({} lines), keeping all files", + code_lines, total_lines + )); + changes + }; + + // Sort by lines_changed descending to identify most significant changes + filtered_changes.sort_by(|a, b| b.lines_changed.cmp(&a.lines_changed)); + + // Take top N files by lines changed if max_files is set + if max_files > 0 { + filtered_changes.into_iter() + .take(max_files) + .map(|c| c.path) + .collect() + } else { + filtered_changes.into_iter() + .map(|c| c.path) + .collect() + } +} /// Builds the diff content for staging area changes. /// +/// # Arguments +/// +/// * `selected_files` - Optional list of files to include. If None, includes all files. +/// /// # Returns /// /// * `String` containing the formatted diff content, or empty string if no changes. -pub fn build_staging_diff() -> String { +pub fn build_staging_diff(selected_files: Option<&[String]>) -> String { let mut diff_content = String::new(); let name_status = git::get_staged_name_status(); - let full_diff = git::get_staged_diff(); + let full_diff = if let Some(files) = selected_files { + if files.is_empty() { + String::new() + } else { + git::get_staged_diff_for_files(files) + } + } else { + git::get_staged_diff() + }; if !name_status.is_empty() { diff_content.push_str("When I use `git diff`, I got the following output: \n"); // Add file status information (including deleted files) + // If selected_files is provided, only show those files + let selected_set: Option> = + selected_files.map(|files| files.iter().map(|s| s.as_str()).collect()); + for line in name_status.lines() { if let Some((status, filename)) = line.split_once('\t') { + // Skip if not in selected files + if let Some(ref set) = selected_set { + if !set.contains(filename) && status != "D" { + continue; + } + } + if status == "D" { diff_content.push_str(&format!("Deleted: {}\n", filename)); } else { @@ -46,21 +194,43 @@ pub fn build_staging_diff() -> String { /// Builds the diff content for the last commit. /// +/// # Arguments +/// +/// * `selected_files` - Optional list of files to include. If None, includes all files. +/// /// # Returns /// /// * `String` containing the formatted diff content. -pub fn build_last_commit_diff() -> String { +pub fn build_last_commit_diff(selected_files: Option<&[String]>) -> String { let mut diff_content = String::new(); diff_content.push_str( "As I want to amend commit message, I use `git show` and got the following output: \n", ); let name_status = git::get_last_commit_name_status(); - let full_diff = git::get_last_commit_diff(); + let full_diff = if let Some(files) = selected_files { + if files.is_empty() { + String::new() + } else { + git::get_last_commit_diff_for_files(files) + } + } else { + git::get_last_commit_diff() + }; // Parse name-status, only output filename for deleted files + let selected_set: Option> = + selected_files.map(|files| files.iter().map(|s| s.as_str()).collect()); + for line in name_status.lines() { if let Some((status, filename)) = line.split_once('\t') { + // Skip if not in selected files + if let Some(ref set) = selected_set { + if !set.contains(filename) && status != "D" { + continue; + } + } + if status == "D" { diff_content.push_str(&format!("Deleted: {}\n", filename)); } else { @@ -93,11 +263,17 @@ pub fn build_last_commit_diff() -> String { /// * `auto_add` - If true, automatically adds changes before building diff. /// * `changes` - List of changed files. /// * `overwrite` - If true, includes last commit diff. +/// * `max_files` - Maximum number of files to include in diff (0 means no limit). /// /// # Returns /// /// * `String` containing the complete diff content, or empty if no changes. -pub fn build_diff_content(auto_add: bool, changes: &[&str], overwrite: bool) -> String { +pub fn build_diff_content( + auto_add: bool, + changes: &[&str], + overwrite: bool, + max_files: usize, +) -> String { let mut diff_content = String::new(); if !changes.is_empty() { @@ -119,11 +295,50 @@ pub fn build_diff_content(auto_add: bool, changes: &[&str], overwrite: bool) -> git::git_add_all(); } - diff_content.push_str(&build_staging_diff()); + // Select files based on max_files limit and 50% rule + let selected_files = { + let numstat = git::get_staged_numstat(); + let name_status = git::get_staged_name_status(); + let file_changes = parse_diff_stats(&numstat, &name_status); + + let selected = select_files(file_changes, max_files); + if !selected.is_empty() && selected.len() < changes.len() { + output::print_normal(&format!( + "Limiting diff to {} most significant files (out of {} total changes)", + selected.len(), + changes.len() + )); + Some(selected) + } else if !selected.is_empty() { + Some(selected) + } else { + None + } + }; + + diff_content.push_str(&build_staging_diff(selected_files.as_deref())); } if overwrite { - diff_content.push_str(&build_last_commit_diff()); + // Apply same file selection logic to last commit + let selected_files = { + let numstat = git::get_last_commit_numstat(); + let name_status = git::get_last_commit_name_status(); + let file_changes = parse_diff_stats(&numstat, &name_status); + + let selected = select_files(file_changes, max_files); + if !selected.is_empty() { + output::print_verbose(&format!( + "Limiting last commit diff to {} files", + selected.len() + )); + Some(selected) + } else { + None + } + }; + + diff_content.push_str(&build_last_commit_diff(selected_files.as_deref())); } diff_content diff --git a/src/core/git.rs b/src/core/git.rs index 0f06872..a58df75 100644 --- a/src/core/git.rs +++ b/src/core/git.rs @@ -1,5 +1,5 @@ -use std::process::Command; use std::path::PathBuf; +use std::process::Command; use crate::utils::output; @@ -48,10 +48,7 @@ pub fn get_git_status(auto_add: bool) -> Vec { .args([ "status", "-s", - &format!( - "--untracked-files={}", - if auto_add { "all" } else { "no" } - ), + &format!("--untracked-files={}", if auto_add { "all" } else { "no" }), ]) .output() .expect("Failed to get git status"); @@ -112,6 +109,49 @@ pub fn get_staged_diff() -> String { String::from_utf8_lossy(&full_diff_output.stdout).to_string() } +/// Gets the numstat for staged changes. +/// +/// # Returns +/// +/// * `String` containing the numstat output (lines added/deleted per file). +pub fn get_staged_numstat() -> String { + let numstat_output = Command::new("git") + .args(["diff", "--cached", "--numstat"]) + .output() + .expect("Failed to get git diff --cached --numstat"); + + output::print_verbose("Run 'git diff --cached --numstat'"); + + String::from_utf8_lossy(&numstat_output.stdout).to_string() +} + +/// Gets the diff for specific files in staging area. +/// +/// # Arguments +/// +/// * `files` - List of file paths to get diff for. +/// +/// # Returns +/// +/// * `String` containing the diff output. +pub fn get_staged_diff_for_files(files: &[String]) -> String { + let mut args = vec!["diff", "--cached", "--diff-filter=AM", "--"]; + let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect(); + args.extend(file_refs); + + let diff_output = Command::new("git") + .args(&args) + .output() + .expect("Failed to get git diff for specific files"); + + output::print_verbose(&format!( + "Run 'git diff --cached --diff-filter=AM -- {:?}'", + files + )); + + String::from_utf8_lossy(&diff_output.stdout).to_string() +} + /// Gets the name-status of the last commit. /// /// # Returns @@ -144,6 +184,49 @@ pub fn get_last_commit_diff() -> String { String::from_utf8_lossy(&show_diff_output.stdout).to_string() } +/// Gets the numstat for the last commit. +/// +/// # Returns +/// +/// * `String` containing the numstat output. +pub fn get_last_commit_numstat() -> String { + let show_numstat_output = Command::new("git") + .args(["show", "--pretty=format:", "--numstat", "HEAD"]) + .output() + .expect("Failed to get git show --numstat"); + + output::print_verbose("Run 'git show --pretty=format: --numstat HEAD'"); + + String::from_utf8_lossy(&show_numstat_output.stdout).to_string() +} + +/// Gets the diff for specific files in the last commit. +/// +/// # Arguments +/// +/// * `files` - List of file paths to get diff for. +/// +/// # Returns +/// +/// * `String` containing the diff output. +pub fn get_last_commit_diff_for_files(files: &[String]) -> String { + let mut args = vec!["show", "--pretty=format:", "--diff-filter=AM", "HEAD", "--"]; + let file_refs: Vec<&str> = files.iter().map(|s| s.as_str()).collect(); + args.extend(file_refs); + + let show_diff_output = Command::new("git") + .args(&args) + .output() + .expect("Failed to get git show for specific files"); + + output::print_verbose(&format!( + "Run 'git show --pretty=format: --diff-filter=AM HEAD -- {:?}'", + files + )); + + String::from_utf8_lossy(&show_diff_output.stdout).to_string() +} + /// Commits changes with the given subject and message. /// /// # Arguments diff --git a/src/core/mod.rs b/src/core/mod.rs index e1383fd..b593329 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,3 @@ -pub mod git; -pub mod diff; pub mod ai; +pub mod diff; +pub mod git; diff --git a/src/main.rs b/src/main.rs index 41cc9f4..2130674 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use cli::{GimCli, GimCommands}; -use commands::{ai as commands_ai, config as commands_config, commit, prompt, update}; +use commands::{ai as commands_ai, commit, config as commands_config, prompt, update}; use core::{ai::client, diff, git}; use gim_config::config::get_config; use std::env; @@ -21,7 +21,8 @@ async fn main() { let start_time = std::time::Instant::now(); // Start update reminder check asynchronously in background // Skip if --dry flag is passed or if this is the update subcommand - let update_check_handle = if !cli.dry && env::args().nth(1).map_or(true, |arg| arg != "update") { + let update_check_handle = if !cli.dry && env::args().nth(1).map_or(true, |arg| arg != "update") + { Some(tokio::spawn(update::check_update_reminder_async())) } else { None @@ -78,9 +79,7 @@ async fn run_cli(cli: &GimCli, mut config: toml::Value) -> Result<(), Box Result<(), Box { if *reset { if *edit || prompt.is_some() || editor.is_some() { - utils::output::print_warning("--edit, --prompt or --editor will be ignored when --reset provided"); + utils::output::print_warning( + "--edit, --prompt or --editor will be ignored when --reset provided", + ); } // delete the 2 files if let Err(e) = prompt::delete_prompt_files() { @@ -173,6 +174,7 @@ async fn run_cli(cli: &GimCli, mut config: toml::Value) -> Result<(), Box { if *show_location { @@ -190,6 +192,12 @@ async fn run_cli(cli: &GimCli, mut config: toml::Value) -> Result<(), Box {} @@ -204,9 +212,16 @@ async fn run_cli(cli: &GimCli, mut config: toml::Value) -> Result<(), Box = git::get_git_status(cli.auto_add); + // Determine max_files value: CLI arg > config file > default + let max_files = cli + .max_files + .unwrap_or_else(|| commands_config::get_max_diff_files()); + utils::output::print_verbose(&format!("Using max_files limit: {}", max_files)); + // Build diff content let changes_ref: Vec<&str> = changes.iter().map(|s| s.as_str()).collect(); - let diff_content = diff::build_diff_content(cli.auto_add, &changes_ref, cli.overwrite); + let diff_content = + diff::build_diff_content(cli.auto_add, &changes_ref, cli.overwrite, max_files); if diff_content.is_empty() { utils::output::print_normal("No changes to commit."); @@ -215,9 +230,10 @@ async fn run_cli(cli: &GimCli, mut config: toml::Value) -> Result<(), Box