Thanks to visit codestin.com
Credit goes to github.com

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
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "git-ai"
version = "1.0.1"
version = "1.0.2"
edition = "2024"


Expand Down
82 changes: 82 additions & 0 deletions docs/enterprise-configuration.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
title: Enterprise Configuration
---

`git-ai`'s behavior can be configured on developer machines by writing a JSON file in the user's home directory.

On Linux and macOS, this file is located at `$HOME/.git-ai/config.json`.
On Windows, this file is located at `%USERPROFILE%\.git-ai\config.json`.

## Options

All the options in `config.json` are optional, and will fall back to default values if not provided.

| --- | --- | --- |
| `git_path` `Path` | The path to the (unaltered) `git` binary you distribute on developer machines | Defaults to whichever git is on the shell path |
| `ignore_prompts` `boolean` flag | Prompts be excluded from authorship logs | `false` |
| `allow_repositories` `Path[]` | Allow `git-ai` in only these remotes | If not specified or set to an empty list, all repositories are allowed. |

```json
{
"git_path": "/usr/bin/git",
"ignore_prompts": false,
"allow_repositories": [
"https://github.com/acunniffe/git-ai.git"
]
}
```

## Installing `git-ai` binary on developer machines

When `git-ai` is installed using the [`install.sh` script](https://github.com/acunniffe/git-ai?tab=readme-ov-file#install) (reccomended for personal use) the downloaded binary will be configured to handle calls to both `git` and `git-ai`, effectively creating a wrapper/proxy to `git`.

If you would like to create a custom installation here is what `git-ai` requires to works correctly cross platform:

### Directory Structure

**Unix/Linux/macOS:**
- Install the `git-ai` binary to: `$HOME/.git-ai/bin/git-ai`
- Create a symlink: `$HOME/.git-ai/bin/git` → `$HOME/.git-ai/bin/git-ai`
- Create a symlink: `$HOME/.git-ai/bin/git-og` → `/path/to/original/git`
- Make the binary executable: `chmod +x $HOME/.git-ai/bin/git-ai`
- On macOS only: Remove quarantine attribute: `xattr -d com.apple.quarantine $HOME/.git-ai/bin/git-ai`

**Windows:**
- Install the binary to: `%USERPROFILE%\.git-ai\bin\git-ai.exe`
- Create a copy: `%USERPROFILE%\.git-ai\bin\git.exe` (copy of `git-ai.exe`)
- Create a batch file: `%USERPROFILE%\.git-ai\bin\git-og.cmd` that calls the original git executable
- Unblock the downloaded files (PowerShell: `Unblock-File`)

### PATH Configuration

**Unix/Linux/macOS:**
- Add `$HOME/.git-ai/bin` to the beginning of the user's PATH
- Update the appropriate shell config file (`.zshrc`, `.bashrc`, etc.)

**Windows:**
- Add `%USERPROFILE%\.git-ai\bin` to the System PATH
- The directory should be positioned **before** any existing Git installation directories to ensure the git-ai shim takes precedence

### Configuration File

Create `$HOME/.git-ai/config.json` (or `%USERPROFILE%\.git-ai\config.json` on Windows) with the options outlined at the top of this page.

### IDE/Agent Hook Installation

After installing the binary and configuring PATH, run:

```bash
git-ai install-hooks
```

This sets up integration with supported IDEs and AI coding agents (Cursor, VS Code with GitHub Copilot, etc.).

### Reference Implementation

Our official install scripts implement all of these requirements and can serve as references:
- Unix/Linux/macOS: [`install.sh`](https://github.com/acunniffe/git-ai/blob/main/install.sh)
- Windows: [`install.ps1`](https://github.com/acunniffe/git-ai/blob/main/install.ps1)

These scripts handle edge cases like detecting the original git path, preventing recursive installations, and gracefully handling errors.


11 changes: 10 additions & 1 deletion src/commands/git_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,17 @@ pub fn handle_git(args: &[String]) {
// println!("command_args: {:?}", parsed_args.command_args);
// println!("to_invocation_vec: {:?}", parsed_args.to_invocation_vec());

let config = config::Config::get();

let skip_hooks = !config.is_allowed_repository(&repository_option);
if skip_hooks {
debug_log(
"Skipping git-ai hooks because repository does not have at least one remote in allow_repositories list",
);
}

// run with hooks
let exit_status = if !parsed_args.is_help && has_repo {
let exit_status = if !parsed_args.is_help && has_repo && !skip_hooks {
let repository = repository_option.as_mut().unwrap();
run_pre_command_hooks(&mut command_hooks_context, &parsed_args, repository);
let exit_status = proxy_to_git(&parsed_args.to_invocation_vec(), false);
Expand Down
32 changes: 32 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
use std::collections::HashSet;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;

use serde::Deserialize;

use crate::git::repository::Repository;

/// Centralized configuration for the application
pub struct Config {
git_path: String,
ignore_prompts: bool,
allow_repositories: HashSet<String>,
}
#[derive(Deserialize)]
struct FileConfig {
#[serde(default)]
git_path: Option<String>,
#[serde(default)]
ignore_prompts: Option<bool>,
#[serde(default)]
allow_repositories: Option<Vec<String>>,
}

static CONFIG: OnceLock<Config> = OnceLock::new();
Expand All @@ -42,6 +48,25 @@ impl Config {
self.ignore_prompts
}

pub fn is_allowed_repository(&self, repository: &Option<Repository>) -> bool {
// If allowlist is empty, allow everything
if self.allow_repositories.is_empty() {
return true;
}

// If allowlist is defined, only allow repos whose remotes match the list
if let Some(repository) = repository {
match repository.remotes_with_urls().ok() {
Some(remotes) => remotes
.iter()
.any(|remote| self.allow_repositories.contains(&remote.1)),
None => false, // Can't verify, deny by default when allowlist is active
}
} else {
false // No repository provided, deny by default when allowlist is active
}
}

/// Returns whether prompts should be ignored (currently unused by internal APIs).
#[allow(dead_code)]
pub fn ignore_prompts(&self) -> bool {
Expand All @@ -55,12 +80,19 @@ fn build_config() -> Config {
.as_ref()
.and_then(|c| c.ignore_prompts)
.unwrap_or(false);
let allow_repositories = file_cfg
.as_ref()
.and_then(|c| c.allow_repositories.clone())
.unwrap_or(vec![])
.into_iter()
.collect();

let git_path = resolve_git_path(&file_cfg);

Config {
git_path,
ignore_prompts,
allow_repositories,
}
}

Expand Down
27 changes: 27 additions & 0 deletions src/git/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,33 @@ impl Repository {
Ok(remotes.trim().split("\n").map(|s| s.to_string()).collect())
}

// List all remotes with their URLs as tuples (name, url)
pub fn remotes_with_urls(&self) -> Result<Vec<(String, String)>, GitAiError> {
let mut args = self.global_args_for_exec();
args.push("remote".to_string());
args.push("-v".to_string());

let output = exec_git(&args)?;
let remotes_output = String::from_utf8(output.stdout)?;

let mut remotes = Vec::new();
let mut seen = std::collections::HashSet::new();

for line in remotes_output.trim().split("\n").filter(|s| !s.is_empty()) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let name = parts[0].to_string();
let url = parts[1].to_string();
// Only add each remote once (git remote -v shows fetch and push)
if seen.insert(name.clone()) {
remotes.push((name, url));
}
}
}

Ok(remotes)
}

pub fn config_get_str(&self, key: &str) -> Result<Option<String>, GitAiError> {
let mut args = self.global_args_for_exec();
args.push("config".to_string());
Expand Down
Loading