Skip to main content

ai_agent/utils/plugins/
git_availability.rs

1// Source: ~/claudecode/openclaudecode/src/utils/plugins/gitAvailability.ts
2
3use std::sync::atomic::{AtomicBool, Ordering};
4
5/// Check if a command is available in PATH.
6///
7/// Uses which to find the actual executable without executing it.
8/// This is a security best practice to avoid executing arbitrary code
9/// in untrusted directories.
10async fn is_command_available(command: &str) -> bool {
11    crate::utils::which(command).await.is_some()
12}
13
14/// Atomic flag for git availability cache.
15static GIT_AVAILABLE: AtomicBool = AtomicBool::new(true);
16static GIT_AVAILABLE_INITIALIZED: AtomicBool = AtomicBool::new(false);
17
18/// Check if git is available on the system.
19///
20/// This is memoized so repeated calls within a session return the cached result.
21/// Git availability is unlikely to change during a single CLI session.
22///
23/// Only checks PATH -- does not exec git. On macOS this means the /usr/bin/git
24/// xcrun shim passes even without Xcode CLT installed; callers that hit
25/// `xcrun: error:` at exec time should call mark_git_unavailable() so the rest
26/// of the session behaves as though git is absent.
27pub async fn check_git_available() -> bool {
28    // Check if cache has been set
29    if GIT_AVAILABLE_INITIALIZED.load(Ordering::SeqCst) {
30        return GIT_AVAILABLE.load(Ordering::SeqCst);
31    }
32
33    let available = is_command_available("git").await;
34    GIT_AVAILABLE.store(available, Ordering::SeqCst);
35    GIT_AVAILABLE_INITIALIZED.store(true, Ordering::SeqCst);
36    available
37}
38
39/// Force the memoized git-availability check to return false for the rest of
40/// the session.
41///
42/// Call this when a git invocation fails in a way that indicates the binary
43/// exists on PATH but cannot actually run -- the macOS xcrun shim being the
44/// main case (`xcrun: error: invalid active developer path`). Subsequent
45/// check_git_available() calls then short-circuit to false, so downstream code
46/// that guards on git availability skips cleanly instead of failing repeatedly
47/// with the same exec error.
48pub fn mark_git_unavailable() {
49    GIT_AVAILABLE.store(false, Ordering::SeqCst);
50    GIT_AVAILABLE_INITIALIZED.store(true, Ordering::SeqCst);
51}
52
53/// Clear the git availability cache.
54/// Used for testing purposes.
55pub fn clear_git_availability_cache() {
56    GIT_AVAILABLE.store(true, Ordering::SeqCst);
57    GIT_AVAILABLE_INITIALIZED.store(false, Ordering::SeqCst);
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[tokio::test]
65    async fn test_clear_cache() {
66        clear_git_availability_cache();
67        assert!(!GIT_AVAILABLE_INITIALIZED.load(Ordering::SeqCst));
68    }
69
70    #[test]
71    fn test_mark_unavailable() {
72        mark_git_unavailable();
73        assert!(!GIT_AVAILABLE.load(Ordering::SeqCst));
74        assert!(GIT_AVAILABLE_INITIALIZED.load(Ordering::SeqCst));
75        clear_git_availability_cache();
76    }
77}