Skip to content

Commit 50cedbb

Browse files
simegclaude
andcommitted
Add comprehensive error handling, validation, and interactive enhancements
- Implement standardized Result<T> error handling patterns across all commands - Add comprehensive input validation with security focus (shell injection prevention) - Enhance interactive mode with fuzzy search capabilities using dialoguer and skim - Add safety features for destructive operations with confirmation dialogs - Implement output buffering for better performance - Add optimized git command operations to reduce git calls - Fix all test failures with proper non-interactive mode support - Add backup and checkpoint functionality for destructive operations - Strengthen validation for commit hashes, branch names, and file paths 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent aab5670 commit 50cedbb

17 files changed

+950
-100
lines changed

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
"Bash(git x bisect reset)",
2727
"Bash(git x bisect:*)",
2828
"Bash(git-x:*)",
29-
"Bash(git log:*)"
29+
"Bash(git log:*)",
30+
"Bash(git checkout:*)",
31+
"Bash(GIT_X_NON_INTERACTIVE=1 cargo test test_clean_branches_run_function_actual_delete)"
3032
],
3133
"deny": []
3234
}

Cargo.lock

Lines changed: 63 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ path = "src/main.rs"
2525
clap = { version = "4.5", features = ["derive"] }
2626
console = "0.16"
2727
chrono = "0.4"
28-
dialoguer = "0.11"
28+
dialoguer = { version = "0.11", features = ["fuzzy-select"] }
29+
fuzzy-matcher = "0.3"
30+
atty = "0.2"
2931

3032
[dev-dependencies]
3133
assert_cmd = "2.0"

src/clean_branches.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::common::Safety;
12
use crate::{GitXError, Result};
23
use std::process::Command;
34

@@ -9,14 +10,22 @@ pub fn run(dry_run: bool) {
910
}
1011

1112
fn run_clean_branches(dry_run: bool) -> Result<String> {
13+
// Safety check: ensure working directory is clean
14+
if !dry_run {
15+
Safety::ensure_clean_working_directory()?;
16+
}
17+
1218
let output = Command::new("git")
1319
.args(get_git_branch_args())
1420
.output()
15-
.map_err(|e| GitXError::Io(e))?;
21+
.map_err(GitXError::Io)?;
1622

1723
if !output.status.success() {
1824
let stderr = String::from_utf8_lossy(&output.stderr);
19-
return Err(GitXError::GitCommand(format!("Failed to list merged branches: {}", stderr.trim())));
25+
return Err(GitXError::GitCommand(format!(
26+
"Failed to list merged branches: {}",
27+
stderr.trim()
28+
)));
2029
}
2130

2231
let stdout = String::from_utf8_lossy(&output.stdout);
@@ -26,6 +35,19 @@ fn run_clean_branches(dry_run: bool) -> Result<String> {
2635
.filter(|branch| !is_protected_branch(branch))
2736
.collect();
2837

38+
// Safety confirmation for destructive operation
39+
if !dry_run && !branches.is_empty() {
40+
let details = format!(
41+
"This will delete {} merged branches: {}",
42+
branches.len(),
43+
branches.join(", ")
44+
);
45+
46+
if !Safety::confirm_destructive_operation("Clean merged branches", &details)? {
47+
return Ok("Operation cancelled by user.".to_string());
48+
}
49+
}
50+
2951
let mut deleted = Vec::new();
3052
let mut result = String::new();
3153

@@ -39,7 +61,7 @@ fn run_clean_branches(dry_run: bool) -> Result<String> {
3961
let status = Command::new("git")
4062
.args(delete_args)
4163
.status()
42-
.map_err(|e| GitXError::Io(e))?;
64+
.map_err(GitXError::Io)?;
4365

4466
if status.success() {
4567
deleted.push(branch);
@@ -48,7 +70,7 @@ fn run_clean_branches(dry_run: bool) -> Result<String> {
4870
}
4971

5072
if deleted.is_empty() {
51-
result.push_str(&format_no_branches_message());
73+
result.push_str(format_no_branches_message());
5274
} else {
5375
result.push_str(&format_deletion_summary(deleted.len(), dry_run));
5476
result.push('\n');

src/color_graph.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ fn run_color_graph() -> Result<String> {
1212
let output = Command::new("git")
1313
.args(get_color_git_log_args())
1414
.output()
15-
.map_err(|e| GitXError::Io(e))?;
15+
.map_err(GitXError::Io)?;
1616

1717
if output.status.success() {
1818
Ok(String::from_utf8_lossy(&output.stdout).to_string())
1919
} else {
2020
let stderr = String::from_utf8_lossy(&output.stderr);
21-
Err(GitXError::GitCommand(format!("git log failed: {}", stderr.trim())))
21+
Err(GitXError::GitCommand(format!(
22+
"git log failed: {}",
23+
stderr.trim()
24+
)))
2225
}
2326
}
2427

0 commit comments

Comments
 (0)