Skip to main content

git_sprout/
interrupt.rs

1// ABOUTME: Defers an interrupt until the worktree is complete, so Ctrl-C never leaves a
2// ABOUTME: destination that git registered but nobody filled.
3
4use std::sync::atomic::{AtomicI32, Ordering};
5
6/// The signal that arrived, or zero. Written only from a signal handler, which may do
7/// nothing but store to an atomic.
8static ARRIVED: AtomicI32 = AtomicI32::new(0);
9
10#[cfg(unix)]
11extern "C" fn record(signal: libc::c_int) {
12    ARRIVED.store(signal, Ordering::Relaxed);
13}
14
15/// Asks the process to note interrupts rather than die on them.
16///
17/// Between the moment git creates the worktree and the moment git finishes checking it out
18/// there is a window where the destination exists, is registered in `git worktree list`,
19/// and holds nothing. Dying inside it leaves the user a worktree that looks real and
20/// reports its whole tree as deleted. Instead the request is recorded, the clone phase
21/// stops at its next path, git still finishes the checkout, and the process then dies of
22/// the original signal.
23#[cfg(unix)]
24pub fn defer() {
25    let handler = record as *const () as libc::sighandler_t;
26    for signal in [libc::SIGINT, libc::SIGTERM, libc::SIGHUP] {
27        // A signal the caller already asked to be ignored stays ignored: a shell that
28        // starts a background job hands it SIGINT set to ignore, and taking it back would
29        // make the tool die where git would not.
30        // SAFETY: `record` only stores to an atomic, which is async-signal-safe.
31        unsafe {
32            if libc::signal(signal, handler) == libc::SIG_IGN {
33                libc::signal(signal, libc::SIG_IGN);
34            }
35        }
36    }
37}
38
39#[cfg(not(unix))]
40pub fn defer() {}
41
42/// Whether an interrupt is waiting to be honoured.
43pub fn requested() -> bool {
44    ARRIVED.load(Ordering::Relaxed) != 0
45}
46
47/// Dies of the deferred signal, if one arrived. Returns if none did.
48#[cfg(unix)]
49pub fn honour() {
50    let signal = ARRIVED.load(Ordering::Relaxed);
51    if signal == 0 {
52        return;
53    }
54    // SAFETY: restoring the default disposition and re-raising is the documented way to
55    // exit with the status the signal would have produced.
56    unsafe {
57        libc::signal(signal, libc::SIG_DFL);
58        libc::raise(signal);
59    }
60}
61
62#[cfg(not(unix))]
63pub fn honour() {}