Skip to content

Commit ad2b996

Browse files
authored
Merge pull request #42 from acunniffe/feat/git-sigpipe-handling
updates to pass t0005-signals
2 parents 39a775b + d9882fc commit ad2b996

File tree

3 files changed

+44
-17
lines changed

3 files changed

+44
-17
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ chrono = "0.4.41"
1515
indicatif = "0.17"
1616
smol = "1.3"
1717
rusqlite = { version = "0.31", features = ["bundled"] }
18+
libc = "0.2"
1819

1920

2021
[dev-dependencies]

src/main.rs

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use git::refs::AI_AUTHORSHIP_REFSPEC;
1111
use std::io::{IsTerminal, Write};
1212
use std::process::Command;
1313
use utils::debug_log;
14+
#[cfg(unix)]
15+
use std::os::unix::process::ExitStatusExt;
1416

1517
use crate::commands::checkpoint_agent::agent_preset::{
1618
AgentCheckpointFlags, AgentCheckpointPreset, ClaudePreset, CursorPreset,
@@ -28,6 +30,8 @@ struct Cli {
2830
}
2931

3032
fn main() {
33+
// Ensure SIGPIPE uses the default action (terminate), and do not inherit ignored SIGPIPE
34+
reset_sigpipe_to_default();
3135
// Initialize global configuration early
3236
config::Config::init();
3337
// Get the binary name that was called
@@ -406,14 +410,13 @@ fn handle_commit(args: &[String]) {
406410
let status = child.wait();
407411
match status {
408412
Ok(status) => {
409-
let code = status.code().unwrap_or(1);
410413
// If commit succeeded, run post-commit
411-
if code == 0 {
414+
if status.success() {
412415
if let Err(e) = git::post_commit::post_commit(&repo, false) {
413416
eprintln!("Post-commit failed: {}", e);
414417
}
415418
}
416-
std::process::exit(code);
419+
exit_with_status(status);
417420
}
418421
Err(e) => {
419422
eprintln!("Failed to wait for git commit process: {}", e);
@@ -503,36 +506,34 @@ fn handle_push(args: &[String]) {
503506
})
504507
.unwrap_or_default();
505508

506-
// Helper to run a git command and optionally forward output, returning exit code
507-
fn run_git_and_forward(args: &[String], quiet: bool) -> i32 {
508-
let output = Command::new(config::Config::get().git_cmd())
509-
.args(args)
510-
.output();
509+
// Helper to run a git command and optionally forward output, returning ExitStatus
510+
fn run_git_and_forward(args: &[String], quiet: bool) -> std::process::ExitStatus {
511+
let output = Command::new(config::Config::get().git_cmd()).args(args).output();
511512
match output {
512513
Ok(output) => {
513514
if !quiet {
514515
if !output.stdout.is_empty() {
515-
std::io::stdout().write_all(&output.stdout).unwrap();
516+
let _ = std::io::stdout().write_all(&output.stdout);
516517
}
517518
if !output.stderr.is_empty() {
518-
std::io::stderr().write_all(&output.stderr).unwrap();
519+
let _ = std::io::stderr().write_all(&output.stderr);
519520
}
520521
}
521-
output.status.code().unwrap_or(1)
522+
output.status
522523
}
523524
Err(e) => {
524525
eprintln!("Failed to execute git command: {}", e);
525-
1
526+
std::process::exit(1);
526527
}
527528
}
528529
}
529530

530531
// 1) Run exactly what the user typed (no arg mutation)
531532
let mut user_push = vec!["push".to_string()];
532533
user_push.extend_from_slice(args);
533-
let status_code = run_git_and_forward(&user_push, false);
534-
if status_code != 0 {
535-
std::process::exit(status_code);
534+
let status = run_git_and_forward(&user_push, false);
535+
if !status.success() {
536+
exit_with_status(status);
536537
}
537538

538539
// 2) Push authorship refs to the appropriate remote
@@ -571,7 +572,7 @@ fn handle_push(args: &[String]) {
571572
debug_log(&format!("pushing authorship refs: {:?}", &push_authorship));
572573
}
573574
let auth_status = run_git_and_forward(&push_authorship, quiet_second_push);
574-
std::process::exit(auth_status);
575+
exit_with_status(auth_status);
575576
} else {
576577
eprintln!("No git remotes found.");
577578
std::process::exit(1);
@@ -609,7 +610,7 @@ fn proxy_to_git(args: &[String]) {
609610
let status = child.wait();
610611
match status {
611612
Ok(status) => {
612-
std::process::exit(status.code().unwrap_or(1));
613+
exit_with_status(status);
613614
}
614615
Err(e) => {
615616
eprintln!("Failed to wait for git process: {}", e);
@@ -624,6 +625,30 @@ fn proxy_to_git(args: &[String]) {
624625
}
625626
}
626627

628+
// Ensure SIGPIPE default action, even if inherited ignored from a parent shell
629+
fn reset_sigpipe_to_default() {
630+
#[cfg(unix)]
631+
unsafe {
632+
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
633+
}
634+
}
635+
636+
// Exit mirroring the child's termination: same signal if signaled, else exit code
637+
fn exit_with_status(status: std::process::ExitStatus) -> ! {
638+
#[cfg(unix)]
639+
{
640+
if let Some(sig) = status.signal() {
641+
unsafe {
642+
libc::signal(sig, libc::SIG_DFL);
643+
libc::raise(sig);
644+
}
645+
// Should not return
646+
unreachable!();
647+
}
648+
}
649+
std::process::exit(status.code().unwrap_or(1));
650+
}
651+
627652
#[allow(dead_code)]
628653
fn parse_file_with_line_range(file_arg: &str) -> (String, Option<(u32, u32)>) {
629654
if let Some(colon_pos) = file_arg.rfind(':') {

0 commit comments

Comments
 (0)