feat: add auto-push functionality to commit and tag commands
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "quicommit"
|
||||
version = "0.1.2"
|
||||
version = "0.1.4"
|
||||
edition = "2024"
|
||||
authors = ["Sidney Zhang <zly@lyzhang.me>"]
|
||||
description = "A powerful Git assistant tool with AI-powered commit/tag/changelog generation(alpha version)"
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::utils::validators::get_commit_types;
|
||||
#[derive(Parser)]
|
||||
pub struct CommitCommand {
|
||||
/// Commit type
|
||||
#[arg(short, long)]
|
||||
#[arg(long)]
|
||||
commit_type: Option<String>,
|
||||
|
||||
/// Commit scope
|
||||
@@ -39,7 +39,7 @@ pub struct CommitCommand {
|
||||
date: bool,
|
||||
|
||||
/// Manual input (skip AI generation)
|
||||
#[arg(short, long)]
|
||||
#[arg(long)]
|
||||
manual: bool,
|
||||
|
||||
/// Sign the commit
|
||||
@@ -73,6 +73,14 @@ pub struct CommitCommand {
|
||||
/// Skip interactive prompts
|
||||
#[arg(short = 'y', long)]
|
||||
yes: bool,
|
||||
|
||||
/// Push after committing
|
||||
#[arg(long)]
|
||||
push: bool,
|
||||
|
||||
/// Remote to push to
|
||||
#[arg(long, default_value = "origin")]
|
||||
remote: String,
|
||||
}
|
||||
|
||||
impl CommitCommand {
|
||||
@@ -101,6 +109,13 @@ impl CommitCommand {
|
||||
config.commit.format
|
||||
};
|
||||
|
||||
// Auto-add if no files are staged and there are unstaged/untracked changes
|
||||
if status.staged == 0 && (status.unstaged > 0 || status.untracked > 0) && !self.all {
|
||||
println!("{}", messages.auto_stage_changes().yellow());
|
||||
repo.stage_all()?;
|
||||
println!("{}", messages.staged_all().green());
|
||||
}
|
||||
|
||||
// Stage all if requested
|
||||
if self.all {
|
||||
repo.stage_all()?;
|
||||
@@ -175,6 +190,24 @@ impl CommitCommand {
|
||||
println!("{} {}", messages.commit_amended().green().bold(), "successfully");
|
||||
}
|
||||
|
||||
// Push after commit if requested or ask user
|
||||
if self.push {
|
||||
println!("\n{}", messages.pushing_commit(&self.remote));
|
||||
repo.push(&self.remote, "HEAD")?;
|
||||
println!("{}", messages.pushed_commit(&self.remote));
|
||||
} else if !self.yes && !self.dry_run {
|
||||
let should_push = Confirm::new()
|
||||
.with_prompt(messages.push_after_commit())
|
||||
.default(false)
|
||||
.interact()?;
|
||||
|
||||
if should_push {
|
||||
println!("\n{}", messages.pushing_commit(&self.remote));
|
||||
repo.push(&self.remote, "HEAD")?;
|
||||
println!("{}", messages.pushed_commit(&self.remote));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -184,12 +217,22 @@ impl CommitCommand {
|
||||
}
|
||||
|
||||
fn create_manual_commit(&self, format: CommitFormat) -> Result<String> {
|
||||
let commit_type = self.commit_type.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("Commit type required for manual commit. Use -t <type>"))?;
|
||||
|
||||
let description = self.message.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("Description required for manual commit. Use -m <message>"))?;
|
||||
|
||||
// Try to extract commit type from message if not provided
|
||||
let commit_type = if let Some(ref ct) = self.commit_type {
|
||||
ct.clone()
|
||||
} else {
|
||||
// Parse from conventional commit format: "type: description"
|
||||
description
|
||||
.split(':')
|
||||
.next()
|
||||
.unwrap_or("feat")
|
||||
.trim()
|
||||
.to_string()
|
||||
};
|
||||
|
||||
let builder = CommitBuilder::new()
|
||||
.commit_type(commit_type)
|
||||
.description(description)
|
||||
|
||||
@@ -158,11 +158,22 @@ impl TagCommand {
|
||||
|
||||
println!("{} {}", messages.tag_created().green(), tag_name.cyan());
|
||||
|
||||
// Push if requested
|
||||
// Push if requested or ask user
|
||||
if self.push {
|
||||
println!("{}", messages.pushing_tag(&self.remote));
|
||||
repo.push(&self.remote, &format!("refs/tags/{}", tag_name))?;
|
||||
println!("{}", messages.pushed_tag(&self.remote));
|
||||
} else if !self.yes && !self.dry_run {
|
||||
let should_push = Confirm::new()
|
||||
.with_prompt(messages.push_after_tag())
|
||||
.default(false)
|
||||
.interact()?;
|
||||
|
||||
if should_push {
|
||||
println!("{}", messages.pushing_tag(&self.remote));
|
||||
repo.push(&self.remote, &format!("refs/tags/{}", tag_name))?;
|
||||
println!("{}", messages.pushed_tag(&self.remote));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{AppConfig, GitProfile, TokenConfig, TokenType};
|
||||
use super::{AppConfig, GitProfile, TokenConfig};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -265,28 +265,18 @@ impl GitRepo {
|
||||
|
||||
/// Stage all changes including subdirectories
|
||||
pub fn stage_all(&self) -> Result<()> {
|
||||
let mut index = self.repo.index()?;
|
||||
// Use git command for reliable staging (handles all edge cases)
|
||||
let output = std::process::Command::new("git")
|
||||
.args(&["add", "-A"])
|
||||
.current_dir(&self.path)
|
||||
.output()
|
||||
.with_context(|| "Failed to stage changes with git command")?;
|
||||
|
||||
fn add_directory_recursive(index: &mut git2::Index, base_dir: &Path, current_dir: &Path) -> Result<()> {
|
||||
for entry in std::fs::read_dir(current_dir)
|
||||
.with_context(|| format!("Failed to read directory: {:?}", current_dir))?
|
||||
{
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_file() {
|
||||
if let Ok(rel_path) = path.strip_prefix(base_dir) {
|
||||
let _ = index.add_path(rel_path);
|
||||
}
|
||||
} else if path.is_dir() {
|
||||
add_directory_recursive(index, base_dir, &path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
bail!("Failed to stage changes: {}", stderr);
|
||||
}
|
||||
|
||||
add_directory_recursive(&mut index, &self.path, &self.path)?;
|
||||
index.write()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,66 @@ impl Messages {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn auto_stage_changes(&self) -> &str {
|
||||
match self.language {
|
||||
Language::English => "No files staged. Auto-staging all changes...",
|
||||
Language::Chinese => "没有暂存文件。自动暂存所有更改...",
|
||||
Language::Japanese => "ステージされたファイルがありません。すべての変更を自動ステージ中...",
|
||||
Language::Korean => "스테이징된 파일이 없습니다. 모든 변경 사항을 자동 스테이징 중...",
|
||||
Language::Spanish => "No hay archivos preparados. Preparando automáticamente todos los cambios...",
|
||||
Language::French => "Aucun fichier indexé. Indexation automatique de tous les changements...",
|
||||
Language::German => "Keine Dateien bereitgestellt. Alle Änderungen werden automatisch bereitgestellt...",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_after_commit(&self) -> &str {
|
||||
match self.language {
|
||||
Language::English => "Push changes to remote?",
|
||||
Language::Chinese => "推送更改到远程仓库?",
|
||||
Language::Japanese => "リモートに変更をプッシュしますか?",
|
||||
Language::Korean => "원격으로 변경 사항을 푸시하시겠습니까?",
|
||||
Language::Spanish => "¿Enviar cambios al remoto?",
|
||||
Language::French => "Envoyer les modifications au distant ?",
|
||||
Language::German => "Änderungen an Remote pushen?",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_after_tag(&self) -> &str {
|
||||
match self.language {
|
||||
Language::English => "Push tag to remote?",
|
||||
Language::Chinese => "推送标签到远程仓库?",
|
||||
Language::Japanese => "リモートにタグをプッシュしますか?",
|
||||
Language::Korean => "원격으로 태그를 푸시하시겠습니까?",
|
||||
Language::Spanish => "¿Enviar etiqueta al remoto?",
|
||||
Language::French => "Envoyer l'étiquette au distant ?",
|
||||
Language::German => "Tag an Remote pushen?",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pushing_commit(&self, remote: &str) -> String {
|
||||
match self.language {
|
||||
Language::English => format!("→ Pushing commit to {}...", remote),
|
||||
Language::Chinese => format!("→ 正在推送提交到 {}...", remote),
|
||||
Language::Japanese => format!("→ コミットを{}にプッシュ中...", remote),
|
||||
Language::Korean => format!("→ 커밋을 {}로 푸시 중...", remote),
|
||||
Language::Spanish => format!("→ Enviando commit a {}...", remote),
|
||||
Language::French => format!("→ Envoi du commit vers {}...", remote),
|
||||
Language::German => format!("→ Commit wird an {} gepusht...", remote),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pushed_commit(&self, remote: &str) -> String {
|
||||
match self.language {
|
||||
Language::English => format!("✓ Pushed commit to {}", remote),
|
||||
Language::Chinese => format!("✓ 已推送提交到 {}", remote),
|
||||
Language::Japanese => format!("✓ コミットを{}にプッシュしました", remote),
|
||||
Language::Korean => format!("✓ 커밋을 {}로 푸시함", remote),
|
||||
Language::Spanish => format!("✓ Commit enviado a {}", remote),
|
||||
Language::French => format!("✓ Commit envoyé à {}", remote),
|
||||
Language::German => format!("✓ Commit an {} gepusht", remote),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ai_analyzing(&self) -> &str {
|
||||
match self.language {
|
||||
Language::English => "🤖 AI is analyzing your changes...",
|
||||
|
||||
Reference in New Issue
Block a user