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
14 changes: 8 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Version is injected at build time: `go build -ldflags "-X main.Version=$(git rev

```
main.go / main_test.go CLI entry point and tests
completions_test.go Bash/fish completion tests (spawn built binary)
get/get.go AbsolutePath, ParseURL, Directory, Clone
completions_test.go Bash/fish/zsh/pwsh completion tests (spawn built binary)
get/get.go AbsolutePath, ShortPath, ParseURL, Directory, Clone, RedactRemote
get/complete.go Shell completion walker
completions/ Shell completion scripts (bash, fish, zsh, ps1)
man/git-get.1 Man page (update when adding flags)
Expand All @@ -39,14 +39,16 @@ run() --complete → Complete() → AbsolutePath() (walks GETPATH for shell co
- `Directory` — strips `.git` suffix, returns `host/path`; guarantees the result is `filepath.IsLocal` (never escapes upward via `..`) so any GETPATH it's joined to stays contained
- `Clone` — three cases: `.git` present → return dir; dir exists without `.git` → error; dir absent → ls-remote, mkdir, clone
- `ShortPath` — inverse of `AbsolutePath`'s `~` expansion; used by `main.go` to render GETPATH in usage output
- `run` parses flags itself: `--complete` first, then `-h`/`--help`/`--version` anywhere, then options. A lone `-v` is the version; with a repository it is `--verbose`. Unknown or duplicate options and extra positionals are errors; `--` ends options.
- stdout carries only the resulting path (scripts do `cd $(git get …)`); verbose output goes to stderr via `verboseOption` (`Clone`'s `io.Writer`, nil = quiet). Git output is printed after each command finishes, not streamed: the wrapper's executor uses `CombinedOutput`, and streaming would need `exec.Command`.

## Hard constraints

**Git operations:** use `github.com/ldez/go-git-cmd-wrapper/v2` exclusively. Never use `exec.Command("git", ...)` or build command strings manually.
**Git operations:** use `github.com/ldez/go-git-cmd-wrapper/v2` exclusively. Never use `exec.Command("git", ...)` or build command strings to execute. (`shellQuote` only formats the verbose log line.)

**Dependencies:** zero new dependencies. The only permitted external module is `github.com/ldez/go-git-cmd-wrapper/v2`.

**Error handling:** `fmt.Errorf("context: %w", err)`. Set `url.User = nil` before formatting any URL into an error string.
**Error handling:** `fmt.Errorf("context: %w", err)`. Format URLs in errors and verbose logs through `redact()` (drops userinfo except a bare ssh username); raw user input through `get.RedactRemote()`.

**Not-exist checks:** `errors.Is(err, fs.ErrNotExist)` — not `os.IsNotExist` (deprecated).

Expand All @@ -60,12 +62,12 @@ run() --complete → Complete() → AbsolutePath() (walks GETPATH for shell co
- `t.TempDir()` for filesystem isolation; `t.Setenv()` for env vars.
- Always call `gitConfigGlobalFixture(t)` to redirect `GIT_CONFIG_GLOBAL` to a temp file — defined once per package (`main_test.go`, `get/get_test.go`); other test files in the same package reuse it. Do not import across packages.
- Guard network tests with `if testing.Short() { t.Skip(...) }`.
- Completion tests (`completions_test.go`) build and spawn a real binary via the bash/fish scripts; `completionEnv` sets `GETPATH`, `HOME`, and `PATH` explicitly and inherits `GIT_CONFIG_GLOBAL`. They skip if the shell is not installed.
- Completion tests (`completions_test.go`) build and spawn a real binary via the bash/fish/zsh/pwsh scripts; `completionEnv` sets `GETPATH`, `HOME`, and `PATH` explicitly and inherits `GIT_CONFIG_GLOBAL`. They skip if the shell is not installed.
- Offline clone tests: map a fake `https://example.test/` to a local `git init --bare` remote with `url.<file://…>.insteadOf` in `GIT_CONFIG_GLOBAL` (see `TestRunVerbose`).
- The `clone ssh github` case needs GitHub SSH auth locally; CI rewrites SSH to HTTPS via `git config url.https://github.com/.insteadOf ssh://git@github.com/`.

## Do not

- Use `os.IsNotExist`; use `errors.Is(err, fs.ErrNotExist)`.
- Skip the `ls-remote` validation in `Clone` — it must fire before any directory is created.
- Add user-facing flags without updating the usage string in `main.go` and `man/git-get.1`.
- Expose `--complete`; it is an internal flag consumed by `completions/` scripts only.
Expand Down
19 changes: 14 additions & 5 deletions completions/git-get.bash
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
_git_get() {
local limit=1
[[ "${COMP_WORDS[0]}" == "git" ]] && limit=2
if [[ $COMP_CWORD -gt $limit ]]; then
compopt +o default +o bashdefault 2>/dev/null
local start=1
[[ "${COMP_WORDS[0]}" == "git" ]] && start=2
local cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "-h --help -v --verbose --version" -- "$cur"))
return
fi
COMPREPLY=($(git-get --complete "${COMP_WORDS[COMP_CWORD]}" 2>/dev/null))
# Only the repository is completed; stop once one is already given.
local i
for ((i = start; i < COMP_CWORD; i++)); do
if [[ "${COMP_WORDS[i]}" != -* ]]; then
compopt +o default +o bashdefault 2>/dev/null
return
fi
done
COMPREPLY=($(git-get --complete "$cur" 2>/dev/null))
}
complete -F _git_get git-get
24 changes: 20 additions & 4 deletions completions/git-get.fish
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,23 @@ function __fish_gitget_completion
git-get --complete (commandline -ct) 2>/dev/null
end

complete -c git-get -f -n 'test (count (commandline -opc)) -le 1' -a "(__fish_gitget_completion)"
complete -c git-get -f -n 'test (count (commandline -opc)) -gt 1'
complete -c git -n '__fish_git_using_command get; and test (count (commandline -opc)) -le 2' -f -a "(__fish_gitget_completion)"
complete -c git -n '__fish_git_using_command get; and test (count (commandline -opc)) -gt 2' -f
# True while no repository has been given yet. $argv[1] is the number of
# leading tokens that precede git-get's own arguments (1 for git-get, 2 for git get).
function __fish_gitget_needs_repo
set -l i 0
for t in (commandline -opc)
set i (math $i + 1)
test $i -le $argv[1]; and continue
string match -q -- '-*' $t; or return 1
end
return 0
end

complete -c git-get -s h -l help -f -d 'Show help'
complete -c git-get -s v -l verbose -f -d 'Show git commands and output'
complete -c git-get -l version -f -d 'Show version'
complete -c git-get -f -n '__fish_gitget_needs_repo 1' -a "(__fish_gitget_completion)"
complete -c git -n '__fish_git_using_command get; and __fish_gitget_needs_repo 2' -f -a "(__fish_gitget_completion)"
complete -c git -n '__fish_git_using_command get' -s h -l help -f -d 'Show help'
complete -c git -n '__fish_git_using_command get' -s v -l verbose -f -d 'Show git commands and output'
complete -c git -n '__fish_git_using_command get' -l version -f -d 'Show version'
24 changes: 19 additions & 5 deletions completions/git-get.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
# $skip is the number of leading elements before git-get's own arguments (1 for git-get, 2 for git get).
$gitGetCompleter = {
param($wordToComplete, $commandAst, $cursorPosition)
if ($commandAst.CommandElements.Count -gt 2) { return }
param($wordToComplete, $commandAst, $cursorPosition, $skip = 1)
$elements = @($commandAst.CommandElements | ForEach-Object { $_.Extent.Text })

if ($wordToComplete -like '-*') {
'-h', '--help', '-v', '--verbose', '--version' | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $_)
}
return
}

# Skip the command itself and the word being completed;
# only the repository is completed, so stop once one is already given.
$given = @($elements | Select-Object -Skip $skip)
if ($wordToComplete -ne '') { $given = @($given | Select-Object -SkipLast 1) }
if (@($given | Where-Object { $_ -notlike '-*' }).Count -gt 0) { return }

git-get --complete "$wordToComplete" 2>$null | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
Expand All @@ -10,8 +25,7 @@ Register-ArgumentCompleter -Native -CommandName git-get -ScriptBlock $gitGetComp

Register-ArgumentCompleter -Native -CommandName git -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
$c = $commandAst.CommandElements.Count
if ($c -ge 2 -and $c -le 3 -and $commandAst.CommandElements[1].Value -eq 'get') {
& $gitGetCompleter $wordToComplete $commandAst $cursorPosition
if ($commandAst.CommandElements.Count -ge 2 -and $commandAst.CommandElements[1].Extent.Text -eq 'get') {
& $gitGetCompleter $wordToComplete $commandAst $cursorPosition 2
}
}
17 changes: 15 additions & 2 deletions completions/git-get.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@

_git_get() {
emulate -L zsh
local c="${cword:-$CURRENT}"
[[ $c -gt 2 ]] && { _ret=0; return }
# Index of git-get's first argument in $words (git get <repo> vs git-get <repo>).
local start=2 i
[[ "${words[1]}" == git ]] && start=3
# Under git's zsh wrapper CURRENT is shifted down by one but $words is not; cword is the real index - 1.
local pos=${cword:+$((cword + 1))}
pos=${pos:-$CURRENT}
local prefix="${cur-${words[$CURRENT]}}"
if [[ "$prefix" == -* ]]; then
compadd -- -h --help -v --verbose --version
_ret=0
return
fi
# Only the repository is completed; stop once one is already given.
for (( i = start; i < pos; i++ )); do
[[ "${words[$i]}" != -* ]] && { _ret=0; return }
done
local completions
completions=($(git-get --complete "$prefix" 2>/dev/null))
compadd -S "" -- ${(M)completions:#*/}
Expand Down
Loading
Loading