Compare commits
4 Commits
v0.1.11
...
8152edba39
| Author | SHA1 | Date | |
|---|---|---|---|
|
8152edba39
|
|||
|
679db5b1db
|
|||
|
b1ad68c7b5
|
|||
|
280d6ec5c9
|
12
Cargo.toml
12
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "quicommit"
|
name = "quicommit"
|
||||||
version = "0.1.11"
|
version = "0.2.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["Sidney Zhang <zly@lyzhang.me>"]
|
authors = ["Sidney Zhang <zly@lyzhang.me>"]
|
||||||
description = "A powerful Git assistant tool with AI-powered commit/tag/changelog generation(alpha version)"
|
description = "A powerful Git assistant tool with AI-powered commit/tag/changelog generation(alpha version)"
|
||||||
@@ -83,11 +83,13 @@ mockall = "0.12"
|
|||||||
wiremock = "0.6"
|
wiremock = "0.6"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = "s"
|
||||||
lto = true
|
lto = "thin"
|
||||||
codegen-units = 1
|
codegen-units = 2
|
||||||
|
panic = "abort"
|
||||||
strip = true
|
strip = true
|
||||||
|
debug = false
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 0
|
opt-level = 1
|
||||||
debug = true
|
debug = true
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ impl ContentGenerator {
|
|||||||
format: CommitFormat,
|
format: CommitFormat,
|
||||||
language: Language,
|
language: Language,
|
||||||
) -> Result<GeneratedCommit> {
|
) -> Result<GeneratedCommit> {
|
||||||
let diff = repo.get_staged_diff()
|
let diff = repo.get_staged_diff_sorted()
|
||||||
.context("Failed to get staged diff")?;
|
.context("Failed to get staged diff")?;
|
||||||
|
|
||||||
if diff.is_empty() {
|
if diff.is_empty() {
|
||||||
@@ -116,7 +116,7 @@ impl ContentGenerator {
|
|||||||
) -> Result<GeneratedCommit> {
|
) -> Result<GeneratedCommit> {
|
||||||
use dialoguer::Select;
|
use dialoguer::Select;
|
||||||
|
|
||||||
let diff = repo.get_staged_diff()?;
|
let diff = repo.get_staged_diff_sorted()?;
|
||||||
|
|
||||||
if diff.is_empty() {
|
if diff.is_empty() {
|
||||||
anyhow::bail!("No staged changes");
|
anyhow::bail!("No staged changes");
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ impl ChangelogGenerator {
|
|||||||
let mut output = format!("## [{}] - {}\n\n", version, date_str);
|
let mut output = format!("## [{}] - {}\n\n", version, date_str);
|
||||||
|
|
||||||
if self.group_by_type {
|
if self.group_by_type {
|
||||||
let grouped = self.group_commits(commits);
|
let _grouped = self.group_commits(commits);
|
||||||
|
|
||||||
// Standard categories
|
// Standard categories
|
||||||
let categories = vec![
|
let categories = vec![
|
||||||
@@ -230,7 +230,7 @@ impl ChangelogGenerator {
|
|||||||
|
|
||||||
fn generate_github_releases(
|
fn generate_github_releases(
|
||||||
&self,
|
&self,
|
||||||
version: &str,
|
_version: &str,
|
||||||
_date: DateTime<Utc>,
|
_date: DateTime<Utc>,
|
||||||
commits: &[CommitInfo],
|
commits: &[CommitInfo],
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
|
|||||||
115
src/git/mod.rs
115
src/git/mod.rs
@@ -8,8 +8,6 @@ pub mod changelog;
|
|||||||
pub mod commit;
|
pub mod commit;
|
||||||
pub mod tag;
|
pub mod tag;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use std::os::windows::ffi::OsStringExt;
|
|
||||||
|
|
||||||
fn normalize_path_for_git2(path: &Path) -> PathBuf {
|
fn normalize_path_for_git2(path: &Path) -> PathBuf {
|
||||||
let mut normalized = path.to_path_buf();
|
let mut normalized = path.to_path_buf();
|
||||||
@@ -346,6 +344,119 @@ impl GitRepo {
|
|||||||
Ok(diff_text)
|
Ok(diff_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get staged diff with files sorted by importance
|
||||||
|
/// Important files (source code) come first, then config files like Cargo.toml,
|
||||||
|
/// then lock files like Cargo.lock
|
||||||
|
pub fn get_staged_diff_sorted(&self) -> Result<String> {
|
||||||
|
let diff = self.get_staged_diff()?;
|
||||||
|
|
||||||
|
if diff.is_empty() {
|
||||||
|
return Ok(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file_diffs = Vec::new();
|
||||||
|
let mut current_file_diff = String::new();
|
||||||
|
let mut current_file = String::new();
|
||||||
|
|
||||||
|
for line in diff.lines() {
|
||||||
|
if line.starts_with("diff --git") {
|
||||||
|
// Save previous file diff if any
|
||||||
|
if !current_file_diff.is_empty() && !current_file.is_empty() {
|
||||||
|
file_diffs.push((current_file.clone(), current_file_diff.clone()));
|
||||||
|
}
|
||||||
|
current_file = extract_file_from_diff_line(line);
|
||||||
|
current_file_diff = format!("{}\n", line);
|
||||||
|
} else {
|
||||||
|
current_file_diff.push_str(line);
|
||||||
|
current_file_diff.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the last file diff
|
||||||
|
if !current_file_diff.is_empty() && !current_file.is_empty() {
|
||||||
|
file_diffs.push((current_file, current_file_diff));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by file importance
|
||||||
|
file_diffs.sort_by(|a, b| {
|
||||||
|
let score_a = file_importance_score(&a.0);
|
||||||
|
let score_b = file_importance_score(&b.0);
|
||||||
|
score_b.cmp(&score_a) // Descending order
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combine sorted diffs
|
||||||
|
let sorted_diff: String = file_diffs.into_iter()
|
||||||
|
.map(|(_, diff)| diff)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(sorted_diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract filename from diff --git line
|
||||||
|
fn extract_file_from_diff_line(line: &str) -> String {
|
||||||
|
// Format: "diff --git a/path/to/file b/path/to/file"
|
||||||
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
|
if parts.len() >= 3 {
|
||||||
|
// Return the second path (after b/)
|
||||||
|
if let Some(path) = parts[2].strip_prefix("b/") {
|
||||||
|
return path.to_string();
|
||||||
|
}
|
||||||
|
// Fallback to first path (after a/)
|
||||||
|
if let Some(path) = parts[1].strip_prefix("a/") {
|
||||||
|
return path.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate file importance score
|
||||||
|
/// Higher score = more important
|
||||||
|
fn file_importance_score(filename: &str) -> i32 {
|
||||||
|
// Priority list for important file types
|
||||||
|
let important_extensions = [
|
||||||
|
".rs", ".py", ".js", ".ts", ".tsx", ".jsx", ".go", ".java", ".cpp", ".c", ".rust",
|
||||||
|
".vue", ".svelte", ".html", ".css", ".scss", ".sass", ".less",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Config files that are important but less than source code
|
||||||
|
let config_files = [
|
||||||
|
"Cargo.toml", "package.json", "go.mod", "go.sum", "pom.xml",
|
||||||
|
"Makefile", "CMakeLists.txt", "build.gradle", "gradle.properties",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Lock files - lowest priority
|
||||||
|
let lock_files = [
|
||||||
|
"Cargo.lock", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
|
||||||
|
"Gemfile.lock", "composer.lock",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check lock files first (lowest priority)
|
||||||
|
for lock in lock_files.iter() {
|
||||||
|
if filename.ends_with(lock) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check config files (medium priority)
|
||||||
|
for config in config_files.iter() {
|
||||||
|
if filename.ends_with(config) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check important source files (highest priority)
|
||||||
|
for ext in important_extensions.iter() {
|
||||||
|
if filename.ends_with(ext) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default priority for other files
|
||||||
|
2
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GitRepo {
|
||||||
/// Get unstaged diff
|
/// Get unstaged diff
|
||||||
pub fn get_unstaged_diff(&self) -> Result<String> {
|
pub fn get_unstaged_diff(&self) -> Result<String> {
|
||||||
let diff = self.repo.diff_index_to_workdir(None, None)?;
|
let diff = self.repo.diff_index_to_workdir(None, None)?;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|||||||
@@ -41,10 +41,10 @@ pub fn get_editor() -> String {
|
|||||||
.or_else(|_| std::env::var("VISUAL"))
|
.or_else(|_| std::env::var("VISUAL"))
|
||||||
.unwrap_or_else(|_| {
|
.unwrap_or_else(|_| {
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
if let Ok(code) = which::which("code") {
|
if let Ok(_code) = which::which("code") {
|
||||||
return "code --wait".to_string();
|
return "code --wait".to_string();
|
||||||
}
|
}
|
||||||
if let Ok(notepad) = which::which("notepad") {
|
if let Ok(_notepad) = which::which("notepad") {
|
||||||
return "notepad".to_string();
|
return "notepad".to_string();
|
||||||
}
|
}
|
||||||
"notepad".to_string()
|
"notepad".to_string()
|
||||||
|
|||||||
Reference in New Issue
Block a user