Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
4 changes: 3 additions & 1 deletion .github/workflows/homebrew.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ on:
release:
types: [published]

permissions: read-all

jobs:
homebrew:
name: Bump Homebrew formula
runs-on: ubuntu-latest
if: ${{ !github.event.release.prerelease }}
steps:
- uses: mislav/bump-homebrew-formula-action@v3
- uses: mislav/bump-homebrew-formula-action@56a283fa15557e9abaa4bdb63b8212abc68e655c # v3
with:
formula-name: git-gtr
formula-path: Formula/git-gtr.rb
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ on:
pull_request:
branches: [main]

permissions: read-all

jobs:
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Install ShellCheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
Expand All @@ -24,7 +26,7 @@ jobs:
name: Completions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Verify completion files are up to date
run: ./scripts/generate-completions.sh --check
Expand All @@ -33,7 +35,7 @@ jobs:
name: Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Install BATS
run: sudo apt-get update && sudo apt-get install -y bats
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,16 @@ git gtr clean --merged --force --yes # Force-clean and auto-confirm

**Note:** The `--merged` mode auto-detects your hosting provider (GitHub or GitLab) from the `origin` remote URL and requires the corresponding CLI tool (`gh` or `glab`) to be installed and authenticated. For self-hosted instances, set the provider explicitly: `git gtr config set gtr.provider gitlab`.

### `git gtr trust`

Review and approve hook commands defined in the repository's `.gtrconfig` file. Hooks from `.gtrconfig` are **not executed** until explicitly trusted — this prevents malicious contributors from injecting arbitrary shell commands via shared config files.

```bash
git gtr trust # Review and approve .gtrconfig hooks
```

Trust is stored per content hash and must be re-approved if hooks change. Hooks from your local git config (`.git/config`, `~/.gitconfig`) are always trusted.

### Other Commands

- `git gtr doctor` - Health check (verify git, editors, AI tools)
Expand Down Expand Up @@ -390,10 +400,12 @@ git gtr config set gtr.ui.color never
ai = claude
```

**Hook trust:** Hooks defined in `.gtrconfig` require explicit approval before they execute. Run `git gtr trust` after cloning a repository or when `.gtrconfig` hooks change. This protects against malicious hook injection in shared repositories.

**Configuration precedence** (highest to lowest):

1. `git config --local` (`.git/config`) - personal overrides
2. `.gtrconfig` (repo root) - team defaults
2. `.gtrconfig` (repo root) - team defaults (hooks require `git gtr trust`)
3. `git config --global` (`~/.gitconfig`) - user defaults

> For complete configuration reference including all settings, hooks, file copying patterns, and environment variables, see [docs/configuration.md](docs/configuration.md)
Expand Down
3 changes: 3 additions & 0 deletions bin/git-gtr
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ main() {
init)
cmd_init "$@"
;;
trust)
cmd_trust "$@"
;;
version|--version|-v)
echo "git gtr version $GTR_VERSION"
;;
Expand Down
1 change: 1 addition & 0 deletions completions/_git-gtr
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ _git-gtr() {
'config:Manage configuration'
'completion:Generate shell completions'
'init:Generate shell integration for cd support'
'trust:Trust .gtrconfig hooks'
'version:Show version'
'help:Show help'
)
Expand Down
1 change: 1 addition & 0 deletions completions/git-gtr.fish
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ complete -f -c git -n '__fish_git_gtr_using_command completion' -a 'bash zsh fis
complete -f -c git -n '__fish_git_gtr_needs_command' -a init -d 'Generate shell integration for cd support'
complete -f -c git -n '__fish_git_gtr_using_command init' -a 'bash zsh fish' -d 'Shell type'
complete -c git -n '__fish_git_gtr_using_command init' -l as -d 'Custom function name' -r
complete -f -c git -n '__fish_git_gtr_needs_command' -a trust -d 'Trust .gtrconfig hooks'
complete -f -c git -n '__fish_git_gtr_needs_command' -a version -d 'Show version'
complete -f -c git -n '__fish_git_gtr_needs_command' -a help -d 'Show help'

Expand Down
2 changes: 1 addition & 1 deletion completions/gtr.bash
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ _git_gtr() {

# If we're completing the first argument after 'git gtr'
if [ "$cword" -eq 2 ]; then
COMPREPLY=($(compgen -W "new go run copy editor ai rm mv rename ls list clean doctor adapter config completion init help version" -- "$cur"))
COMPREPLY=($(compgen -W "new go run copy editor ai rm mv rename ls list clean doctor adapter config completion init trust help version" -- "$cur"))
return 0
fi

Expand Down
51 changes: 39 additions & 12 deletions lib/adapters.sh
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,7 @@ editor_open() {
target="$workspace"
fi

# $GTR_EDITOR_CMD may contain arguments (e.g., "code --wait")
# Using eval here is necessary to handle multi-word commands properly
eval "$GTR_EDITOR_CMD \"\$target\""
_run_configured_command "$GTR_EDITOR_CMD" "$target"
}

# Globals set by load_ai_adapter: GTR_AI_CMD, GTR_AI_CMD_NAME
Expand All @@ -179,9 +177,20 @@ ai_can_start() {
ai_start() {
local path="$1"
shift
# $GTR_AI_CMD may contain arguments (e.g., "bunx @github/copilot@latest")
# Using eval here is necessary to handle multi-word commands properly
(cd "$path" && eval "$GTR_AI_CMD \"\$@\"")
(cd "$path" && _run_configured_command "$GTR_AI_CMD" "$@")
}

# Parse and run a config-supplied command string while preserving quoted args.
_run_configured_command() {
local command_string="$1"
shift
local extra_args=("$@")

(
eval "set -- $command_string" || exit 1
[ "$#" -gt 0 ] || exit 1
"$@" "${extra_args[@]}"
)
}

# Standard AI adapter builder — used by adapter files that follow the common pattern
Expand Down Expand Up @@ -295,14 +304,21 @@ resolve_workspace_file() {
# Usage: _load_adapter <type> <name> <label> <builtin_list> <path_hint>
_load_adapter() {
local type="$1" name="$2" label="$3" builtin_list="$4" path_hint="$5"
local adapter_file="$GTR_DIR/adapters/${type}/${name}.sh"
local adapter_selector="${name%% *}"

local adapter_file="$GTR_DIR/adapters/${type}/${adapter_selector}.sh"

# 1. Try loading explicit adapter file (custom overrides like claude, nano)
if [ -f "$adapter_file" ]; then
# shellcheck disable=SC1090
. "$adapter_file"
return 0
fi
case "$adapter_selector" in
*/* | *..* | *\\*) ;;
*)
if [ -f "$adapter_file" ]; then
# shellcheck disable=SC1090
. "$adapter_file"
return 0
fi
;;
esac

# 2. Try registry lookup (declarative adapters)
local registry entry
Expand Down Expand Up @@ -332,6 +348,17 @@ _load_adapter() {
return 1
fi

# Reject shell metacharacters in config-supplied command names to prevent injection
# Allows multi-word commands (e.g., "code --wait") but blocks shell operators
# shellcheck disable=SC2016 # Literal '$(' pattern match is intentional
case "$name" in
*\;* | *\`* | *'$('* | *\|* | *\&* | *'>'* | *'<'*)
log_error "$label '$name' contains shell metacharacters — refusing to execute"
log_info "Use a simple command name, optionally with flags (e.g., 'code --wait')"
return 1
;;
esac

# Set globals for generic adapter functions
# Note: $name may contain arguments (e.g., "code --wait", "bunx @github/copilot@latest")
if [ "$type" = "editor" ]; then
Expand Down
4 changes: 1 addition & 3 deletions lib/args.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,13 @@ _pa_match_flag() {

# Check if $flag matches any alternative in the pattern
local alt matched=0
local IFS_save="$IFS"
IFS="|"
local IFS="|"
for alt in $line; do
if [ "$flag" = "$alt" ]; then
matched=1
break
fi
done
IFS="$IFS_save"

if [ "$matched" = "1" ]; then
# Derive variable name from the first (canonical) pattern
Expand Down
26 changes: 26 additions & 0 deletions lib/commands/help.sh
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,27 @@ Command palette (gtr cd with no arguments, requires fzf):
EOF
}

_help_trust() {
cat <<'EOF'
git gtr trust - Trust .gtrconfig hooks

Usage: git gtr trust

Reviews and approves hook commands defined in the repository's .gtrconfig
file. Hooks from .gtrconfig are not executed until explicitly trusted.

This prevents malicious contributors from injecting arbitrary shell
commands via shared .gtrconfig files. Trust is stored per content hash
in ~/.config/gtr/trusted/ and must be re-approved if hooks change.

Hooks from your local git config (.git/config, ~/.gitconfig) are always
trusted since you control those files directly.

Examples:
git gtr trust # Review and approve hooks
EOF
}

_help_version() {
cat <<'EOF'
git gtr version - Show version
Expand Down Expand Up @@ -572,6 +593,11 @@ SETUP & MAINTENANCE:
--dry-run, -n: show what would be removed without removing
--force, -f: force removal even if worktree has uncommitted changes or untracked files

trust
Review and approve .gtrconfig hook commands
Hooks from .gtrconfig are not executed until trusted
Trust is re-required when hook content changes

completion <shell>
Generate shell completions (bash, zsh, fish)
Usage: eval "$(git gtr completion zsh)"
Expand Down
Loading
Loading