Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions .github/workflows/install-scripts-local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
name: Install Scripts (Local Build)

on:
pull_request:
branches: [main]
push:
branches: [main]
merge_group:
branches: [main]

jobs:
install-local-unix:
name: Local install.sh on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true

- name: Build git-ai
run: cargo build --release --bin git-ai

- name: Prepare test home and fake Claude Code
run: |
TEST_HOME="$RUNNER_TEMP/git-ai-home"
mkdir -p "$TEST_HOME/.config/fish" "$TEST_HOME/.claude"
touch "$TEST_HOME/.bashrc" "$TEST_HOME/.zshrc" "$TEST_HOME/.config/fish/config.fish"
echo "HOME=$TEST_HOME" >> "$GITHUB_ENV"

BIN_DIR="$RUNNER_TEMP/fake-bin"
mkdir -p "$BIN_DIR"
cat > "$BIN_DIR/claude" <<'EOF'
#!/bin/sh
echo "2.0.0 (Claude Code)"
EOF
chmod +x "$BIN_DIR/claude"
echo "$BIN_DIR" >> "$GITHUB_PATH"

- name: Run install.sh with local binary
env:
GIT_AI_LOCAL_BINARY: ${{ github.workspace }}/target/release/git-ai
run: |
chmod +x ./install.sh
./install.sh

- name: Verify shell configs and Claude hooks
run: |
INSTALL_DIR="$HOME/.git-ai/bin"
test -x "$INSTALL_DIR/git-ai"
grep -F "$INSTALL_DIR" "$HOME/.bashrc"
grep -F "$INSTALL_DIR" "$HOME/.zshrc"
grep -F "fish_add_path -g \"$INSTALL_DIR\"" "$HOME/.config/fish/config.fish"
grep -F "checkpoint claude" "$HOME/.claude/settings.json"

install-local-windows:
name: Local install.ps1 on windows-latest
runs-on: windows-latest

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true

- name: Build git-ai
run: cargo build --release --bin git-ai

- name: Prepare test home and fake Claude Code
shell: pwsh
run: |
$testHome = Join-Path $env:RUNNER_TEMP "git-ai-home"
New-Item -ItemType Directory -Force -Path $testHome | Out-Null
$homeDrive = [System.IO.Path]::GetPathRoot($testHome).TrimEnd('\')
$homePath = $testHome.Substring($homeDrive.Length)
Add-Content -Path $env:GITHUB_ENV -Value "TEST_HOME=$testHome"
Add-Content -Path $env:GITHUB_ENV -Value "TEST_HOME_DRIVE=$homeDrive"
Add-Content -Path $env:GITHUB_ENV -Value "TEST_HOME_PATH=$homePath"

$claudeDir = Join-Path $testHome ".claude"
New-Item -ItemType Directory -Force -Path $claudeDir | Out-Null

$binDir = Join-Path $env:RUNNER_TEMP "fake-bin"
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
$claudeCmd = Join-Path $binDir "claude.cmd"
"@echo 2.0.0 (Claude Code)" | Out-File -FilePath $claudeCmd -Encoding ASCII -Force
Add-Content -Path $env:GITHUB_PATH -Value $binDir

- name: Run install.ps1 with local binary
shell: pwsh
env:
GIT_AI_LOCAL_BINARY: ${{ github.workspace }}\target\release\git-ai.exe
HOME: ${{ env.TEST_HOME }}
USERPROFILE: ${{ env.TEST_HOME }}
HOMEDRIVE: ${{ env.TEST_HOME_DRIVE }}
HOMEPATH: ${{ env.TEST_HOME_PATH }}
run: |
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
./install.ps1

- name: Verify Claude hooks
shell: pwsh
env:
HOME: ${{ env.TEST_HOME }}
USERPROFILE: ${{ env.TEST_HOME }}
HOMEDRIVE: ${{ env.TEST_HOME_DRIVE }}
HOMEPATH: ${{ env.TEST_HOME_PATH }}
run: |
$installDir = Join-Path $env:USERPROFILE ".git-ai\bin"
if (-not (Test-Path -LiteralPath (Join-Path $installDir "git-ai.exe"))) { throw "git-ai.exe not installed" }
$settings = Join-Path $env:USERPROFILE ".claude\settings.json"
if (-not (Select-String -Path $settings -Pattern "checkpoint claude")) { throw "Claude hooks not configured" }
93 changes: 93 additions & 0 deletions .github/workflows/install-scripts-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Install Scripts (Nightly)

on:
schedule:
- cron: "0 3 * * *"
workflow_dispatch:

jobs:
install-nightly-unix:
name: Nightly install.sh on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]

steps:
- name: Prepare test home and fake Claude Code
run: |
TEST_HOME="$RUNNER_TEMP/git-ai-home"
mkdir -p "$TEST_HOME/.config/fish" "$TEST_HOME/.claude"
touch "$TEST_HOME/.bashrc" "$TEST_HOME/.zshrc" "$TEST_HOME/.config/fish/config.fish"
echo "HOME=$TEST_HOME" >> "$GITHUB_ENV"

BIN_DIR="$RUNNER_TEMP/fake-bin"
mkdir -p "$BIN_DIR"
cat > "$BIN_DIR/claude" <<'EOF'
#!/bin/sh
echo "2.0.0 (Claude Code)"
EOF
chmod +x "$BIN_DIR/claude"
echo "$BIN_DIR" >> "$GITHUB_PATH"

- name: Run install.sh from usegitai.com
run: curl -fsSL https://usegitai.com/install.sh | bash

- name: Verify shell configs and Claude hooks
run: |
INSTALL_DIR="$HOME/.git-ai/bin"
test -x "$INSTALL_DIR/git-ai"
grep -F "$INSTALL_DIR" "$HOME/.bashrc"
grep -F "$INSTALL_DIR" "$HOME/.zshrc"
grep -F "fish_add_path -g \"$INSTALL_DIR\"" "$HOME/.config/fish/config.fish"
grep -F "checkpoint claude" "$HOME/.claude/settings.json"

install-nightly-windows:
name: Nightly install.ps1 on windows-latest
runs-on: windows-latest

steps:
- name: Prepare test home and fake Claude Code
shell: pwsh
run: |
$testHome = Join-Path $env:RUNNER_TEMP "git-ai-home"
New-Item -ItemType Directory -Force -Path $testHome | Out-Null
$homeDrive = [System.IO.Path]::GetPathRoot($testHome).TrimEnd('\')
$homePath = $testHome.Substring($homeDrive.Length)
Add-Content -Path $env:GITHUB_ENV -Value "TEST_HOME=$testHome"
Add-Content -Path $env:GITHUB_ENV -Value "TEST_HOME_DRIVE=$homeDrive"
Add-Content -Path $env:GITHUB_ENV -Value "TEST_HOME_PATH=$homePath"

$claudeDir = Join-Path $testHome ".claude"
New-Item -ItemType Directory -Force -Path $claudeDir | Out-Null

$binDir = Join-Path $env:RUNNER_TEMP "fake-bin"
New-Item -ItemType Directory -Force -Path $binDir | Out-Null
$claudeCmd = Join-Path $binDir "claude.cmd"
"@echo 2.0.0 (Claude Code)" | Out-File -FilePath $claudeCmd -Encoding ASCII -Force
Add-Content -Path $env:GITHUB_PATH -Value $binDir

- name: Run install.ps1 from usegitai.com
shell: pwsh
env:
HOME: ${{ env.TEST_HOME }}
USERPROFILE: ${{ env.TEST_HOME }}
HOMEDRIVE: ${{ env.TEST_HOME_DRIVE }}
HOMEPATH: ${{ env.TEST_HOME_PATH }}
run: |
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
Invoke-WebRequest -UseBasicParsing https://usegitai.com/install.ps1 | Invoke-Expression

- name: Verify Claude hooks
shell: pwsh
env:
HOME: ${{ env.TEST_HOME }}
USERPROFILE: ${{ env.TEST_HOME }}
HOMEDRIVE: ${{ env.TEST_HOME_DRIVE }}
HOMEPATH: ${{ env.TEST_HOME_PATH }}
run: |
$installDir = Join-Path $env:USERPROFILE ".git-ai\bin"
if (-not (Test-Path -LiteralPath (Join-Path $installDir "git-ai.exe"))) { throw "git-ai.exe not installed" }
$settings = Join-Path $env:USERPROFILE ".claude\settings.json"
if (-not (Select-String -Path $settings -Pattern "checkpoint claude")) { throw "Claude hooks not configured" }
15 changes: 12 additions & 3 deletions install.ps1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 install.ps1 prints misleading "Downloading" message when using a local binary override

When GIT_AI_LOCAL_BINARY is set, the install.ps1 script unconditionally prints "Downloading git-ai (release: local)..." at line 299, even though no download occurs — the binary is copied locally. The install.sh script correctly handles this with a conditional message ("Using local git-ai binary" vs "Downloading git-ai" at lines 246/252).

Root Cause

The Write-Host at install.ps1:299 was not updated to be conditional when the local binary override was added. In install.sh, the message was properly split:

if [ -n "${GIT_AI_LOCAL_BINARY:-}" ]; then
    echo "Using local git-ai binary (release: ${RELEASE_TAG})..."
    ...
else
    echo "Downloading git-ai (release: ${RELEASE_TAG})..."
    ...
fi

But in install.ps1, the message at line 299 is always printed before the conditional logic at line 316:

Write-Host ("Downloading git-ai (release: {0})..." -f $releaseTag)  # line 299 - always says "Downloading"
...
if (-not [string]::IsNullOrWhiteSpace($env:GIT_AI_LOCAL_BINARY)) {  # line 316
    Copy-Item ...  # actually copies, not downloads
}

Impact: Users and CI logs will show a misleading "Downloading" message when the local binary override is in use, causing confusion during debugging.

(Refers to line 299)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,10 @@ $os = 'windows'
$binaryName = "git-ai-$os-$arch"

# Determine release tag
# Priority: 1. Pinned version (for release builds), 2. Environment variable, 3. "latest"
if ($PinnedVersion -ne '__VERSION_PLACEHOLDER__') {
# Priority: 1. Local binary override, 2. Pinned version (for release builds), 3. Environment variable, 4. "latest"
if (-not [string]::IsNullOrWhiteSpace($env:GIT_AI_LOCAL_BINARY)) {
$releaseTag = 'local'
} elseif ($PinnedVersion -ne '__VERSION_PLACEHOLDER__') {
# Version-pinned install script from a release
$releaseTag = $PinnedVersion
$downloadUrlExe = "https://usegitai.com/worker/releases/download/$releaseTag/$binaryName.exe"
Expand Down Expand Up @@ -311,7 +313,14 @@ function Try-Download {

# Track which download URL succeeded for checksum verification
$downloadedBinaryName = $null
if (Try-Download -Url $downloadUrlExe) {
if (-not [string]::IsNullOrWhiteSpace($env:GIT_AI_LOCAL_BINARY)) {
if (-not (Test-Path -LiteralPath $env:GIT_AI_LOCAL_BINARY)) {
Remove-Item -Force -ErrorAction SilentlyContinue $tmpFile
Write-ErrorAndExit "Local binary not found at $($env:GIT_AI_LOCAL_BINARY)"
}
Copy-Item -Force -Path $env:GIT_AI_LOCAL_BINARY -Destination $tmpFile
$downloadedBinaryName = "$binaryName.exe"
} elseif (Try-Download -Url $downloadUrlExe) {
$downloadedBinaryName = "$binaryName.exe"
} elseif (Try-Download -Url $downloadUrlNoExt) {
$downloadedBinaryName = $binaryName
Expand Down
23 changes: 17 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,11 @@ esac
BINARY_NAME="git-ai-${OS}-${ARCH}"

# Determine release tag
# Priority: 1. Pinned version (for release builds), 2. Environment variable, 3. "latest"
if [ "$PINNED_VERSION" != "__VERSION_PLACEHOLDER__" ]; then
# Priority: 1. Local binary override, 2. Pinned version (for release builds), 3. Environment variable, 4. "latest"
if [ -n "${GIT_AI_LOCAL_BINARY:-}" ]; then
RELEASE_TAG="local"
DOWNLOAD_URL=""
elif [ "$PINNED_VERSION" != "__VERSION_PLACEHOLDER__" ]; then
# Version-pinned install script from a release
RELEASE_TAG="$PINNED_VERSION"
DOWNLOAD_URL="https://usegitai.com/worker/releases/download/${RELEASE_TAG}/${BINARY_NAME}"
Expand All @@ -238,11 +241,19 @@ INSTALL_DIR="$HOME/.git-ai/bin"
mkdir -p "$INSTALL_DIR"

# Download and install
echo "Downloading git-ai (release: ${RELEASE_TAG})..."
TMP_FILE="${INSTALL_DIR}/git-ai.tmp.$$"
if ! curl --fail --location --silent --show-error -o "$TMP_FILE" "$DOWNLOAD_URL"; then
rm -f "$TMP_FILE" 2>/dev/null || true
error "Failed to download binary (HTTP error)"
if [ -n "${GIT_AI_LOCAL_BINARY:-}" ]; then
echo "Using local git-ai binary (release: ${RELEASE_TAG})..."
if [ ! -f "$GIT_AI_LOCAL_BINARY" ]; then
error "Local binary not found at $GIT_AI_LOCAL_BINARY"
fi
cp "$GIT_AI_LOCAL_BINARY" "$TMP_FILE"
else
echo "Downloading git-ai (release: ${RELEASE_TAG})..."
if ! curl --fail --location --silent --show-error -o "$TMP_FILE" "$DOWNLOAD_URL"; then
rm -f "$TMP_FILE" 2>/dev/null || true
error "Failed to download binary (HTTP error)"
fi
fi

# Basic validation: ensure file is not empty
Expand Down
15 changes: 3 additions & 12 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};

use crate::feature_flags::FeatureFlags;
use crate::git::repository::Repository;
use crate::mdm::utils::home_dir;

#[cfg(any(test, feature = "test-support"))]
use std::sync::RwLock;
Expand Down Expand Up @@ -681,8 +682,7 @@ fn load_file_config() -> Option<FileConfig> {
}

fn config_file_path() -> Option<PathBuf> {
let home = dirs::home_dir()?;
Some(home.join(".git-ai").join("config.json"))
Some(home_dir().join(".git-ai").join("config.json"))
}

/// Public accessor for config file path
Expand All @@ -693,16 +693,7 @@ pub fn config_file_path_public() -> Option<PathBuf> {

/// Returns the path to the git-ai base directory (~/.git-ai)
pub fn git_ai_dir_path() -> Option<PathBuf> {
#[cfg(windows)]
{
let home = env::var("USERPROFILE").ok()?;
Some(Path::new(&home).join(".git-ai"))
}
#[cfg(not(windows))]
{
let home = env::var("HOME").ok()?;
Some(Path::new(&home).join(".git-ai"))
}
Some(home_dir().join(".git-ai"))
}

/// Returns the path to the internal state directory (~/.git-ai/internal)
Expand Down
36 changes: 35 additions & 1 deletion src/mdm/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,41 @@ pub fn is_github_codespaces() -> bool {

/// Get the user's home directory
pub fn home_dir() -> PathBuf {
dirs::home_dir().unwrap_or_else(|| PathBuf::from("."))
#[cfg(windows)]
{
if let Ok(userprofile) = std::env::var("USERPROFILE") {
if !userprofile.is_empty() {
return PathBuf::from(userprofile);
}
}

if let (Ok(home_drive), Ok(home_path)) =
(std::env::var("HOMEDRIVE"), std::env::var("HOMEPATH"))
{
if !home_drive.is_empty() && !home_path.is_empty() {
return PathBuf::from(format!("{}{}", home_drive, home_path));
}
}

if let Ok(home) = std::env::var("HOME")
&& !home.is_empty()
{
return PathBuf::from(home);
}

return dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
}

#[cfg(not(windows))]
{
if let Ok(home) = std::env::var("HOME")
&& !home.is_empty()
{
return PathBuf::from(home);
}

dirs::home_dir().unwrap_or_else(|| PathBuf::from("."))
}
}

/// Write data to a file atomically (write to temp, then rename)
Expand Down
Loading