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
9 changes: 9 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ Source order is deterministic and lives at `scripts/gitb.sh:105-138`. `common.sh

`init.sh` runs git config / remote probes at source time. Tests that only need the validators / sanitizers can short-circuit those by exporting `GITBASHER_SKIP_INIT_QUERIES=1` before sourcing — that's what the `source_gitbasher_lite` helper in `tests/setup_suite.bash` does. The flag is internal to the test layer; production runs never set it.

### Long-running commands and progress

Anything that can take more than a moment must show that it is alive. Use the helpers in `common.sh` instead of a bare `out=$(git ... 2>&1)`:

- **`stream_or_capture_git out_var code_var shown_var git <cmd> --progress ...`** — for git commands (fetch, push, checkout/switch, merge, stash, worktree add). It captures output and exit code for parsing (progress meters stripped), shows a spinner with elapsed seconds after ~1s, and streams git's native progress if the command outlives a ~10s quiet window. Pass `--progress` only to subcommands that accept it; it is removed automatically when there is no TTY.
- **`run_with_spinner "Label" cmd args...`** — for anything else (AI requests). stdout passes through, so it works inside `$(...)`. The command runs in the background with stdin detached, so never use it for commands that prompt or open an editor.

Both stay silent without a TTY (tests/CI) and honour `GITBASHER_NO_SPINNER=1`. Network commands skip the spinner when git might prompt for credentials (HTTPS without a credential helper, SSH without an agent) so the prompt is not overwritten.

### Top-level dispatch

`scripts/base.sh` is the dispatcher: a `case "$1"` block (`base.sh:127-229`) maps each command name and its aliases to the corresponding `*_script` function. The convention: `commit_script`, `push_script`, `branch_script`, etc. — defined in their respective files.
Expand Down
4 changes: 4 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ Run `gitb sync`. It shows the divergence and rebases by default (use `gitb sync

Bare `gitb log` is now an interactive browser: a numbered, paginated commit list where picking a commit shows it and offers actions (diff, stat, copy hash, revert, cherry-pick, fixup, restore a file). The old full dump is still there as `gitb log all` (alias `dump`). Piping still works — non-interactive runs print a plain `git log`-style listing and exit. You can also pass an argument directly: a number (`gitb log 20`), a file path, a ref or range, or words to search commit messages. Page size: `git config gitbasher.log-count 30`.

### What is the spinner on long commands, and can I turn it off?

Fetches, pushes, pulls, branch switches, merges, stashes, worktree creation and AI requests show a spinner with elapsed seconds once they take longer than about a second. If a fetch, push, or checkout runs past ~10s, git's own progress (`Receiving objects: 45%`, `Updating files: 80%`) takes over. Quick commands print nothing extra. Set `GITBASHER_NO_SPINNER=1` to turn the spinner off; it is also off automatically when output is not a terminal, and for network commands where git may ask for a password.

## Contributing and bugs

### How do I report a bug?
Expand Down
14 changes: 11 additions & 3 deletions scripts/ai.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,7 @@ function call_ai_api {
# demands bare JSON and squash_parse_ai_plan extracts and validates it.
if [ "$provider" = "claude" ]; then
local claude_out
claude_out=$(_call_claude_cli "$system_prompt" "$user_prompt" "$model")
claude_out=$(run_with_spinner "Waiting for AI response" _call_claude_cli "$system_prompt" "$user_prompt" "$model")
if [ $? -ne 0 ] || [ -z "$claude_out" ]; then
clear_sensitive_vars
return 1
Expand Down Expand Up @@ -1272,9 +1272,9 @@ function call_ai_api {
# Retry logic for server errors
while [ $retry_count -le $max_retries ]; do
if [ -n "$safe_proxy_url" ]; then
response=$(secure_curl_with_api_key "$safe_proxy_url" "$api_key" "$json_payload" "$api_url" "$provider")
response=$(run_with_spinner "Waiting for AI response" secure_curl_with_api_key "$safe_proxy_url" "$api_key" "$json_payload" "$api_url" "$provider")
else
response=$(secure_curl_with_api_key "" "$api_key" "$json_payload" "$api_url" "$provider")
response=$(run_with_spinner "Waiting for AI response" secure_curl_with_api_key "" "$api_key" "$json_payload" "$api_url" "$provider")
fi

curl_exit_code=$?
Expand Down Expand Up @@ -1519,6 +1519,14 @@ function call_ai_api {
}


### call_ai_api with its stderr (error messages, inner spinner) silenced,
### for callers that report failures themselves; wrap it in run_with_spinner
### to keep the wait visible.
function call_ai_api_quiet {
call_ai_api "$@" 2>/dev/null
}


### Function to check if AI tools are available
# Returns: 0 if available, 1 if not
function check_ai_available {
Expand Down
28 changes: 16 additions & 12 deletions scripts/branch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,9 @@ function branch_script {
echo -e "${YELLOW}Fetching and pruning remote references...${ENDCOLOR}"
echo

fetch_output=$(git fetch --prune 2>&1)
check_code $? "$fetch_output" "fetch and prune remote"
stream_or_capture_git fetch_output fetch_code fetch_progress_shown \
git fetch --progress --prune
check_code "$fetch_code" "$fetch_output" "fetch and prune remote"

# Find local branches whose upstream is gone. Plumbing, not
# `git branch -vv` scraping: the human format prefixes worktree
Expand Down Expand Up @@ -229,8 +230,9 @@ function branch_script {
if [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
echo
echo -e "${YELLOW}Fetching remote tags...${ENDCOLOR}"
fetch_output=$(git fetch --tags 2>&1)
check_code $? "$fetch_output" "fetch remote tags"
stream_or_capture_git fetch_output fetch_code fetch_progress_shown \
git fetch --progress --tags
check_code "$fetch_code" "$fetch_output" "fetch remote tags"
fi
echo

Expand Down Expand Up @@ -285,8 +287,8 @@ function branch_script {
echo -e "${RED}✗ Invalid tag name: ${selected_tag}${ENDCOLOR}"
exit 1
fi
checkout_output=$(git checkout "$sanitized_git_name" 2>&1)
checkout_code=$?
stream_or_capture_git checkout_output checkout_code checkout_progress_shown \
git checkout --progress "$sanitized_git_name"

if [ $checkout_code -eq 0 ]; then
echo -e "${GREEN}✓ Checked out tag '${selected_tag}'${ENDCOLOR}"
Expand Down Expand Up @@ -319,8 +321,9 @@ function branch_script {
echo -e "${YELLOW}Fetching remote...${ENDCOLOR}"
echo

fetch_output=$(git fetch 2>&1)
check_code $? "$fetch_output" "fetch remote"
stream_or_capture_git fetch_output fetch_code fetch_progress_shown \
git fetch --progress
check_code "$fetch_code" "$fetch_output" "fetch remote"

prune_output=$(git remote prune "$origin_name" 2>&1)

Expand Down Expand Up @@ -350,8 +353,9 @@ function branch_script {
echo -e "${YELLOW}Fetching remote and pruning...${ENDCOLOR}"
echo

fetch_output=$(git fetch --prune 2>&1)
check_code $? "$fetch_output" "fetch and prune remote"
stream_or_capture_git fetch_output fetch_code fetch_progress_shown \
git fetch --progress --prune
check_code "$fetch_code" "$fetch_output" "fetch and prune remote"

# Get all local branches
IFS=$'\n' read -rd '' -a local_branches <<<"$(git branch --format='%(refname:short)' | cat 2>&1)"
Expand Down Expand Up @@ -597,8 +601,8 @@ function branch_script {
echo
echo -e "${YELLOW}Deleting from remote...${ENDCOLOR}"

push_output=$(LC_ALL=C git push "$origin_name" -d "$branch_name" 2>&1)
push_code=$?
LC_ALL=C stream_or_capture_git push_output push_code push_progress_shown \
git push --progress "$origin_name" -d "$branch_name"

echo
if [ "$push_code" != 0 ]; then
Expand Down
4 changes: 3 additions & 1 deletion scripts/commit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,9 @@ Read <diff> to decide which files implement the same feature. Output TSV (scope<
local ai_response
# "low" reasoning: grouping is a structural decision over many files —
# worth ~1s of thinking, unlike plain message generation (see call_ai_api).
ai_response=$(call_ai_api "$system_prompt" "$user_prompt" "$AI_MAX_TOKENS_FULL" "" "" "low" 2>/dev/null)
# The spinner wraps a stderr-silenced call: errors stay hidden (the caller
# reports its own fallback reason) while the wait is still visible.
ai_response=$(run_with_spinner "Grouping changes with AI" call_ai_api_quiet "$system_prompt" "$user_prompt" "$AI_MAX_TOKENS_FULL" "" "" "low")
if [ $? -ne 0 ] || [ -z "$ai_response" ]; then
ai_grouping_fail_reason="API error"
return 1
Expand Down
Loading
Loading