Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
92 changes: 92 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ git2 = { version = "0.20.2", optional = true }
diff-match-patch-rs = "0.5.1"
strsim = "0.11.1"
jsonc-parser = { version = "0.27", features = ["cst"] }
dirs = "5.0"
minreq = { version = "2.12", features = ["https-rustls"] }
url = "2.5"
glob = "0.3"
Expand All @@ -36,3 +37,4 @@ predicates = "3.0"
insta = "1.38"
rand = "0.8"
regex = "1.10"
filetime = "0.2"
18 changes: 17 additions & 1 deletion docs/enterprise-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ All options in `config.json` are optional and will fall back to default values i
| `exclude_repositories` | `Pattern[]` | Exclude `git-ai` from these remotes. Supports glob patterns (e.g., `https://github.com/myorg/*`) | If a repository is present in both allow and exclude lists, exclusion takes precedence |
| `telemetry_oss` | `"off"` | Disable OSS performance metrics and error logging sent to Git AI maintainers | Defaults to enabled |
| `telemetry_enterprise_dsn` | `string` | A Sentry DSN to use to send yourself performance metrics and error logging | Defaults to none |
| `disable_version_checks` | `boolean` | Skip automated version checks that would otherwise run on fetch/pull/push | `false` |
| `disable_auto_updates` | `boolean` | Keep checking for updates but never install them automatically | `false` |
| `update_channel` | `"latest" \| "next"` | Release channel to follow (`latest` = stable, `next` = prerelease) | `"latest"` |

## Example Configuration

Expand All @@ -33,10 +36,23 @@ All options in `config.json` are optional and will fall back to default values i
],
"exclude_repositories": [
"https://github.com/internal/private-repo.git"
]
],
"disable_version_checks": false,
"disable_auto_updates": false,
"update_channel": "latest"
}
```

## Update Controls

Most enterprises roll out new binaries gradually. Combine these three options to match your rollout plan:

- `update_channel` selects which release feed each machine follows. Use `next` for early adopters and keep the rest of the org on `latest`.
- `disable_version_checks` completely skips the asynchronous checks that normally run on fetch/pull/push. Only use this if you manage upgrades manually.
- `disable_auto_updates` keeps those checks but stops the background installer, so the CLI only surfaces a "run git-ai upgrade" message.

Because the config file lives in each user's home directory, you can templatize these fields through MDM, your endpoint management tool, or any bootstrap script.

## Configuration Use Cases

### Limiting to Specific Repositories
Expand Down
68 changes: 62 additions & 6 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@ function Write-Warning {
Write-Host $Message -ForegroundColor Yellow
}

function Wait-ForFileAvailable {
param(
[Parameter(Mandatory = $true)][string]$Path,
[Parameter(Mandatory = $false)][int]$MaxWaitSeconds = 300,
[Parameter(Mandatory = $false)][int]$RetryIntervalSeconds = 5
)

$elapsed = 0
while ($elapsed -lt $MaxWaitSeconds) {
try {
# Try to open the file for writing to check if it's available
$stream = [System.IO.File]::Open($Path, 'Open', 'Write', 'None')
$stream.Close()
return $true
} catch {
if ($elapsed -eq 0) {
Write-Host "Waiting for file to be available: $Path" -ForegroundColor Yellow
}
Start-Sleep -Seconds $RetryIntervalSeconds
$elapsed += $RetryIntervalSeconds
}
}
return $false
}

# GitHub repository details
$Repo = 'acunniffe/git-ai'

Expand Down Expand Up @@ -195,14 +220,24 @@ $os = 'windows'

# Determine binary name and download URLs
$binaryName = "git-ai-$os-$arch"
$downloadUrlExe = "https://github.com/$Repo/releases/latest/download/$binaryName.exe"
$downloadUrlNoExt = "https://github.com/$Repo/releases/latest/download/$binaryName"
$releaseTag = $env:GIT_AI_RELEASE_TAG
if ([string]::IsNullOrWhiteSpace($releaseTag)) {
$releaseTag = 'latest'
}

if ($releaseTag -eq 'latest') {
$downloadUrlExe = "https://github.com/$Repo/releases/latest/download/$binaryName.exe"
$downloadUrlNoExt = "https://github.com/$Repo/releases/latest/download/$binaryName"
} else {
$downloadUrlExe = "https://github.com/$Repo/releases/download/$releaseTag/$binaryName.exe"
$downloadUrlNoExt = "https://github.com/$Repo/releases/download/$releaseTag/$binaryName"
}

# Install directory: %USERPROFILE%\.git-ai\bin
$installDir = Join-Path $HOME ".git-ai\bin"
New-Item -ItemType Directory -Force -Path $installDir | Out-Null

Write-Host 'Downloading git-ai...'
Write-Host ("Downloading git-ai (release: {0})..." -f $releaseTag)
$tmpFile = Join-Path $installDir "git-ai.tmp.$PID.exe"

function Try-Download {
Expand Down Expand Up @@ -237,11 +272,28 @@ try {
}

$finalExe = Join-Path $installDir 'git-ai.exe'

# Wait for git-ai.exe to be available if it exists and is in use
if (Test-Path -LiteralPath $finalExe) {
if (-not (Wait-ForFileAvailable -Path $finalExe -MaxWaitSeconds 300 -RetryIntervalSeconds 5)) {
Remove-Item -Force -ErrorAction SilentlyContinue $tmpFile
Write-ErrorAndExit "Timeout waiting for $finalExe to be available. Please close any running git-ai processes and try again."
}
}

Move-Item -Force -Path $tmpFile -Destination $finalExe
try { Unblock-File -Path $finalExe -ErrorAction SilentlyContinue } catch { }

# Create a shim so calling `git` goes through git-ai by PATH precedence
$gitShim = Join-Path $installDir 'git.exe'

# Wait for git.exe shim to be available if it exists and is in use
if (Test-Path -LiteralPath $gitShim) {
if (-not (Wait-ForFileAvailable -Path $gitShim -MaxWaitSeconds 300 -RetryIntervalSeconds 5)) {
Write-ErrorAndExit "Timeout waiting for $gitShim to be available. Please close any running git processes and try again."
}
}

Copy-Item -Force -Path $finalExe -Destination $gitShim
try { Unblock-File -Path $gitShim -ErrorAction SilentlyContinue } catch { }

Expand Down Expand Up @@ -281,14 +333,18 @@ if ($pathUpdate.MachineStatus -eq 'Updated') {
Write-Success "Successfully installed git-ai into $installDir"
Write-Success "You can now run 'git-ai' from your terminal"

# Write JSON config at %USERPROFILE%\.git-ai\config.json
# Write JSON config at %USERPROFILE%\.git-ai\config.json (only if it doesn't exist)
try {
$configDir = Join-Path $HOME '.git-ai'
$configJsonPath = Join-Path $configDir 'config.json'
New-Item -ItemType Directory -Force -Path $configDir | Out-Null

$cfg = @{ git_path = $stdGitPath; ignore_prompts = $false } | ConvertTo-Json -Compress
$cfg | Out-File -FilePath $configJsonPath -Encoding UTF8 -Force
if (-not (Test-Path -LiteralPath $configJsonPath)) {
$cfg = @{
git_path = $stdGitPath
} | ConvertTo-Json -Compress
$cfg | Out-File -FilePath $configJsonPath -Encoding UTF8 -Force
}
} catch {
Write-Host "Warning: Failed to write config.json: $($_.Exception.Message)" -ForegroundColor Yellow
}
Expand Down
25 changes: 16 additions & 9 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,14 @@ esac
# Determine binary name
BINARY_NAME="git-ai-${OS}-${ARCH}"

# Download URL
DOWNLOAD_URL="https://github.com/${REPO}/releases/latest/download/${BINARY_NAME}"
# Determine release tag (defaults to latest but can be overridden)
RELEASE_TAG="${GIT_AI_RELEASE_TAG:-latest}"
if [ -n "$RELEASE_TAG" ] && [ "$RELEASE_TAG" != "latest" ]; then
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${BINARY_NAME}"
else
DOWNLOAD_URL="https://github.com/${REPO}/releases/latest/download/${BINARY_NAME}"
RELEASE_TAG="latest"
fi

# Install into the user's bin directory ~/.git-ai/bin
INSTALL_DIR="$HOME/.git-ai/bin"
Expand All @@ -154,7 +160,7 @@ INSTALL_DIR="$HOME/.git-ai/bin"
mkdir -p "$INSTALL_DIR"

# Download and install
echo "Downloading git-ai..."
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
Expand Down Expand Up @@ -195,19 +201,20 @@ else
success "Successfully set up IDE/agent hooks"
fi

# Write JSON config at ~/.git-ai/config.json
# Write JSON config at ~/.git-ai/config.json (only if it doesn't exist)
CONFIG_DIR="$HOME/.git-ai"
CONFIG_JSON_PATH="$CONFIG_DIR/config.json"
mkdir -p "$CONFIG_DIR"

TMP_CFG="$CONFIG_JSON_PATH.tmp.$$"
cat >"$TMP_CFG" <<EOF
if [ ! -f "$CONFIG_JSON_PATH" ]; then
TMP_CFG="$CONFIG_JSON_PATH.tmp.$$"
cat >"$TMP_CFG" <<EOF
{
"git_path": "${STD_GIT_PATH}",
"ignore_prompts": false
"git_path": "${STD_GIT_PATH}"
}
EOF
mv -f "$TMP_CFG" "$CONFIG_JSON_PATH"
mv -f "$TMP_CFG" "$CONFIG_JSON_PATH"
fi

# Add to PATH automatically if not already there
if [[ ":$PATH:" != *"$INSTALL_DIR"* ]]; then
Expand Down
9 changes: 8 additions & 1 deletion src/commands/git_ai_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ pub fn handle_git_ai(args: &[String]) {
"ci" => {
commands::ci_handlers::handle_ci(&args[1..]);
}
"upgrade" => {
commands::upgrade::run_with_args(&args[1..]);
}
"flush-logs" => {
commands::flush_logs::handle_flush_logs(&args[1..]);
}
Expand Down Expand Up @@ -119,9 +122,13 @@ fn print_help() {
eprintln!(" ci Continuous integration utilities");
eprintln!(" github GitHub CI helpers");
eprintln!(" squash-authorship Generate authorship log for squashed commits");
eprintln!(" <base_branch> <new_sha> <old_sha> Required: base branch, new commit SHA, old commit SHA");
eprintln!(
" <base_branch> <new_sha> <old_sha> Required: base branch, new commit SHA, old commit SHA"
);
eprintln!(" --dry-run Show what would be done without making changes");
eprintln!(" git-path Print the path to the underlying git executable");
eprintln!(" upgrade Check for updates and install if available");
eprintln!(" --force Reinstall latest version even if already up to date");
eprintln!(" version, -v, --version Print the git-ai version");
eprintln!(" help, -h, --help Show this help message");
eprintln!("");
Expand Down
3 changes: 3 additions & 0 deletions src/commands/hooks/fetch_hooks.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::commands::git_handlers::CommandHooksContext;
use crate::commands::upgrade;
use crate::git::cli_parser::{ParsedGitInvocation, is_dry_run};
use crate::git::repository::{Repository, find_repository};
use crate::git::sync_authorship::{fetch_authorship_notes, fetch_remote_from_args};
Expand All @@ -8,6 +9,8 @@ pub fn fetch_pull_pre_command_hook(
parsed_args: &ParsedGitInvocation,
repository: &Repository,
) -> Option<std::thread::JoinHandle<()>> {
upgrade::maybe_schedule_background_update_check();

// Early return for dry-run
if is_dry_run(&parsed_args.command_args) {
return None;
Expand Down
Loading