gitbasher 5.0.1 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +30 -38
- package/dist/gitb +732 -276
- package/package.json +1 -1
package/dist/gitb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -o pipefail
|
|
3
|
-
GITBASHER_VERSION="5.0
|
|
3
|
+
GITBASHER_VERSION="5.2.0"
|
|
4
4
|
if [ "$1" == "init" ] || [ "$1" == "i" ]; then
|
|
5
5
|
git init
|
|
6
6
|
fi
|
|
@@ -839,29 +839,7 @@ function config_source_tag {
|
|
|
839
839
|
default) echo -e "${GRAY}(default)${ENDCOLOR}" ;;
|
|
840
840
|
esac
|
|
841
841
|
}
|
|
842
|
-
function
|
|
843
|
-
echo -e "${YELLOW}Current configuration:${ENDCOLOR}"
|
|
844
|
-
local user_name=$(get_config_value user.name)
|
|
845
|
-
if [ -n "$user_name" ]; then
|
|
846
|
-
echo -e "\tuser.name:\t${YELLOW}$user_name${ENDCOLOR} $(config_source_tag user.name)"
|
|
847
|
-
else
|
|
848
|
-
echo -e "\tuser.name:\t${RED}not set${ENDCOLOR}"
|
|
849
|
-
fi
|
|
850
|
-
local user_email=$(get_config_value user.email)
|
|
851
|
-
if [ -n "$user_email" ]; then
|
|
852
|
-
echo -e "\tuser.email:\t${YELLOW}$user_email${ENDCOLOR} $(config_source_tag user.email)"
|
|
853
|
-
else
|
|
854
|
-
echo -e "\tuser.email:\t${RED}not set${ENDCOLOR}"
|
|
855
|
-
fi
|
|
856
|
-
echo -e "\tdefault:\t${YELLOW}$main_branch${ENDCOLOR} $(config_source_tag gitbasher.branch)"
|
|
857
|
-
echo -e "\tseparator:\t${YELLOW}$sep${ENDCOLOR} $(config_source_tag gitbasher.sep)"
|
|
858
|
-
echo -e "\teditor:\t\t${YELLOW}$editor${ENDCOLOR} $(config_source_tag core.editor)"
|
|
859
|
-
if [ "$ticket_name" != "" ]; then
|
|
860
|
-
echo -e "\tticket:\t\t${YELLOW}$ticket_name${ENDCOLOR} $(config_source_tag gitbasher.ticket)"
|
|
861
|
-
fi
|
|
862
|
-
if [ "$scopes" != "" ]; then
|
|
863
|
-
echo -e "\tscopes:\t\t${YELLOW}$scopes${ENDCOLOR} $(config_source_tag gitbasher.scopes)"
|
|
864
|
-
fi
|
|
842
|
+
function print_ai_configuration {
|
|
865
843
|
local ai_provider=$(get_ai_provider)
|
|
866
844
|
echo -e "\tAI provider:\t${GREEN}$ai_provider${ENDCOLOR} $(config_source_tag gitbasher.ai-provider)"
|
|
867
845
|
local ai_base_url=$(get_ai_base_url)
|
|
@@ -897,12 +875,48 @@ function print_configuration {
|
|
|
897
875
|
fi
|
|
898
876
|
local ai_model=$(get_ai_model)
|
|
899
877
|
if [ -n "$ai_model" ]; then
|
|
900
|
-
|
|
878
|
+
local model_key="gitbasher.ai-model-${ai_provider}"
|
|
879
|
+
if [ "$(config_source "$model_key")" = "default" ]; then
|
|
880
|
+
model_key="gitbasher.ai-model"
|
|
881
|
+
fi
|
|
882
|
+
echo -e "\tAI model:\t${GREEN}$ai_model${ENDCOLOR} $(config_source_tag "$model_key")"
|
|
901
883
|
else
|
|
902
|
-
echo -e "\tAI
|
|
903
|
-
echo -e "\t\t\t${GREEN}$(get_ai_model_for full)${ENDCOLOR} ${GRAY}(full)${ENDCOLOR} $(config_source_tag gitbasher.ai-model-full)"
|
|
904
|
-
echo -e "\t\t\t${GREEN}$(get_ai_model_for grouping)${ENDCOLOR} ${GRAY}(grouping)${ENDCOLOR} $(config_source_tag gitbasher.ai-model-grouping)"
|
|
884
|
+
echo -e "\tAI model:\t${GREEN}$(get_ai_default_model)${ENDCOLOR} ${GRAY}(default)${ENDCOLOR}"
|
|
905
885
|
fi
|
|
886
|
+
local other_models=""
|
|
887
|
+
while IFS= read -r prov; do
|
|
888
|
+
[ -z "$prov" ] && continue
|
|
889
|
+
[ "$prov" = "$ai_provider" ] && continue
|
|
890
|
+
other_models="${other_models:+${other_models}, }${prov}"
|
|
891
|
+
done < <(list_providers_with_model)
|
|
892
|
+
if [ -n "$other_models" ]; then
|
|
893
|
+
echo -e "\t\t\t${GRAY}also set for: ${other_models}${ENDCOLOR}"
|
|
894
|
+
fi
|
|
895
|
+
}
|
|
896
|
+
function print_configuration {
|
|
897
|
+
echo -e "${YELLOW}Current configuration:${ENDCOLOR}"
|
|
898
|
+
local user_name=$(get_config_value user.name)
|
|
899
|
+
if [ -n "$user_name" ]; then
|
|
900
|
+
echo -e "\tuser.name:\t${YELLOW}$user_name${ENDCOLOR} $(config_source_tag user.name)"
|
|
901
|
+
else
|
|
902
|
+
echo -e "\tuser.name:\t${RED}not set${ENDCOLOR}"
|
|
903
|
+
fi
|
|
904
|
+
local user_email=$(get_config_value user.email)
|
|
905
|
+
if [ -n "$user_email" ]; then
|
|
906
|
+
echo -e "\tuser.email:\t${YELLOW}$user_email${ENDCOLOR} $(config_source_tag user.email)"
|
|
907
|
+
else
|
|
908
|
+
echo -e "\tuser.email:\t${RED}not set${ENDCOLOR}"
|
|
909
|
+
fi
|
|
910
|
+
echo -e "\tdefault:\t${YELLOW}$main_branch${ENDCOLOR} $(config_source_tag gitbasher.branch)"
|
|
911
|
+
echo -e "\tseparator:\t${YELLOW}$sep${ENDCOLOR} $(config_source_tag gitbasher.sep)"
|
|
912
|
+
echo -e "\teditor:\t\t${YELLOW}$editor${ENDCOLOR} $(config_source_tag core.editor)"
|
|
913
|
+
if [ "$ticket_name" != "" ]; then
|
|
914
|
+
echo -e "\tticket:\t\t${YELLOW}$ticket_name${ENDCOLOR} $(config_source_tag gitbasher.ticket)"
|
|
915
|
+
fi
|
|
916
|
+
if [ "$scopes" != "" ]; then
|
|
917
|
+
echo -e "\tscopes:\t\t${YELLOW}$scopes${ENDCOLOR} $(config_source_tag gitbasher.scopes)"
|
|
918
|
+
fi
|
|
919
|
+
print_ai_configuration
|
|
906
920
|
local ai_proxy=$(get_ai_proxy)
|
|
907
921
|
if [ -n "$ai_proxy" ]; then
|
|
908
922
|
echo -e "\tAI proxy:\t${GREEN}$ai_proxy${ENDCOLOR} $(config_source_tag gitbasher.ai-proxy)"
|
|
@@ -1548,6 +1562,9 @@ function set_config_value {
|
|
|
1548
1562
|
git config --local "$1" "$2"
|
|
1549
1563
|
else
|
|
1550
1564
|
git config --global "$1" "$2"
|
|
1565
|
+
if [ "$GITBASHER_NO_REPO" != "true" ]; then
|
|
1566
|
+
git config --local --unset "$1" 2>/dev/null || true
|
|
1567
|
+
fi
|
|
1551
1568
|
fi
|
|
1552
1569
|
printf '%s\n' "$2"
|
|
1553
1570
|
}
|
|
@@ -1561,7 +1578,7 @@ function unset_config_value {
|
|
|
1561
1578
|
if [ -n "$global_config" ]; then
|
|
1562
1579
|
echo
|
|
1563
1580
|
echo -e "${YELLOW}Global $1 is also configured: ${BLUE}$global_config${ENDCOLOR}"
|
|
1564
|
-
echo -e "Do you want to clear it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
1581
|
+
echo -e "Do you want to clear it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
1565
1582
|
local _unset_key=""
|
|
1566
1583
|
read_key _unset_key || _unset_key="n"
|
|
1567
1584
|
if is_yes "$_unset_key"; then
|
|
@@ -1693,9 +1710,9 @@ readonly OPENAI_API_URL="https://api.openai.com/v1/chat/completions"
|
|
|
1693
1710
|
readonly AI_DEFAULT_OLLAMA_HOST="http://localhost:11434"
|
|
1694
1711
|
readonly AI_DEFAULT_PROVIDER="openrouter"
|
|
1695
1712
|
readonly AI_TEMPERATURE="0.3"
|
|
1696
|
-
readonly AI_MAX_TOKENS_SUBJECT=
|
|
1697
|
-
readonly AI_MAX_TOKENS_SIMPLE=
|
|
1698
|
-
readonly AI_MAX_TOKENS_FULL=
|
|
1713
|
+
readonly AI_MAX_TOKENS_SUBJECT=512
|
|
1714
|
+
readonly AI_MAX_TOKENS_SIMPLE=1024
|
|
1715
|
+
readonly AI_MAX_TOKENS_FULL=2048
|
|
1699
1716
|
function get_ai_api_key {
|
|
1700
1717
|
local provider=$(get_ai_provider)
|
|
1701
1718
|
local provider_upper
|
|
@@ -1826,74 +1843,74 @@ function is_valid_model_id {
|
|
|
1826
1843
|
}
|
|
1827
1844
|
function ai_provider_requires_api_key {
|
|
1828
1845
|
case "$(get_ai_provider)" in
|
|
1829
|
-
ollama) return 1 ;;
|
|
1830
|
-
*)
|
|
1846
|
+
ollama|claude) return 1 ;;
|
|
1847
|
+
*) return 0 ;;
|
|
1831
1848
|
esac
|
|
1832
1849
|
}
|
|
1833
1850
|
function get_ai_model {
|
|
1851
|
+
local provider=$(get_ai_provider)
|
|
1852
|
+
local provider_model
|
|
1853
|
+
provider_model=$(get_config_value "gitbasher.ai-model-${provider}" "")
|
|
1854
|
+
if [ -n "$provider_model" ]; then
|
|
1855
|
+
echo "$provider_model"
|
|
1856
|
+
return
|
|
1857
|
+
fi
|
|
1834
1858
|
get_config_value gitbasher.ai-model ""
|
|
1835
1859
|
}
|
|
1836
1860
|
function set_ai_model {
|
|
1837
|
-
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
readonly AI_DEFAULT_MODEL_GROUPING_OLLAMA="qwen3:8b"
|
|
1851
|
-
function get_ai_model_for {
|
|
1852
|
-
local task="$1"
|
|
1853
|
-
local task_model
|
|
1854
|
-
task_model=$(get_config_value "gitbasher.ai-model-$task" "")
|
|
1855
|
-
if [ -n "$task_model" ]; then
|
|
1856
|
-
echo "$task_model"
|
|
1857
|
-
return
|
|
1861
|
+
local provider=$(get_ai_provider)
|
|
1862
|
+
set_config_value "gitbasher.ai-model-${provider}" "$1" "$2"
|
|
1863
|
+
}
|
|
1864
|
+
function migrate_legacy_ai_model_to {
|
|
1865
|
+
local target_provider="$1"
|
|
1866
|
+
local migrated=""
|
|
1867
|
+
local local_legacy=$(git config --local --get gitbasher.ai-model 2>/dev/null)
|
|
1868
|
+
if [ -n "$local_legacy" ]; then
|
|
1869
|
+
if ! git config --local --get "gitbasher.ai-model-${target_provider}" >/dev/null 2>&1; then
|
|
1870
|
+
git config --local "gitbasher.ai-model-${target_provider}" "$local_legacy"
|
|
1871
|
+
migrated="local"
|
|
1872
|
+
fi
|
|
1873
|
+
git config --local --unset gitbasher.ai-model 2>/dev/null || true
|
|
1858
1874
|
fi
|
|
1859
|
-
local
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1875
|
+
local global_legacy=$(git config --global --get gitbasher.ai-model 2>/dev/null)
|
|
1876
|
+
if [ -n "$global_legacy" ]; then
|
|
1877
|
+
if ! git config --global --get "gitbasher.ai-model-${target_provider}" >/dev/null 2>&1; then
|
|
1878
|
+
git config --global "gitbasher.ai-model-${target_provider}" "$global_legacy"
|
|
1879
|
+
migrated="${migrated:+$migrated, }global"
|
|
1880
|
+
fi
|
|
1881
|
+
git config --global --unset gitbasher.ai-model 2>/dev/null || true
|
|
1882
|
+
fi
|
|
1883
|
+
if [ -n "$migrated" ]; then
|
|
1884
|
+
echo -e "${GRAY}↻ Kept model for provider '${target_provider}' (${migrated})${ENDCOLOR}" >&2
|
|
1864
1885
|
fi
|
|
1886
|
+
}
|
|
1887
|
+
function list_providers_with_model {
|
|
1888
|
+
{
|
|
1889
|
+
git config --local --get-regexp '^gitbasher\.ai-model-' 2>/dev/null
|
|
1890
|
+
git config --global --get-regexp '^gitbasher\.ai-model-' 2>/dev/null
|
|
1891
|
+
} | awk '{print $1}' | sed 's/^gitbasher\.ai-model-//' \
|
|
1892
|
+
| grep -E '^(openrouter|openai|ollama|claude)$' | sort -u
|
|
1893
|
+
}
|
|
1894
|
+
readonly AI_DEFAULT_MODEL_OPENROUTER="google/gemini-3.5-flash"
|
|
1895
|
+
readonly AI_DEFAULT_MODEL_OPENAI="gpt-5.4-mini"
|
|
1896
|
+
readonly AI_DEFAULT_MODEL_OLLAMA="qwen3:8b"
|
|
1897
|
+
readonly AI_DEFAULT_MODEL_CLAUDE="haiku"
|
|
1898
|
+
function get_ai_default_model {
|
|
1865
1899
|
case "$(get_ai_provider)" in
|
|
1866
|
-
openai)
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
full) echo "$AI_DEFAULT_MODEL_FULL_OPENAI" ;;
|
|
1871
|
-
grouping) echo "$AI_DEFAULT_MODEL_GROUPING_OPENAI" ;;
|
|
1872
|
-
*) echo "$AI_DEFAULT_MODEL_SIMPLE_OPENAI" ;;
|
|
1873
|
-
esac
|
|
1874
|
-
;;
|
|
1875
|
-
ollama)
|
|
1876
|
-
case "$task" in
|
|
1877
|
-
simple) echo "$AI_DEFAULT_MODEL_SIMPLE_OLLAMA" ;;
|
|
1878
|
-
subject) echo "$AI_DEFAULT_MODEL_SUBJECT_OLLAMA" ;;
|
|
1879
|
-
full) echo "$AI_DEFAULT_MODEL_FULL_OLLAMA" ;;
|
|
1880
|
-
grouping) echo "$AI_DEFAULT_MODEL_GROUPING_OLLAMA" ;;
|
|
1881
|
-
*) echo "$AI_DEFAULT_MODEL_SIMPLE_OLLAMA" ;;
|
|
1882
|
-
esac
|
|
1883
|
-
;;
|
|
1884
|
-
*)
|
|
1885
|
-
case "$task" in
|
|
1886
|
-
simple) echo "$AI_DEFAULT_MODEL_SIMPLE" ;;
|
|
1887
|
-
subject) echo "$AI_DEFAULT_MODEL_SUBJECT" ;;
|
|
1888
|
-
full) echo "$AI_DEFAULT_MODEL_FULL" ;;
|
|
1889
|
-
grouping) echo "$AI_DEFAULT_MODEL_GROUPING" ;;
|
|
1890
|
-
*) echo "$AI_DEFAULT_MODEL_SIMPLE" ;;
|
|
1891
|
-
esac
|
|
1892
|
-
;;
|
|
1900
|
+
openai) echo "$AI_DEFAULT_MODEL_OPENAI" ;;
|
|
1901
|
+
ollama) echo "$AI_DEFAULT_MODEL_OLLAMA" ;;
|
|
1902
|
+
claude) echo "$AI_DEFAULT_MODEL_CLAUDE" ;;
|
|
1903
|
+
*) echo "$AI_DEFAULT_MODEL_OPENROUTER" ;;
|
|
1893
1904
|
esac
|
|
1894
1905
|
}
|
|
1895
|
-
function
|
|
1896
|
-
|
|
1906
|
+
function resolve_ai_model {
|
|
1907
|
+
local model
|
|
1908
|
+
model=$(get_ai_model)
|
|
1909
|
+
if [ -n "$model" ]; then
|
|
1910
|
+
echo "$model"
|
|
1911
|
+
return
|
|
1912
|
+
fi
|
|
1913
|
+
get_ai_default_model
|
|
1897
1914
|
}
|
|
1898
1915
|
function get_ai_proxy {
|
|
1899
1916
|
get_config_value gitbasher.ai-proxy ""
|
|
@@ -2148,11 +2165,89 @@ function ollama_list_models {
|
|
|
2148
2165
|
local base resp names
|
|
2149
2166
|
base=$(ollama_api_base)
|
|
2150
2167
|
resp=$(curl -s --max-time 5 "${base}/api/tags" 2>/dev/null)
|
|
2151
|
-
[ -z "$resp" ] && return 1
|
|
2152
2168
|
names=$(printf '%s' "$resp" | _parse_ollama_model_names)
|
|
2169
|
+
if [ -z "$names" ] && command -v ollama >/dev/null 2>&1; then
|
|
2170
|
+
names=$(OLLAMA_HOST="$base" ollama list 2>/dev/null \
|
|
2171
|
+
| LC_ALL=C awk 'NR>1 && $1 != "" {print $1}')
|
|
2172
|
+
fi
|
|
2153
2173
|
[ -z "$names" ] && return 1
|
|
2154
2174
|
printf '%s\n' "$names"
|
|
2155
2175
|
}
|
|
2176
|
+
function secure_curl_get {
|
|
2177
|
+
local proxy_url="$1"
|
|
2178
|
+
local api_key="$2"
|
|
2179
|
+
local url="$3"
|
|
2180
|
+
(
|
|
2181
|
+
{ set +x; } 2>/dev/null
|
|
2182
|
+
unset HISTFILE
|
|
2183
|
+
local curl_cmd=(
|
|
2184
|
+
curl -s
|
|
2185
|
+
--connect-timeout 10
|
|
2186
|
+
--max-time 20
|
|
2187
|
+
)
|
|
2188
|
+
[ -n "$proxy_url" ] && curl_cmd+=(--proxy "$proxy_url")
|
|
2189
|
+
curl_cmd+=("$url")
|
|
2190
|
+
if [ -n "$api_key" ]; then
|
|
2191
|
+
local escaped_key="${api_key//\\/\\\\}"
|
|
2192
|
+
escaped_key="${escaped_key//\"/\\\"}"
|
|
2193
|
+
"${curl_cmd[@]}" --config - <<EOF 2>/dev/null
|
|
2194
|
+
header = "Authorization: Bearer ${escaped_key}"
|
|
2195
|
+
EOF
|
|
2196
|
+
else
|
|
2197
|
+
"${curl_cmd[@]}" 2>/dev/null
|
|
2198
|
+
fi
|
|
2199
|
+
)
|
|
2200
|
+
}
|
|
2201
|
+
function _parse_model_ids_json {
|
|
2202
|
+
if command -v jq &>/dev/null; then
|
|
2203
|
+
jq -r '.data[]?.id // empty' 2>/dev/null
|
|
2204
|
+
else
|
|
2205
|
+
LC_ALL=C grep -o '"id"[[:space:]]*:[[:space:]]*"[^"]*"' \
|
|
2206
|
+
| LC_ALL=C sed 's/.*"\([^"]*\)"$/\1/'
|
|
2207
|
+
fi
|
|
2208
|
+
}
|
|
2209
|
+
function openrouter_list_models {
|
|
2210
|
+
local sort_key="${1:-top-weekly}"
|
|
2211
|
+
local models_url resp ids
|
|
2212
|
+
models_url="${OPENROUTER_API_URL/chat\/completions/models}?sort=${sort_key}"
|
|
2213
|
+
resp=$(secure_curl_get "$(get_ai_proxy)" "" "$models_url")
|
|
2214
|
+
[ -z "$resp" ] && return 1
|
|
2215
|
+
ids=$(printf '%s' "$resp" | _parse_model_ids_json)
|
|
2216
|
+
[ -z "$ids" ] && return 1
|
|
2217
|
+
printf '%s\n' "$ids"
|
|
2218
|
+
}
|
|
2219
|
+
function openai_list_models {
|
|
2220
|
+
local api_key resp ids
|
|
2221
|
+
api_key=$(get_ai_api_key)
|
|
2222
|
+
[ -z "$api_key" ] && return 1
|
|
2223
|
+
local models_url="${OPENAI_API_URL/chat\/completions/models}"
|
|
2224
|
+
resp=$(secure_curl_get "$(get_ai_proxy)" "$api_key" "$models_url")
|
|
2225
|
+
[ -z "$resp" ] && return 1
|
|
2226
|
+
if command -v jq &>/dev/null; then
|
|
2227
|
+
ids=$(printf '%s' "$resp" | jq -r '.data // [] | sort_by(-(.created // 0)) | .[].id // empty' 2>/dev/null)
|
|
2228
|
+
else
|
|
2229
|
+
ids=$(printf '%s' "$resp" | _parse_model_ids_json)
|
|
2230
|
+
fi
|
|
2231
|
+
ids=$(printf '%s\n' "$ids" \
|
|
2232
|
+
| LC_ALL=C grep -E '^(gpt-|o[0-9]|chatgpt-)' \
|
|
2233
|
+
| LC_ALL=C grep -vE 'embedding|tts|whisper|audio|realtime|dall-e|image|moderation|transcribe|search|instruct')
|
|
2234
|
+
[ -z "$ids" ] && return 1
|
|
2235
|
+
printf '%s\n' "$ids"
|
|
2236
|
+
}
|
|
2237
|
+
function ai_model_smoke_check {
|
|
2238
|
+
local model out rc
|
|
2239
|
+
model=$(resolve_ai_model)
|
|
2240
|
+
echo -e "${YELLOW}Testing model '${model}' with a live request...${ENDCOLOR}"
|
|
2241
|
+
out=$(call_ai_api "Reply with exactly: OK" "ping" 64 "$model" 2>&1)
|
|
2242
|
+
rc=$?
|
|
2243
|
+
if [ "$rc" -eq 0 ] && [ -n "$out" ]; then
|
|
2244
|
+
echo -e "${GREEN}✓ Model '${model}' responded${ENDCOLOR}"
|
|
2245
|
+
return 0
|
|
2246
|
+
fi
|
|
2247
|
+
echo -e "${RED}✗ Model '${model}' did not respond${ENDCOLOR}" >&2
|
|
2248
|
+
[ -n "$out" ] && printf '%s\n' "$out" | head -4 >&2
|
|
2249
|
+
return 1
|
|
2250
|
+
}
|
|
2156
2251
|
function ai_smoke_check {
|
|
2157
2252
|
local provider
|
|
2158
2253
|
provider=$(get_ai_provider)
|
|
@@ -2176,6 +2271,17 @@ function ai_smoke_check {
|
|
|
2176
2271
|
echo -e "${GREEN}✓ Ollama reachable at ${base} (${count} model(s))${ENDCOLOR}"
|
|
2177
2272
|
return 0
|
|
2178
2273
|
fi
|
|
2274
|
+
if [ "$provider" = "claude" ]; then
|
|
2275
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
2276
|
+
echo -e "${RED}✗ claude CLI not found on PATH.${ENDCOLOR}" >&2
|
|
2277
|
+
echo -e "${CYAN}💡 Install it with ${BLUE}npm install -g @anthropic-ai/claude-code${ENDCOLOR}${CYAN}, then sign in once.${ENDCOLOR}" >&2
|
|
2278
|
+
return 1
|
|
2279
|
+
fi
|
|
2280
|
+
local claude_version
|
|
2281
|
+
claude_version=$(claude --version 2>/dev/null | head -1)
|
|
2282
|
+
echo -e "${GREEN}✓ claude CLI available${claude_version:+ (}${claude_version}${claude_version:+)}${ENDCOLOR}"
|
|
2283
|
+
return 0
|
|
2284
|
+
fi
|
|
2179
2285
|
local api_key
|
|
2180
2286
|
api_key=$(get_ai_api_key)
|
|
2181
2287
|
if [ -z "$api_key" ]; then
|
|
@@ -2230,18 +2336,49 @@ function get_ai_timeout {
|
|
|
2230
2336
|
printf '%s\n' "$t"
|
|
2231
2337
|
return
|
|
2232
2338
|
fi
|
|
2233
|
-
|
|
2234
|
-
printf '300\n'
|
|
2339
|
+
case "$(get_ai_provider)" in
|
|
2340
|
+
ollama|claude) printf '300\n' ;;
|
|
2341
|
+
*) printf '60\n' ;;
|
|
2342
|
+
esac
|
|
2343
|
+
}
|
|
2344
|
+
function _call_claude_cli {
|
|
2345
|
+
local system_prompt="$1"
|
|
2346
|
+
local user_prompt="$2"
|
|
2347
|
+
local model="$3"
|
|
2348
|
+
local timeout_s output rc
|
|
2349
|
+
timeout_s=$(get_ai_timeout)
|
|
2350
|
+
local -a claude_args=(-p --model "$model" --output-format text
|
|
2351
|
+
--strict-mcp-config --mcp-config '{"mcpServers":{}}'
|
|
2352
|
+
--setting-sources "")
|
|
2353
|
+
if command -v perl >/dev/null 2>&1; then
|
|
2354
|
+
output=$(printf '%s\n\n%s' "$system_prompt" "$user_prompt" \
|
|
2355
|
+
| MAX_THINKING_TOKENS=0 perl -e 'alarm shift; exec @ARGV' "$timeout_s" \
|
|
2356
|
+
claude "${claude_args[@]}" 2>/dev/null)
|
|
2357
|
+
rc=$?
|
|
2235
2358
|
else
|
|
2236
|
-
printf '
|
|
2359
|
+
output=$(printf '%s\n\n%s' "$system_prompt" "$user_prompt" \
|
|
2360
|
+
| MAX_THINKING_TOKENS=0 claude "${claude_args[@]}" 2>/dev/null)
|
|
2361
|
+
rc=$?
|
|
2362
|
+
fi
|
|
2363
|
+
if [ "$rc" -eq 142 ]; then
|
|
2364
|
+
echo -e "${RED}✗ claude timed out after ${timeout_s}s.${ENDCOLOR}" >&2
|
|
2365
|
+
echo -e "${YELLOW}Raise the limit with:${ENDCOLOR} ${GREEN}git config gitbasher.ai-timeout <seconds>${ENDCOLOR}" >&2
|
|
2366
|
+
return 1
|
|
2237
2367
|
fi
|
|
2368
|
+
if [ "$rc" -ne 0 ] || [ -z "$output" ]; then
|
|
2369
|
+
echo -e "${RED}✗ claude CLI failed (exit ${rc}).${ENDCOLOR}" >&2
|
|
2370
|
+
echo -e "${CYAN}💡 Check it works: ${BLUE}echo hi | claude -p${ENDCOLOR}${CYAN} — you may need to sign in first.${ENDCOLOR}" >&2
|
|
2371
|
+
return 1
|
|
2372
|
+
fi
|
|
2373
|
+
printf '%s\n' "$output"
|
|
2238
2374
|
}
|
|
2239
2375
|
function call_ai_api {
|
|
2240
2376
|
local system_prompt="$1"
|
|
2241
2377
|
local user_prompt="$2"
|
|
2242
2378
|
local max_tokens="${3:-$AI_MAX_TOKENS_FULL}"
|
|
2243
|
-
local model="${4:-$(
|
|
2379
|
+
local model="${4:-$(resolve_ai_model)}"
|
|
2244
2380
|
local response_format="${5:-}"
|
|
2381
|
+
local reasoning_effort="${6:-}"
|
|
2245
2382
|
local provider=$(get_ai_provider)
|
|
2246
2383
|
local api_url=$(get_ai_api_url)
|
|
2247
2384
|
local api_key=$(get_ai_api_key)
|
|
@@ -2253,6 +2390,17 @@ function call_ai_api {
|
|
|
2253
2390
|
clear_sensitive_vars
|
|
2254
2391
|
return 1
|
|
2255
2392
|
fi
|
|
2393
|
+
if [ "$provider" = "claude" ]; then
|
|
2394
|
+
local claude_out
|
|
2395
|
+
claude_out=$(_call_claude_cli "$system_prompt" "$user_prompt" "$model")
|
|
2396
|
+
if [ $? -ne 0 ] || [ -z "$claude_out" ]; then
|
|
2397
|
+
clear_sensitive_vars
|
|
2398
|
+
return 1
|
|
2399
|
+
fi
|
|
2400
|
+
clear_sensitive_vars
|
|
2401
|
+
echo "$claude_out"
|
|
2402
|
+
return 0
|
|
2403
|
+
fi
|
|
2256
2404
|
local tokens_field="max_tokens"
|
|
2257
2405
|
if [ "$provider" = "openai" ]; then
|
|
2258
2406
|
tokens_field="max_completion_tokens"
|
|
@@ -2263,6 +2411,21 @@ function call_ai_api {
|
|
|
2263
2411
|
gpt-5*|o[0-9]*) include_temperature="" ;;
|
|
2264
2412
|
esac
|
|
2265
2413
|
fi
|
|
2414
|
+
case "$reasoning_effort" in
|
|
2415
|
+
low|medium|high|"") : ;;
|
|
2416
|
+
*) reasoning_effort="" ;; # unknown value → provider floor
|
|
2417
|
+
esac
|
|
2418
|
+
local or_reasoning="" oa_reasoning=""
|
|
2419
|
+
if [ -n "$(get_ai_base_url)" ]; then
|
|
2420
|
+
: # custom endpoint — send nothing
|
|
2421
|
+
elif [ "$provider" = "openrouter" ]; then
|
|
2422
|
+
or_reasoning="${reasoning_effort:-minimal}"
|
|
2423
|
+
elif [ "$provider" = "openai" ]; then
|
|
2424
|
+
case "$model" in
|
|
2425
|
+
gpt-5*chat*|chatgpt-*) : ;;
|
|
2426
|
+
gpt-5*|o[0-9]*) oa_reasoning="${reasoning_effort:-none}" ;;
|
|
2427
|
+
esac
|
|
2428
|
+
fi
|
|
2266
2429
|
local json_payload
|
|
2267
2430
|
if command -v jq &>/dev/null; then
|
|
2268
2431
|
json_payload=$(jq -nc \
|
|
@@ -2284,6 +2447,17 @@ function call_ai_api {
|
|
|
2284
2447
|
--argjson rf "$response_format" \
|
|
2285
2448
|
'$base + {response_format: $rf}')
|
|
2286
2449
|
fi
|
|
2450
|
+
if [ -n "$or_reasoning" ]; then
|
|
2451
|
+
json_payload=$(jq -nc \
|
|
2452
|
+
--argjson base "$json_payload" \
|
|
2453
|
+
--arg effort "$or_reasoning" \
|
|
2454
|
+
'$base + {reasoning: {effort: $effort}}')
|
|
2455
|
+
elif [ -n "$oa_reasoning" ]; then
|
|
2456
|
+
json_payload=$(jq -nc \
|
|
2457
|
+
--argjson base "$json_payload" \
|
|
2458
|
+
--arg effort "$oa_reasoning" \
|
|
2459
|
+
'$base + {reasoning_effort: $effort}')
|
|
2460
|
+
fi
|
|
2287
2461
|
else
|
|
2288
2462
|
local system_escaped=$(_json_escape_for_payload "$system_prompt")
|
|
2289
2463
|
local user_escaped=$(_json_escape_for_payload "$user_prompt")
|
|
@@ -2296,7 +2470,13 @@ function call_ai_api {
|
|
|
2296
2470
|
if [ -n "$include_temperature" ]; then
|
|
2297
2471
|
temp_field=",\"temperature\":$AI_TEMPERATURE"
|
|
2298
2472
|
fi
|
|
2299
|
-
|
|
2473
|
+
local reasoning_field=""
|
|
2474
|
+
if [ -n "$or_reasoning" ]; then
|
|
2475
|
+
reasoning_field=",\"reasoning\":{\"effort\":\"$or_reasoning\"}"
|
|
2476
|
+
elif [ -n "$oa_reasoning" ]; then
|
|
2477
|
+
reasoning_field=",\"reasoning_effort\":\"$oa_reasoning\""
|
|
2478
|
+
fi
|
|
2479
|
+
json_payload="{\"model\":\"$model_escaped\",\"messages\":[{\"role\":\"system\",\"content\":\"$system_escaped\"},{\"role\":\"user\",\"content\":\"$user_escaped\"}]${temp_field},\"${tokens_field}\":$max_tokens${rf_field}${reasoning_field}}"
|
|
2300
2480
|
fi
|
|
2301
2481
|
local proxy_url=$(get_ai_proxy)
|
|
2302
2482
|
local safe_proxy_url=""
|
|
@@ -2492,11 +2672,34 @@ function call_ai_api {
|
|
|
2492
2672
|
clear_sensitive_vars
|
|
2493
2673
|
return 1
|
|
2494
2674
|
fi
|
|
2675
|
+
local finish_reason=""
|
|
2676
|
+
if command -v jq &>/dev/null; then
|
|
2677
|
+
finish_reason=$(echo "$response" | jq -r '.choices[0].finish_reason // empty' 2>/dev/null)
|
|
2678
|
+
else
|
|
2679
|
+
case "$response" in
|
|
2680
|
+
*'"finish_reason":"length"'*|*'"finish_reason": "length"'*) finish_reason="length" ;;
|
|
2681
|
+
esac
|
|
2682
|
+
fi
|
|
2683
|
+
if [ "$finish_reason" = "length" ]; then
|
|
2684
|
+
echo -e "${RED}✗ AI response was cut off by the output-token limit.${ENDCOLOR}" >&2
|
|
2685
|
+
echo -e "${CYAN}💡 The model likely spent the budget on internal reasoning — pick a faster model with ${GREEN}gitb cfg model${ENDCOLOR}${CYAN}, or retry.${ENDCOLOR}" >&2
|
|
2686
|
+
clear_sensitive_vars
|
|
2687
|
+
return 1
|
|
2688
|
+
fi
|
|
2495
2689
|
clear_sensitive_vars
|
|
2496
2690
|
echo "$ai_response"
|
|
2497
2691
|
}
|
|
2498
2692
|
function check_ai_available {
|
|
2499
|
-
|
|
2693
|
+
local active_provider
|
|
2694
|
+
active_provider=$(get_ai_provider)
|
|
2695
|
+
if [ "$active_provider" = "claude" ]; then
|
|
2696
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
2697
|
+
echo -e "${RED}✗ claude CLI not found on PATH.${ENDCOLOR}" >&2
|
|
2698
|
+
echo -e "${YELLOW}Install it with:${ENDCOLOR} ${GREEN}npm install -g @anthropic-ai/claude-code${ENDCOLOR}" >&2
|
|
2699
|
+
echo -e "${YELLOW}Or switch provider with:${ENDCOLOR} ${GREEN}gitb cfg provider${ENDCOLOR}" >&2
|
|
2700
|
+
return 1
|
|
2701
|
+
fi
|
|
2702
|
+
elif ! command -v curl >/dev/null 2>&1; then
|
|
2500
2703
|
echo -e "${RED}curl is required for AI functionality but not installed${ENDCOLOR}" >&2
|
|
2501
2704
|
return 1
|
|
2502
2705
|
fi
|
|
@@ -2692,13 +2895,13 @@ function generate_ai_commit_message {
|
|
|
2692
2895
|
local system_prompt user_prompt
|
|
2693
2896
|
system_prompt=$(build_ai_commit_system_prompt "$mode" "$commit_prefix")
|
|
2694
2897
|
user_prompt=$(build_ai_commit_user_prompt "$mode" "$detected_scopes" "$provided_scopes" "$rejected_messages")
|
|
2695
|
-
local max_tokens
|
|
2898
|
+
local max_tokens
|
|
2696
2899
|
case "$mode" in
|
|
2697
|
-
subject) max_tokens="$AI_MAX_TOKENS_SUBJECT"
|
|
2698
|
-
full) max_tokens="$AI_MAX_TOKENS_FULL"
|
|
2699
|
-
*) max_tokens="$AI_MAX_TOKENS_SIMPLE"
|
|
2900
|
+
subject) max_tokens="$AI_MAX_TOKENS_SUBJECT" ;;
|
|
2901
|
+
full) max_tokens="$AI_MAX_TOKENS_FULL" ;;
|
|
2902
|
+
*) max_tokens="$AI_MAX_TOKENS_SIMPLE" ;;
|
|
2700
2903
|
esac
|
|
2701
|
-
call_ai_api "$system_prompt" "$user_prompt" "$max_tokens"
|
|
2904
|
+
call_ai_api "$system_prompt" "$user_prompt" "$max_tokens"
|
|
2702
2905
|
}
|
|
2703
2906
|
function clear_sensitive_vars {
|
|
2704
2907
|
api_key=""
|
|
@@ -2732,7 +2935,7 @@ function set_default_branch {
|
|
|
2732
2935
|
echo -e "${GREEN}✓ Set '${branch_name}' as the default gitbasher branch in '${project_name}'${ENDCOLOR}"
|
|
2733
2936
|
echo
|
|
2734
2937
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2735
|
-
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
2938
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
2736
2939
|
yes_no_choice "\nSet '${branch_name}' globally" "true"
|
|
2737
2940
|
main_branch=$(set_config_value gitbasher.branch $branch_name "true")
|
|
2738
2941
|
}
|
|
@@ -2770,7 +2973,7 @@ function set_sep {
|
|
|
2770
2973
|
echo -e "${GREEN}✓ Set '${sep}' as the branch name separator in '${project_name}'${ENDCOLOR}"
|
|
2771
2974
|
echo
|
|
2772
2975
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2773
|
-
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
2976
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
2774
2977
|
yes_no_choice "\nSet '${sep}' globally" "true"
|
|
2775
2978
|
sep=$(set_config_value gitbasher.sep "$new_sep" "true")
|
|
2776
2979
|
}
|
|
@@ -2800,7 +3003,7 @@ function set_editor {
|
|
|
2800
3003
|
echo -e "${GREEN}✓ Using editor '$editor' (${which_output})${ENDCOLOR}"
|
|
2801
3004
|
echo
|
|
2802
3005
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2803
|
-
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
3006
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
2804
3007
|
yes_no_choice "\nSet '${editor}' globally" "true"
|
|
2805
3008
|
editor=$(set_config_value core.editor "$choice" "true")
|
|
2806
3009
|
}
|
|
@@ -2833,12 +3036,15 @@ function set_ticket {
|
|
|
2833
3036
|
echo -e "${GREEN}✓ Set '${ticket_name}' as the ticket prefix in '${project_name}'${ENDCOLOR}"
|
|
2834
3037
|
echo
|
|
2835
3038
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2836
|
-
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
3039
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
2837
3040
|
yes_no_choice "\nSet '${ticket_name}' globally" "true"
|
|
2838
3041
|
ticket_name=$(set_config_value gitbasher.ticket "$ticket_name" "true")
|
|
2839
3042
|
}
|
|
2840
3043
|
function configure_ai_key {
|
|
2841
|
-
|
|
3044
|
+
local _chained=""
|
|
3045
|
+
[ "$1" = "--chained" ] && _chained="true"
|
|
3046
|
+
if [ -z "$_chained" ] \
|
|
3047
|
+
&& [ -z "$(git config --get gitbasher.ai-provider 2>/dev/null)" ] \
|
|
2842
3048
|
&& [ -z "$(git config --global --get gitbasher.ai-provider 2>/dev/null)" ]; then
|
|
2843
3049
|
configure_ai_provider
|
|
2844
3050
|
return
|
|
@@ -2848,9 +3054,10 @@ function configure_ai_key {
|
|
|
2848
3054
|
provider_upper=$(echo "$provider" | tr '[:lower:]' '[:upper:]')
|
|
2849
3055
|
echo -e "${YELLOW}Enter AI API key for provider '${provider}'${ENDCOLOR}"
|
|
2850
3056
|
echo
|
|
2851
|
-
if
|
|
3057
|
+
if ! ai_provider_requires_api_key; then
|
|
2852
3058
|
echo -e "${YELLOW}Provider '${provider}' does not require an API key.${ENDCOLOR}"
|
|
2853
3059
|
echo -e "Switch providers with ${GREEN}gitb cfg provider${ENDCOLOR} to set one."
|
|
3060
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
2854
3061
|
exit
|
|
2855
3062
|
fi
|
|
2856
3063
|
ai_api_key=$(get_ai_api_key)
|
|
@@ -2858,6 +3065,24 @@ function configure_ai_key {
|
|
|
2858
3065
|
echo -e "${YELLOW}No AI API key is set for '${provider}'.${ENDCOLOR}"
|
|
2859
3066
|
else
|
|
2860
3067
|
echo -e "AI API key for '${provider}' is ${GREEN}configured${ENDCOLOR}: ${BLUE}$(mask_api_key "$ai_api_key")${ENDCOLOR}"
|
|
3068
|
+
echo
|
|
3069
|
+
echo -e "Press ${YELLOW}Enter${ENDCOLOR} to keep it, ${YELLOW}r${ENDCOLOR} to replace, or ${YELLOW}0${ENDCOLOR} to remove"
|
|
3070
|
+
local _key_action=""
|
|
3071
|
+
read_key _key_action || { if [ -n "$_chained" ]; then return 130; fi; exit; }
|
|
3072
|
+
if [ "$_key_action" = "0" ]; then
|
|
3073
|
+
unset_ai_api_key
|
|
3074
|
+
echo
|
|
3075
|
+
echo -e "${GREEN}✓ Removed AI API key for '${provider}' from '${project_name}'${ENDCOLOR}"
|
|
3076
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
3077
|
+
exit
|
|
3078
|
+
fi
|
|
3079
|
+
normalize_key "$_key_action"
|
|
3080
|
+
if [ "$normalized_key" != "r" ]; then
|
|
3081
|
+
echo -e "Keeping the existing key."
|
|
3082
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
3083
|
+
exit
|
|
3084
|
+
fi
|
|
3085
|
+
echo
|
|
2861
3086
|
fi
|
|
2862
3087
|
case "$provider" in
|
|
2863
3088
|
openai)
|
|
@@ -2882,7 +3107,8 @@ function configure_ai_key {
|
|
|
2882
3107
|
echo -e " ${GREEN}env var${ENDCOLOR} — recommended; the key never touches disk via gitbasher"
|
|
2883
3108
|
echo -e " ${BLUE}git config${ENDCOLOR} — convenient, but stored in plaintext under .git/config (or ~/.gitconfig if global)"
|
|
2884
3109
|
echo
|
|
2885
|
-
read -n 1 -p "Use environment variable (recommended)? (
|
|
3110
|
+
read -n 1 -p "Use environment variable (recommended)? (Y/n) " ai_storage_choice \
|
|
3111
|
+
|| { if [ -n "$_chained" ]; then echo; return 130; fi; ai_storage_choice="n"; }
|
|
2886
3112
|
echo
|
|
2887
3113
|
if is_yes "$ai_storage_choice"; then
|
|
2888
3114
|
echo
|
|
@@ -2892,6 +3118,7 @@ function configure_ai_key {
|
|
|
2892
3118
|
echo -e "${CYAN}gitbasher checks env vars before git config every run, so this takes precedence.${ENDCOLOR}"
|
|
2893
3119
|
echo -e "Append it now with:"
|
|
2894
3120
|
echo -e " ${GREEN}echo \"export GITB_AI_API_KEY_${provider_upper}='your-${provider}-key'\" >> $(printf '%q' "$rc_file")${ENDCOLOR}"
|
|
3121
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
2895
3122
|
exit
|
|
2896
3123
|
fi
|
|
2897
3124
|
echo
|
|
@@ -2901,20 +3128,24 @@ function configure_ai_key {
|
|
|
2901
3128
|
echo -e "${YELLOW}Input is hidden — type the key and press Enter.${ENDCOLOR}"
|
|
2902
3129
|
read_silent_input ai_key_input "API Key: "
|
|
2903
3130
|
if [ "$ai_key_input" == "" ]; then
|
|
3131
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
2904
3132
|
exit
|
|
2905
3133
|
fi
|
|
2906
3134
|
if [ "$ai_key_input" == "0" ]; then
|
|
2907
3135
|
unset_ai_api_key
|
|
2908
3136
|
echo
|
|
2909
3137
|
echo -e "${GREEN}✓ Removed AI API key for '${provider}' from '${project_name}'${ENDCOLOR}"
|
|
3138
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
2910
3139
|
exit
|
|
2911
3140
|
fi
|
|
2912
3141
|
echo
|
|
2913
3142
|
if [[ ! "$ai_key_input" =~ ^[a-zA-Z0-9._-]{20,}$ ]]; then
|
|
2914
3143
|
echo -e "${YELLOW}⚠ API key format does not look like a valid ${provider} key.${ENDCOLOR}" >&2
|
|
2915
|
-
read -n 1 -p "Continue anyway? (
|
|
3144
|
+
read -n 1 -p "Continue anyway? (Y/n) " -s choice \
|
|
3145
|
+
|| { if [ -n "$_chained" ]; then echo; return 130; fi; choice="n"; }
|
|
2916
3146
|
echo
|
|
2917
3147
|
if ! is_yes "$choice"; then
|
|
3148
|
+
if [ -n "$_chained" ]; then return 1; fi
|
|
2918
3149
|
exit 1
|
|
2919
3150
|
fi
|
|
2920
3151
|
fi
|
|
@@ -2923,14 +3154,24 @@ function configure_ai_key {
|
|
|
2923
3154
|
echo
|
|
2924
3155
|
ai_smoke_check || true
|
|
2925
3156
|
echo
|
|
2926
|
-
[ "$GITBASHER_NO_REPO" = "true" ]
|
|
2927
|
-
|
|
3157
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
3158
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
3159
|
+
exit
|
|
3160
|
+
fi
|
|
3161
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
2928
3162
|
echo -e "${RED}⚠ Global API keys are stored in plaintext in ~/.gitconfig.${ENDCOLOR}"
|
|
2929
3163
|
echo -e "${CYAN}💡 For better security, set ${BLUE}GITB_AI_API_KEY_${provider_upper}${CYAN} as an environment variable instead.${ENDCOLOR}"
|
|
2930
|
-
|
|
2931
|
-
|
|
3164
|
+
local _key_global=""
|
|
3165
|
+
read_key _key_global || { if [ -n "$_chained" ]; then return 130; fi; _key_global="n"; }
|
|
3166
|
+
if is_yes "$_key_global"; then
|
|
3167
|
+
echo -e "${YELLOW}Set AI API key globally${ENDCOLOR}"
|
|
3168
|
+
ai_api_key=$(set_ai_api_key "$ai_key_input" "true")
|
|
3169
|
+
fi
|
|
3170
|
+
return 0
|
|
2932
3171
|
}
|
|
2933
3172
|
function configure_ai_provider {
|
|
3173
|
+
local _chained=""
|
|
3174
|
+
[ "$1" = "--chained" ] && _chained="true"
|
|
2934
3175
|
echo -e "${YELLOW}Choose AI Provider${ENDCOLOR}"
|
|
2935
3176
|
echo
|
|
2936
3177
|
local current_provider=$(get_ai_provider)
|
|
@@ -2941,13 +3182,18 @@ function configure_ai_provider {
|
|
|
2941
3182
|
fi
|
|
2942
3183
|
echo
|
|
2943
3184
|
echo -e "Options:"
|
|
2944
|
-
echo -e " 1. ${
|
|
2945
|
-
echo -e " 2. ${
|
|
2946
|
-
echo -e " 3. ${
|
|
3185
|
+
echo -e " 1. ${BOLD}openrouter${ENDCOLOR} Aggregator with hundreds of models — needs an API key"
|
|
3186
|
+
echo -e " 2. ${BOLD}openai${ENDCOLOR} OpenAI direct (GPT-5 family) — needs an API key"
|
|
3187
|
+
echo -e " 3. ${BOLD}ollama${ENDCOLOR} Local or remote models (you'll set the host) — no API key required"
|
|
3188
|
+
echo -e " 4. ${BOLD}claude${ENDCOLOR} Local Claude Code CLI (claude -p) — uses your Claude account, no API key"
|
|
2947
3189
|
echo
|
|
2948
3190
|
echo -e "Press Enter to keep the current provider, or enter 0 to reset to default (${AI_DEFAULT_PROVIDER})"
|
|
2949
|
-
read_editable_input choice "Choice (1-
|
|
3191
|
+
if ! read_editable_input choice "Choice (1-4): "; then
|
|
3192
|
+
if [ -n "$_chained" ]; then return 130; fi
|
|
3193
|
+
exit
|
|
3194
|
+
fi
|
|
2950
3195
|
if [ "$choice" == "" ]; then
|
|
3196
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
2951
3197
|
exit
|
|
2952
3198
|
fi
|
|
2953
3199
|
if [ "$choice" == "0" ]; then
|
|
@@ -2955,6 +3201,7 @@ function configure_ai_provider {
|
|
|
2955
3201
|
git config --global --unset gitbasher.ai-provider 2>/dev/null
|
|
2956
3202
|
echo
|
|
2957
3203
|
echo -e "${GREEN}✓ Reset provider to default (${AI_DEFAULT_PROVIDER})${ENDCOLOR}"
|
|
3204
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
2958
3205
|
exit
|
|
2959
3206
|
fi
|
|
2960
3207
|
local new_provider
|
|
@@ -2963,14 +3210,20 @@ function configure_ai_provider {
|
|
|
2963
3210
|
1) new_provider="openrouter" ;;
|
|
2964
3211
|
2) new_provider="openai" ;;
|
|
2965
3212
|
3) new_provider="ollama" ;;
|
|
3213
|
+
4) new_provider="claude" ;;
|
|
2966
3214
|
*)
|
|
2967
3215
|
echo -e "${RED}✗ Invalid choice.${ENDCOLOR}" >&2
|
|
3216
|
+
if [ -n "$_chained" ]; then return 1; fi
|
|
2968
3217
|
exit 1
|
|
2969
3218
|
;;
|
|
2970
3219
|
esac
|
|
2971
3220
|
migrate_legacy_ai_api_key_to "$current_provider"
|
|
3221
|
+
migrate_legacy_ai_model_to "$current_provider"
|
|
3222
|
+
local prev_local_provider
|
|
3223
|
+
prev_local_provider=$(git config --local --get gitbasher.ai-provider 2>/dev/null)
|
|
2972
3224
|
set_ai_provider "$new_provider"
|
|
2973
3225
|
echo -e "${GREEN}✓ Set AI provider to '${new_provider}' for '${project_name}'${ENDCOLOR}"
|
|
3226
|
+
local local_smoke_failed=""
|
|
2974
3227
|
case "$new_provider" in
|
|
2975
3228
|
ollama)
|
|
2976
3229
|
local current_host
|
|
@@ -2987,6 +3240,7 @@ function configure_ai_provider {
|
|
|
2987
3240
|
host_input="${host_input%/}"
|
|
2988
3241
|
if ! validate_proxy_url "$host_input"; then
|
|
2989
3242
|
echo -e "${RED}✗ '$host_input' does not look like a valid host URL (expected http://host[:port]).${ENDCOLOR}"
|
|
3243
|
+
if [ -n "$_chained" ]; then return 1; fi
|
|
2990
3244
|
exit 1
|
|
2991
3245
|
fi
|
|
2992
3246
|
set_ai_ollama_host "$host_input" >/dev/null
|
|
@@ -2994,9 +3248,24 @@ function configure_ai_provider {
|
|
|
2994
3248
|
fi
|
|
2995
3249
|
echo
|
|
2996
3250
|
echo -e "${CYAN}💡 Make sure the Ollama daemon is running (${BLUE}ollama serve${CYAN}) and a model is pulled (${BLUE}ollama pull qwen3:8b${CYAN}).${ENDCOLOR}"
|
|
2997
|
-
|
|
3251
|
+
if [ -z "$_chained" ]; then
|
|
3252
|
+
echo -e "${CYAN}💡 Pick a model with ${GREEN}gitb cfg model${CYAN}.${ENDCOLOR}"
|
|
3253
|
+
fi
|
|
3254
|
+
echo
|
|
3255
|
+
ai_smoke_check || local_smoke_failed="true"
|
|
3256
|
+
;;
|
|
3257
|
+
claude)
|
|
2998
3258
|
echo
|
|
2999
|
-
|
|
3259
|
+
if command -v claude >/dev/null 2>&1; then
|
|
3260
|
+
ai_smoke_check || local_smoke_failed="true"
|
|
3261
|
+
else
|
|
3262
|
+
echo -e "${RED}✗ claude CLI not found on PATH.${ENDCOLOR}"
|
|
3263
|
+
echo -e "${CYAN}💡 Install it with ${BLUE}npm install -g @anthropic-ai/claude-code${CYAN}, then sign in once.${ENDCOLOR}"
|
|
3264
|
+
local_smoke_failed="true"
|
|
3265
|
+
fi
|
|
3266
|
+
if [ -z "$_chained" ]; then
|
|
3267
|
+
echo -e "${CYAN}💡 Pick a model with ${GREEN}gitb cfg model${CYAN} (haiku, sonnet, opus).${ENDCOLOR}"
|
|
3268
|
+
fi
|
|
3000
3269
|
;;
|
|
3001
3270
|
*)
|
|
3002
3271
|
if [ -n "$(get_ai_api_key)" ]; then
|
|
@@ -3006,10 +3275,29 @@ function configure_ai_provider {
|
|
|
3006
3275
|
;;
|
|
3007
3276
|
esac
|
|
3008
3277
|
echo
|
|
3278
|
+
if [ -n "$local_smoke_failed" ]; then
|
|
3279
|
+
echo -e "Keep '${new_provider}' as the AI provider anyway (y/N)?"
|
|
3280
|
+
local _keep_rc=0
|
|
3281
|
+
confirm_destructive || _keep_rc=$?
|
|
3282
|
+
if [ "$_keep_rc" -ne 0 ]; then
|
|
3283
|
+
if [ -n "$prev_local_provider" ]; then
|
|
3284
|
+
git config --local gitbasher.ai-provider "$prev_local_provider"
|
|
3285
|
+
else
|
|
3286
|
+
git config --local --unset gitbasher.ai-provider 2>/dev/null || true
|
|
3287
|
+
fi
|
|
3288
|
+
echo -e "${YELLOW}Provider unchanged — still '$(get_ai_provider)'.${ENDCOLOR}"
|
|
3289
|
+
if [ -n "$_chained" ]; then
|
|
3290
|
+
if [ "$_keep_rc" -eq 2 ]; then return 130; fi
|
|
3291
|
+
return 3
|
|
3292
|
+
fi
|
|
3293
|
+
exit 1
|
|
3294
|
+
fi
|
|
3295
|
+
echo
|
|
3296
|
+
fi
|
|
3009
3297
|
if [ "$GITBASHER_NO_REPO" != "true" ]; then
|
|
3010
|
-
echo -e "Do you want to set this provider ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
3298
|
+
echo -e "Do you want to set this provider ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
3011
3299
|
local _prov_key=""
|
|
3012
|
-
read_key _prov_key || _prov_key="n"
|
|
3300
|
+
read_key _prov_key || { if [ -n "$_chained" ]; then return 130; fi; _prov_key="n"; }
|
|
3013
3301
|
if is_yes "$_prov_key"; then
|
|
3014
3302
|
echo -e "${YELLOW}Set AI provider globally${ENDCOLOR}"
|
|
3015
3303
|
set_config_value gitbasher.ai-provider "$new_provider" "true"
|
|
@@ -3018,12 +3306,12 @@ function configure_ai_provider {
|
|
|
3018
3306
|
fi
|
|
3019
3307
|
fi
|
|
3020
3308
|
fi
|
|
3021
|
-
if ai_provider_requires_api_key && [ -z "$(get_ai_api_key)" ]; then
|
|
3309
|
+
if [ -z "$_chained" ] && ai_provider_requires_api_key && [ -z "$(get_ai_api_key)" ]; then
|
|
3022
3310
|
echo
|
|
3023
3311
|
echo -e "${YELLOW}No API key is set for '${new_provider}'.${ENDCOLOR}"
|
|
3024
3312
|
echo -e "${CYAN}AI commands will be blocked until a key is configured.${ENDCOLOR}"
|
|
3025
3313
|
echo
|
|
3026
|
-
echo -e "Set one now? (
|
|
3314
|
+
echo -e "Set one now? (Y/n)"
|
|
3027
3315
|
if read -n 1 -s key_choice && is_yes "$key_choice"; then
|
|
3028
3316
|
echo
|
|
3029
3317
|
configure_ai_key
|
|
@@ -3033,105 +3321,247 @@ function configure_ai_provider {
|
|
|
3033
3321
|
fi
|
|
3034
3322
|
fi
|
|
3035
3323
|
}
|
|
3324
|
+
function _print_model_menu_entry {
|
|
3325
|
+
local _desc=""
|
|
3326
|
+
[ -n "$4" ] && _desc=" ${GRAY}- ${4}${ENDCOLOR}"
|
|
3327
|
+
if [ "$1" = "$3" ]; then
|
|
3328
|
+
echo -e " ${GREEN}${2}. ${1}${ENDCOLOR} (current)${_desc}"
|
|
3329
|
+
else
|
|
3330
|
+
echo -e " ${2}. ${BOLD}${1}${ENDCOLOR}${_desc}"
|
|
3331
|
+
fi
|
|
3332
|
+
}
|
|
3036
3333
|
function configure_ai_model {
|
|
3334
|
+
local _chained=""
|
|
3335
|
+
[ "$1" = "--chained" ] && _chained="true"
|
|
3037
3336
|
echo -e "${YELLOW}Configure AI Model${ENDCOLOR}"
|
|
3038
3337
|
echo
|
|
3039
3338
|
current_model=$(get_ai_model)
|
|
3040
3339
|
if [ -n "$current_model" ]; then
|
|
3041
|
-
echo -e "Current
|
|
3340
|
+
echo -e "Current model for provider '${GREEN}$(get_ai_provider)${ENDCOLOR}': ${GREEN}$current_model${ENDCOLOR}"
|
|
3042
3341
|
else
|
|
3043
|
-
echo -e "
|
|
3044
|
-
echo -e " • simple → ${GREEN}$(get_ai_model_for simple)${ENDCOLOR}"
|
|
3045
|
-
echo -e " • subject → ${GREEN}$(get_ai_model_for subject)${ENDCOLOR}"
|
|
3046
|
-
echo -e " • full → ${GREEN}$(get_ai_model_for full)${ENDCOLOR}"
|
|
3047
|
-
echo -e " • grouping → ${GREEN}$(get_ai_model_for grouping)${ENDCOLOR}"
|
|
3342
|
+
echo -e "Provider '${GREEN}$(get_ai_provider)${ENDCOLOR}' uses its default model: ${GREEN}$(get_ai_default_model)${ENDCOLOR}"
|
|
3048
3343
|
fi
|
|
3049
3344
|
echo
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3345
|
+
local -a model_menu=()
|
|
3346
|
+
local model_catalog=""
|
|
3347
|
+
local _m _i
|
|
3348
|
+
local _resolved_model
|
|
3349
|
+
_resolved_model=$(resolve_ai_model)
|
|
3055
3350
|
case "$(get_ai_provider)" in
|
|
3056
|
-
openai)
|
|
3057
|
-
echo -e " • ${BLUE}gpt-5.4-nano${ENDCOLOR} - Cheapest+fastest, ideal for one-line subjects (~\$0.20/\$1.25 per M)"
|
|
3058
|
-
echo -e " • ${BLUE}gpt-5.4-mini${ENDCOLOR} - Balanced for full-body prose and strict TSV output (~\$0.75/\$4.50 per M)"
|
|
3059
|
-
echo -e " • ${BLUE}gpt-5.4${ENDCOLOR} - Flagship, only worth it for the hardest grouping cases"
|
|
3060
|
-
;;
|
|
3061
3351
|
ollama)
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3352
|
+
while IFS= read -r _m; do
|
|
3353
|
+
[ -n "$_m" ] && model_menu+=("$_m")
|
|
3354
|
+
done < <(ollama_list_models)
|
|
3355
|
+
if [ ${#model_menu[@]} -gt 0 ]; then
|
|
3356
|
+
echo -e "Installed models on ${BLUE}$(ollama_api_base)${ENDCOLOR}:"
|
|
3357
|
+
_i=1
|
|
3358
|
+
for _m in "${model_menu[@]}"; do
|
|
3359
|
+
_print_model_menu_entry "$_m" "$_i" "$_resolved_model"
|
|
3360
|
+
_i=$((_i + 1))
|
|
3361
|
+
done
|
|
3362
|
+
else
|
|
3363
|
+
echo -e "${YELLOW}No models found on $(ollama_api_base) — is the daemon running?${ENDCOLOR}"
|
|
3364
|
+
fi
|
|
3066
3365
|
;;
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3366
|
+
openrouter)
|
|
3367
|
+
model_catalog=$(openrouter_list_models top-weekly 2>/dev/null)
|
|
3368
|
+
if [ -n "$model_catalog" ]; then
|
|
3369
|
+
_i=1
|
|
3370
|
+
echo -e "Most used on OpenRouter this week:"
|
|
3371
|
+
while IFS= read -r _m; do
|
|
3372
|
+
model_menu+=("$_m")
|
|
3373
|
+
_print_model_menu_entry "$_m" "$_i" "$_resolved_model"
|
|
3374
|
+
_i=$((_i + 1))
|
|
3375
|
+
done < <(printf '%s\n' "$model_catalog" | head -8)
|
|
3376
|
+
local _newest
|
|
3377
|
+
_newest=$(openrouter_list_models newest 2>/dev/null | head -5)
|
|
3378
|
+
if [ -n "$_newest" ]; then
|
|
3379
|
+
echo -e "Newest:"
|
|
3380
|
+
while IFS= read -r _m; do
|
|
3381
|
+
[ -z "$_m" ] && continue
|
|
3382
|
+
case " ${model_menu[*]} " in *" $_m "*) continue ;; esac
|
|
3383
|
+
model_menu+=("$_m")
|
|
3384
|
+
_print_model_menu_entry "$_m" "$_i" "$_resolved_model"
|
|
3385
|
+
_i=$((_i + 1))
|
|
3386
|
+
done <<< "$_newest"
|
|
3387
|
+
fi
|
|
3388
|
+
fi
|
|
3389
|
+
;;
|
|
3390
|
+
openai)
|
|
3391
|
+
model_catalog=$(openai_list_models 2>/dev/null)
|
|
3392
|
+
if [ -n "$model_catalog" ]; then
|
|
3393
|
+
echo -e "Newest chat models on your OpenAI account:"
|
|
3394
|
+
_i=1
|
|
3395
|
+
while IFS= read -r _m; do
|
|
3396
|
+
model_menu+=("$_m")
|
|
3397
|
+
_print_model_menu_entry "$_m" "$_i" "$_resolved_model"
|
|
3398
|
+
_i=$((_i + 1))
|
|
3399
|
+
done < <(printf '%s\n' "$model_catalog" \
|
|
3400
|
+
| LC_ALL=C grep -vE -- '-[0-9]{4}-[0-9]{2}-[0-9]{2}$' | head -12)
|
|
3401
|
+
fi
|
|
3073
3402
|
;;
|
|
3074
3403
|
esac
|
|
3404
|
+
if [ ${#model_menu[@]} -eq 0 ]; then
|
|
3405
|
+
local -a _menu_desc=()
|
|
3406
|
+
case "$(get_ai_provider)" in
|
|
3407
|
+
openai)
|
|
3408
|
+
echo -e "Popular OpenAI models:"
|
|
3409
|
+
model_menu=("gpt-5.4-mini" "gpt-5.4-nano" "gpt-5.5")
|
|
3410
|
+
_menu_desc=("Default: as fast as nano with better quality (~\$0.75/\$4.50 per M)"
|
|
3411
|
+
"Budget option for high volume (~\$0.20/\$1.25 per M)"
|
|
3412
|
+
"Flagship, only worth it for the hardest grouping cases")
|
|
3413
|
+
;;
|
|
3414
|
+
ollama)
|
|
3415
|
+
echo -e "Suggested models (pull first: ${BLUE}ollama pull <id>${ENDCOLOR}):"
|
|
3416
|
+
model_menu=("qwen3:8b" "llama3.3:8b" "qwen2.5-coder:7b")
|
|
3417
|
+
_menu_desc=("Default: best small instruction-follower; most stable structured output"
|
|
3418
|
+
"Solid general-purpose alternative"
|
|
3419
|
+
"Code-focused; good when most diffs are code")
|
|
3420
|
+
;;
|
|
3421
|
+
claude)
|
|
3422
|
+
echo -e "Model aliases (resolve to the current generation; full ids like ${BLUE}claude-haiku-4-5${ENDCOLOR} also work):"
|
|
3423
|
+
model_menu=("haiku" "sonnet" "opus")
|
|
3424
|
+
_menu_desc=("Default: fastest and cheapest"
|
|
3425
|
+
"Better prose, noticeably slower"
|
|
3426
|
+
"Strongest, for the hardest cases")
|
|
3427
|
+
;;
|
|
3428
|
+
*)
|
|
3429
|
+
echo -e "Popular OpenRouter models:"
|
|
3430
|
+
model_menu=("google/gemini-3.5-flash" "google/gemini-3.1-flash-lite" "openrouter/auto" "anthropic/claude-haiku-4.5" "anthropic/claude-sonnet-5")
|
|
3431
|
+
_menu_desc=("Default: current flash generation, fast with good prose"
|
|
3432
|
+
"Fastest, cheapest (~\$0.25/\$1.50 per M)"
|
|
3433
|
+
"Auto-select best available model"
|
|
3434
|
+
"Strict instruction following"
|
|
3435
|
+
"Premium quality for the hardest cases")
|
|
3436
|
+
;;
|
|
3437
|
+
esac
|
|
3438
|
+
_i=0
|
|
3439
|
+
for _m in "${model_menu[@]}"; do
|
|
3440
|
+
_print_model_menu_entry "$_m" "$((_i + 1))" "$_resolved_model" "${_menu_desc[$_i]}"
|
|
3441
|
+
_i=$((_i + 1))
|
|
3442
|
+
done
|
|
3443
|
+
fi
|
|
3444
|
+
echo -e "Enter a number to select, or type a model id."
|
|
3075
3445
|
echo
|
|
3076
|
-
|
|
3077
|
-
if
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
[ -n "$_m" ] && ollama_model_list+=("$_m")
|
|
3081
|
-
done < <(ollama_list_models)
|
|
3082
|
-
if [ ${#ollama_model_list[@]} -gt 0 ]; then
|
|
3083
|
-
echo -e "Installed models on ${BLUE}$(ollama_api_base)${ENDCOLOR}:"
|
|
3084
|
-
_i=1
|
|
3085
|
-
for _m in "${ollama_model_list[@]}"; do
|
|
3086
|
-
if [ "$_m" = "$current_model" ]; then
|
|
3087
|
-
echo -e " ${GREEN}${_i}. ${_m}${ENDCOLOR} (current)"
|
|
3088
|
-
else
|
|
3089
|
-
echo -e " ${BLUE}${_i}.${ENDCOLOR} ${_m}"
|
|
3090
|
-
fi
|
|
3091
|
-
_i=$((_i + 1))
|
|
3092
|
-
done
|
|
3093
|
-
echo -e "Enter a number to select, or type a model id."
|
|
3094
|
-
else
|
|
3095
|
-
echo -e "${YELLOW}No models found on $(ollama_api_base) — is the daemon running?${ENDCOLOR}"
|
|
3096
|
-
echo -e "Pull one with ${BLUE}ollama pull qwen3:8b${ENDCOLOR}, or type a model id below."
|
|
3097
|
-
fi
|
|
3098
|
-
echo
|
|
3446
|
+
echo -e "Press Enter to keep the current model, or 0 to reset to the default (${GREEN}$(get_ai_default_model)${ENDCOLOR})"
|
|
3447
|
+
if ! read_editable_input model_input "Model ID: "; then
|
|
3448
|
+
if [ -n "$_chained" ]; then return 130; fi
|
|
3449
|
+
exit
|
|
3099
3450
|
fi
|
|
3100
|
-
echo -e "Press Enter to exit without changes, or 0 to clear the override (use per-task defaults)"
|
|
3101
|
-
read_editable_input model_input "Model ID: "
|
|
3102
3451
|
if [ "$model_input" == "" ]; then
|
|
3452
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
3103
3453
|
exit
|
|
3104
3454
|
fi
|
|
3105
3455
|
if [ "$model_input" == "0" ]; then
|
|
3456
|
+
git config --unset "gitbasher.ai-model-$(get_ai_provider)" 2>/dev/null
|
|
3457
|
+
git config --global --unset "gitbasher.ai-model-$(get_ai_provider)" 2>/dev/null
|
|
3106
3458
|
git config --unset gitbasher.ai-model 2>/dev/null
|
|
3107
3459
|
git config --global --unset gitbasher.ai-model 2>/dev/null
|
|
3108
3460
|
echo
|
|
3109
|
-
echo -e "${GREEN}✓ Cleared
|
|
3461
|
+
echo -e "${GREEN}✓ Cleared model override — provider '$(get_ai_provider)' now uses its default ($(get_ai_default_model))${ENDCOLOR}"
|
|
3462
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
3110
3463
|
exit
|
|
3111
3464
|
fi
|
|
3112
3465
|
echo
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3466
|
+
local picked_from_menu=""
|
|
3467
|
+
if [ ${#model_menu[@]} -gt 0 ] && [[ "$model_input" =~ ^[0-9]+$ ]]; then
|
|
3468
|
+
if [ "$model_input" -ge 1 ] && [ "$model_input" -le ${#model_menu[@]} ]; then
|
|
3469
|
+
model_input="${model_menu[$((model_input - 1))]}"
|
|
3470
|
+
picked_from_menu="true"
|
|
3116
3471
|
else
|
|
3117
3472
|
echo -e "${RED}✗ Invalid selection: ${model_input}${ENDCOLOR}" >&2
|
|
3473
|
+
if [ -n "$_chained" ]; then return 1; fi
|
|
3118
3474
|
exit 1
|
|
3119
3475
|
fi
|
|
3120
3476
|
fi
|
|
3121
3477
|
if ! is_valid_model_id "$model_input"; then
|
|
3122
3478
|
echo -e "${RED}✗ Invalid model ID format.${ENDCOLOR}" >&2
|
|
3123
3479
|
echo -e "${YELLOW}Use only letters, numbers, dots, dashes, underscores, colons, and slashes.${ENDCOLOR}" >&2
|
|
3480
|
+
if [ -n "$_chained" ]; then return 1; fi
|
|
3124
3481
|
exit 1
|
|
3125
3482
|
fi
|
|
3483
|
+
if [ -z "$picked_from_menu" ] && [ -n "$model_catalog" ]; then
|
|
3484
|
+
if ! printf '%s\n' "$model_catalog" | LC_ALL=C grep -qxF "$model_input"; then
|
|
3485
|
+
echo -e "${YELLOW}⚠ '$model_input' is not in $(get_ai_provider)'s current model list.${ENDCOLOR}"
|
|
3486
|
+
echo -e "Save it anyway (y/N)?"
|
|
3487
|
+
if ! confirm_destructive; then
|
|
3488
|
+
echo -e "${YELLOW}Not saved. Pick a number from the list or re-check the id.${ENDCOLOR}"
|
|
3489
|
+
if [ -n "$_chained" ]; then return 1; fi
|
|
3490
|
+
exit 1
|
|
3491
|
+
fi
|
|
3492
|
+
fi
|
|
3493
|
+
fi
|
|
3494
|
+
local prev_model
|
|
3495
|
+
prev_model=$(git config --local --get "gitbasher.ai-model-$(get_ai_provider)" 2>/dev/null)
|
|
3126
3496
|
set_ai_model "$model_input"
|
|
3127
|
-
echo -e "${GREEN}✓ Set AI model to '$model_input' for '${project_name}'${ENDCOLOR}"
|
|
3497
|
+
echo -e "${GREEN}✓ Set AI model to '$model_input' for provider '$(get_ai_provider)' in '${project_name}'${ENDCOLOR}"
|
|
3128
3498
|
echo
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3499
|
+
case "$(get_ai_provider)" in
|
|
3500
|
+
openrouter|openai)
|
|
3501
|
+
if ! ai_model_smoke_check; then
|
|
3502
|
+
echo -e "Keep '$model_input' anyway (y/N)?"
|
|
3503
|
+
if ! confirm_destructive; then
|
|
3504
|
+
if [ -n "$prev_model" ]; then
|
|
3505
|
+
git config "gitbasher.ai-model-$(get_ai_provider)" "$prev_model"
|
|
3506
|
+
echo -e "${YELLOW}Restored previous model '${prev_model}'.${ENDCOLOR}"
|
|
3507
|
+
else
|
|
3508
|
+
git config --unset "gitbasher.ai-model-$(get_ai_provider)" 2>/dev/null
|
|
3509
|
+
echo -e "${YELLOW}Removed it — provider '$(get_ai_provider)' uses its default ($(get_ai_default_model)).${ENDCOLOR}"
|
|
3510
|
+
fi
|
|
3511
|
+
if [ -n "$_chained" ]; then return 1; fi
|
|
3512
|
+
exit 1
|
|
3513
|
+
fi
|
|
3514
|
+
fi
|
|
3515
|
+
echo
|
|
3516
|
+
;;
|
|
3517
|
+
esac
|
|
3518
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
3519
|
+
if [ -n "$_chained" ]; then return 0; fi
|
|
3520
|
+
exit
|
|
3521
|
+
fi
|
|
3522
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
3523
|
+
local _model_global=""
|
|
3524
|
+
read_key _model_global || { if [ -n "$_chained" ]; then return 130; fi; _model_global="n"; }
|
|
3525
|
+
if is_yes "$_model_global"; then
|
|
3526
|
+
echo -e "${YELLOW}Set AI model globally${ENDCOLOR}"
|
|
3527
|
+
set_ai_model "$model_input" "true"
|
|
3528
|
+
fi
|
|
3529
|
+
return 0
|
|
3133
3530
|
}
|
|
3134
|
-
function
|
|
3531
|
+
function configure_ai_wizard {
|
|
3532
|
+
local _rc
|
|
3533
|
+
echo -e "${YELLOW}Step 1/3 — Provider${ENDCOLOR}"
|
|
3534
|
+
echo
|
|
3535
|
+
configure_ai_provider --chained
|
|
3536
|
+
_rc=$?
|
|
3537
|
+
if [ "$_rc" -eq 130 ]; then prompt_aborted; fi
|
|
3538
|
+
if [ "$_rc" -eq 3 ]; then
|
|
3539
|
+
echo
|
|
3540
|
+
echo -e "${YELLOW}AI setup stopped — nothing changed.${ENDCOLOR}"
|
|
3541
|
+
return 0
|
|
3542
|
+
fi
|
|
3543
|
+
echo
|
|
3544
|
+
echo -e "${YELLOW}Step 2/3 — API key${ENDCOLOR}"
|
|
3545
|
+
echo
|
|
3546
|
+
if ai_provider_requires_api_key; then
|
|
3547
|
+
configure_ai_key --chained
|
|
3548
|
+
_rc=$?
|
|
3549
|
+
if [ "$_rc" -eq 130 ]; then prompt_aborted; fi
|
|
3550
|
+
else
|
|
3551
|
+
echo -e "Provider '$(get_ai_provider)' does not require an API key — skipping."
|
|
3552
|
+
fi
|
|
3553
|
+
echo
|
|
3554
|
+
echo -e "${YELLOW}Step 3/3 — Model${ENDCOLOR}"
|
|
3555
|
+
echo
|
|
3556
|
+
configure_ai_model --chained
|
|
3557
|
+
_rc=$?
|
|
3558
|
+
if [ "$_rc" -eq 130 ]; then prompt_aborted; fi
|
|
3559
|
+
echo
|
|
3560
|
+
echo -e "${GREEN}✓ AI setup complete${ENDCOLOR}"
|
|
3561
|
+
echo
|
|
3562
|
+
print_ai_configuration
|
|
3563
|
+
}
|
|
3564
|
+
function configure_ai_proxy {
|
|
3135
3565
|
echo -e "${YELLOW}Configure AI HTTP/SOCKS Proxy${ENDCOLOR}"
|
|
3136
3566
|
echo
|
|
3137
3567
|
ai_proxy=$(get_ai_proxy)
|
|
@@ -3143,9 +3573,9 @@ function configure_ai_proxy {
|
|
|
3143
3573
|
echo -e "Enter proxy URL to route AI requests through (useful for bypassing geo-restrictions)"
|
|
3144
3574
|
echo -e ""
|
|
3145
3575
|
echo -e "${BLUE}HTTP proxy formats:${ENDCOLOR}"
|
|
3146
|
-
echo -e " • ${
|
|
3147
|
-
echo -e " • ${
|
|
3148
|
-
echo -e " • ${
|
|
3576
|
+
echo -e " • ${BOLD}http://proxy.example.com:8080${ENDCOLOR}"
|
|
3577
|
+
echo -e " • ${BOLD}http://username:password@proxy.example.com:8080${ENDCOLOR}"
|
|
3578
|
+
echo -e " • ${BOLD}http://[2001:db8::1]:8080${ENDCOLOR} (IPv6)"
|
|
3149
3579
|
echo -e ""
|
|
3150
3580
|
echo -e "Press Enter to exit without changes, or 0 to remove the existing proxy"
|
|
3151
3581
|
read_editable_input ai_proxy_input "Proxy URL: "
|
|
@@ -3175,7 +3605,7 @@ function configure_ai_proxy {
|
|
|
3175
3605
|
echo -e " ${BLUE}gitb commit aif${ENDCOLOR} - Fast AI commit through proxy"
|
|
3176
3606
|
echo
|
|
3177
3607
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
3178
|
-
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
3608
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
3179
3609
|
yes_no_choice "\nSet AI proxy globally" "true"
|
|
3180
3610
|
set_ai_proxy "$ai_proxy_input"
|
|
3181
3611
|
git config --global gitbasher.ai-proxy "$ai_proxy_input"
|
|
@@ -3209,7 +3639,7 @@ function configure_ai_history {
|
|
|
3209
3639
|
if [ "$limit_input" -gt 20 ]; then
|
|
3210
3640
|
echo -e "${YELLOW}High values may exceed token limits and slow down AI responses.${ENDCOLOR}"
|
|
3211
3641
|
fi
|
|
3212
|
-
read -n 1 -p "Continue anyway? (
|
|
3642
|
+
read -n 1 -p "Continue anyway? (Y/n) " -s choice || choice="n"
|
|
3213
3643
|
echo
|
|
3214
3644
|
if ! is_yes "$choice"; then
|
|
3215
3645
|
exit
|
|
@@ -3219,7 +3649,7 @@ function configure_ai_history {
|
|
|
3219
3649
|
echo -e "${GREEN}✓ Set AI commit history limit to ${limit_input} for '${project_name}'${ENDCOLOR}"
|
|
3220
3650
|
echo
|
|
3221
3651
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
3222
|
-
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
3652
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
3223
3653
|
yes_no_choice "\nSet AI commit history limit globally" "true"
|
|
3224
3654
|
git config --global gitbasher.ai-commit-history-limit "$limit_input"
|
|
3225
3655
|
}
|
|
@@ -3257,7 +3687,7 @@ function configure_push_warn_size {
|
|
|
3257
3687
|
fi
|
|
3258
3688
|
echo
|
|
3259
3689
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
3260
|
-
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
3690
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
3261
3691
|
yes_no_choice "\nSet push size warning globally" "true"
|
|
3262
3692
|
git config --global gitbasher.push-warn-size "$limit_input"
|
|
3263
3693
|
}
|
|
@@ -3286,7 +3716,7 @@ function configure_ai_diff {
|
|
|
3286
3716
|
lines_input="$validated_number"
|
|
3287
3717
|
if [ "$lines_input" -lt 50 ] || [ "$lines_input" -gt 2000 ]; then
|
|
3288
3718
|
echo -e "${YELLOW}⚠ Value is outside the recommended range (50-2000).${ENDCOLOR}"
|
|
3289
|
-
read -n 1 -p "Continue anyway? (
|
|
3719
|
+
read -n 1 -p "Continue anyway? (Y/n) " -s choice || choice="n"
|
|
3290
3720
|
echo
|
|
3291
3721
|
if ! is_yes "$choice"; then
|
|
3292
3722
|
exit
|
|
@@ -3304,7 +3734,7 @@ function configure_ai_diff {
|
|
|
3304
3734
|
chars_input="$validated_number"
|
|
3305
3735
|
if [ "$chars_input" -lt 4000 ] || [ "$chars_input" -gt 100000 ]; then
|
|
3306
3736
|
echo -e "${YELLOW}⚠ Value is outside the recommended range (4000-100000).${ENDCOLOR}"
|
|
3307
|
-
read -n 1 -p "Continue anyway? (
|
|
3737
|
+
read -n 1 -p "Continue anyway? (Y/n) " -s choice || choice="n"
|
|
3308
3738
|
echo
|
|
3309
3739
|
if ! is_yes "$choice"; then
|
|
3310
3740
|
exit
|
|
@@ -3319,7 +3749,7 @@ function configure_ai_diff {
|
|
|
3319
3749
|
fi
|
|
3320
3750
|
echo
|
|
3321
3751
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
3322
|
-
echo -e "Do you want to apply ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
3752
|
+
echo -e "Do you want to apply ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
3323
3753
|
yes_no_choice "\nApply AI diff settings globally" "true"
|
|
3324
3754
|
if [ -n "$lines_input" ]; then
|
|
3325
3755
|
git config --global gitbasher.ai-diff-limit "$lines_input"
|
|
@@ -3367,7 +3797,7 @@ function set_scopes {
|
|
|
3367
3797
|
echo -e "${GREEN}✓ Set '${scopes}' as the scopes list in '${project_name}'${ENDCOLOR}"
|
|
3368
3798
|
echo
|
|
3369
3799
|
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
3370
|
-
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (
|
|
3800
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
3371
3801
|
yes_no_choice "\nSet '${scopes}' globally" "true"
|
|
3372
3802
|
git config --global --replace-all gitbasher.scopes "$scopes_raw"
|
|
3373
3803
|
}
|
|
@@ -3400,11 +3830,11 @@ function delete_global {
|
|
|
3400
3830
|
_delete_global_add "gitbasher.ai-ollama-host" "Ollama host" "$global_ollama_host" "$GREEN"
|
|
3401
3831
|
local global_base_url=$(git config --global --get gitbasher.ai-base-url)
|
|
3402
3832
|
_delete_global_add "gitbasher.ai-base-url" "AI base URL" "$global_base_url" "$GREEN"
|
|
3403
|
-
local
|
|
3404
|
-
|
|
3405
|
-
if [ -n "$
|
|
3406
|
-
labels+=("AI per
|
|
3407
|
-
actions+=("${
|
|
3833
|
+
local _provider_models
|
|
3834
|
+
_provider_models=$(git config --global --get-regexp '^gitbasher\.ai-model-' 2>/dev/null | awk '{print $1}' | tr '\n' '+' | sed 's/+$//')
|
|
3835
|
+
if [ -n "$_provider_models" ]; then
|
|
3836
|
+
labels+=("AI models (per provider): ${GREEN}configured${ENDCOLOR}")
|
|
3837
|
+
actions+=("${_provider_models}|AI models (per provider)")
|
|
3408
3838
|
fi
|
|
3409
3839
|
while IFS=$'\t' read -r key_name; do
|
|
3410
3840
|
[ -z "$key_name" ] && continue
|
|
@@ -3511,7 +3941,8 @@ function config_script {
|
|
|
3511
3941
|
editor|ed|e) set_editor_cfg="true";;
|
|
3512
3942
|
ticket|jira|ti|t) set_ticket_cfg="true";;
|
|
3513
3943
|
scopes|scope|sc|s) set_scopes_cfg="true";;
|
|
3514
|
-
ai|llm
|
|
3944
|
+
ai|llm) set_ai_cfg="true";;
|
|
3945
|
+
key) set_ai_key_cfg="true";;
|
|
3515
3946
|
provider|prov) set_ai_provider_cfg="true";;
|
|
3516
3947
|
model|m) set_ai_model_cfg="true";;
|
|
3517
3948
|
proxy|prx|p) set_proxy_cfg="true";;
|
|
@@ -3536,6 +3967,8 @@ function config_script {
|
|
|
3536
3967
|
elif [ -n "${set_scopes_cfg}" ]; then
|
|
3537
3968
|
header="$header SCOPES LIST"
|
|
3538
3969
|
elif [ -n "${set_ai_cfg}" ]; then
|
|
3970
|
+
header="$header AI SETUP"
|
|
3971
|
+
elif [ -n "${set_ai_key_cfg}" ]; then
|
|
3539
3972
|
header="$header AI API KEY"
|
|
3540
3973
|
elif [ -n "${set_ai_provider_cfg}" ]; then
|
|
3541
3974
|
header="$header AI PROVIDER"
|
|
@@ -3591,6 +4024,10 @@ function config_script {
|
|
|
3591
4024
|
exit
|
|
3592
4025
|
fi
|
|
3593
4026
|
if [ "$set_ai_cfg" == "true" ]; then
|
|
4027
|
+
configure_ai_wizard
|
|
4028
|
+
exit
|
|
4029
|
+
fi
|
|
4030
|
+
if [ "$set_ai_key_cfg" == "true" ]; then
|
|
3594
4031
|
configure_ai_key
|
|
3595
4032
|
exit
|
|
3596
4033
|
fi
|
|
@@ -3638,8 +4075,9 @@ function config_script {
|
|
|
3638
4075
|
print_help_row $PAD "editor" "ed, e" "Set the editor for commit messages"
|
|
3639
4076
|
print_help_row $PAD "ticket" "ti, t, jira" "Set the ticket prefix used in commits and branches"
|
|
3640
4077
|
print_help_row $PAD "scopes" "sc" "Set the list of suggested commit scopes"
|
|
3641
|
-
print_help_row $PAD "ai" "llm
|
|
3642
|
-
print_help_row $PAD "
|
|
4078
|
+
print_help_row $PAD "ai" "llm" "Set up AI: provider, API key, and model (wizard)"
|
|
4079
|
+
print_help_row $PAD "key" "" "Set the AI API key only"
|
|
4080
|
+
print_help_row $PAD "provider" "prov" "Choose the AI provider (openrouter, openai, ollama, claude)"
|
|
3643
4081
|
print_help_row $PAD "model" "m" "Set the AI model override"
|
|
3644
4082
|
print_help_row $PAD "proxy" "prx, p" "Set an HTTP/SOCKS proxy for AI requests"
|
|
3645
4083
|
print_help_row $PAD "history" "hist" "Set how many recent commits to include in AI prompts"
|
|
@@ -3653,7 +4091,7 @@ function config_script {
|
|
|
3653
4091
|
echo -e " ${GREEN}gitb cfg${ENDCOLOR} Show current configuration"
|
|
3654
4092
|
echo -e " ${GREEN}gitb cfg user${ENDCOLOR} Set user.name and user.email"
|
|
3655
4093
|
echo -e " ${GREEN}gitb cfg default${ENDCOLOR} Choose the default gitbasher branch"
|
|
3656
|
-
echo -e " ${GREEN}gitb cfg ai${ENDCOLOR}
|
|
4094
|
+
echo -e " ${GREEN}gitb cfg ai${ENDCOLOR} Interactive AI setup (provider, key, model)"
|
|
3657
4095
|
exit
|
|
3658
4096
|
fi
|
|
3659
4097
|
print_configuration
|
|
@@ -3718,7 +4156,7 @@ function merge_script {
|
|
|
3718
4156
|
elif [ "$current_local_commit" != "$current_remote_commit" ]; then
|
|
3719
4157
|
echo -e "${YELLOW}⚠ Remote changes detected on ${current_branch}.${ENDCOLOR}"
|
|
3720
4158
|
echo
|
|
3721
|
-
echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} first (
|
|
4159
|
+
echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} first (Y/n)?"
|
|
3722
4160
|
read -n 1 -s choice || prompt_aborted
|
|
3723
4161
|
if is_yes "$choice"; then
|
|
3724
4162
|
echo
|
|
@@ -3772,7 +4210,7 @@ function merge_script {
|
|
|
3772
4210
|
elif [ "$local_commit" != "$remote_commit" ]; then
|
|
3773
4211
|
echo -e "${YELLOW}⚠ Remote changes detected.${ENDCOLOR}"
|
|
3774
4212
|
echo
|
|
3775
|
-
echo -e "Do you want to fetch ${YELLOW}${origin_name}/${merge_branch}${ENDCOLOR} before merge (
|
|
4213
|
+
echo -e "Do you want to fetch ${YELLOW}${origin_name}/${merge_branch}${ENDCOLOR} before merge (Y/n)?"
|
|
3776
4214
|
read -n 1 -s choice || prompt_aborted
|
|
3777
4215
|
if is_yes "$choice"; then
|
|
3778
4216
|
echo
|
|
@@ -3901,7 +4339,7 @@ function resolve_conflicts {
|
|
|
3901
4339
|
if [ "$choice" == "4" ]; then
|
|
3902
4340
|
echo
|
|
3903
4341
|
echo -e "${RED}⚠ This will discard your current changes for these files.${ENDCOLOR}"
|
|
3904
|
-
echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/
|
|
4342
|
+
echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/N)?"
|
|
3905
4343
|
if confirm_destructive; then
|
|
3906
4344
|
echo -e "${YELLOW}Accepting all incoming changes...${ENDCOLOR}"
|
|
3907
4345
|
deleted_files=$(git --no-pager diff --name-only --diff-filter=D --relative 2>/dev/null)
|
|
@@ -3909,7 +4347,7 @@ function resolve_conflicts {
|
|
|
3909
4347
|
echo -e "${YELLOW}⚠ Some files were deleted in the incoming branch:${ENDCOLOR}"
|
|
3910
4348
|
echo "$deleted_files" | sed 's/^/\t/'
|
|
3911
4349
|
echo
|
|
3912
|
-
echo -e "Do you want to continue and accept the deletions (y/
|
|
4350
|
+
echo -e "Do you want to continue and accept the deletions (y/N)?"
|
|
3913
4351
|
if ! confirm_destructive; then
|
|
3914
4352
|
echo -e "${YELLOW}Cancelled. Continuing...${ENDCOLOR}"
|
|
3915
4353
|
continue
|
|
@@ -3967,7 +4405,7 @@ function resolve_conflicts {
|
|
|
3967
4405
|
if [ "$choice" == "5" ]; then
|
|
3968
4406
|
echo
|
|
3969
4407
|
echo -e "${RED}⚠ This will discard the incoming changes for these files.${ENDCOLOR}"
|
|
3970
|
-
echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/
|
|
4408
|
+
echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/N)?"
|
|
3971
4409
|
if confirm_destructive; then
|
|
3972
4410
|
echo -e "${YELLOW}Accepting all current changes...${ENDCOLOR}"
|
|
3973
4411
|
deleted_files=$(git --no-pager diff --name-only --diff-filter=D --relative 2>/dev/null)
|
|
@@ -3975,7 +4413,7 @@ function resolve_conflicts {
|
|
|
3975
4413
|
echo -e "${YELLOW}⚠ Some files were deleted in the current branch:${ENDCOLOR}"
|
|
3976
4414
|
echo "$deleted_files" | sed 's/^/\t/'
|
|
3977
4415
|
echo
|
|
3978
|
-
echo -e "Do you want to continue and accept the deletions (y/
|
|
4416
|
+
echo -e "Do you want to continue and accept the deletions (y/N)?"
|
|
3979
4417
|
if ! confirm_destructive; then
|
|
3980
4418
|
echo -e "${YELLOW}Cancelled. Continuing...${ENDCOLOR}"
|
|
3981
4419
|
continue
|
|
@@ -4094,7 +4532,7 @@ ${staged_with_tab}
|
|
|
4094
4532
|
echo
|
|
4095
4533
|
echo -e "${YELLOW}⚠ Merge commit message cannot be empty.${ENDCOLOR}"
|
|
4096
4534
|
echo
|
|
4097
|
-
read -n 1 -p "Do you want to try for one more time? (
|
|
4535
|
+
read -n 1 -p "Do you want to try for one more time? (Y/n) " -s -e choice || choice="n"
|
|
4098
4536
|
if ! is_yes "$choice"; then
|
|
4099
4537
|
while IFS= read -r _cf; do
|
|
4100
4538
|
[ -n "$_cf" ] && git restore --staged -- ":(literal)$_cf" 2>/dev/null
|
|
@@ -4181,7 +4619,7 @@ function rebase_script {
|
|
|
4181
4619
|
elif [ "$current_local_commit" != "$current_remote_commit" ]; then
|
|
4182
4620
|
echo -e "${YELLOW}⚠ Remote changes detected on ${current_branch}.${ENDCOLOR}"
|
|
4183
4621
|
echo
|
|
4184
|
-
echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} first (
|
|
4622
|
+
echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} first (Y/n)?"
|
|
4185
4623
|
read -n 1 -s choice || prompt_aborted
|
|
4186
4624
|
if is_yes "$choice"; then
|
|
4187
4625
|
echo
|
|
@@ -4340,7 +4778,7 @@ function rebase_branch {
|
|
|
4340
4778
|
fixup_commits_with_dates=$(git log --format="${YELLOW}%h${ENDCOLOR} (${BLUE}%cr${ENDCOLOR}) ${BOLD}%s${ENDCOLOR}" --grep="^fixup!" $ref..HEAD 2>/dev/null || echo "")
|
|
4341
4779
|
echo -e "$fixup_commits_with_dates" | sed 's/^/\t/'
|
|
4342
4780
|
echo
|
|
4343
|
-
echo -e "Proceed with fast autosquash rebase (y/
|
|
4781
|
+
echo -e "Proceed with fast autosquash rebase (y/N)?"
|
|
4344
4782
|
if ! confirm_destructive; then
|
|
4345
4783
|
echo -e "${YELLOW}Rebase cancelled${ENDCOLOR}"
|
|
4346
4784
|
exit
|
|
@@ -4526,7 +4964,7 @@ function rebase_conflicts {
|
|
|
4526
4964
|
if [ "$force_skip" != "true" ]; then
|
|
4527
4965
|
echo
|
|
4528
4966
|
echo -e "${RED}⚠ Dropping this commit will permanently remove it from history.${ENDCOLOR}"
|
|
4529
|
-
echo -e "Are you sure you want to ${RED}skip and discard${ENDCOLOR} this commit (y/
|
|
4967
|
+
echo -e "Are you sure you want to ${RED}skip and discard${ENDCOLOR} this commit (y/N)?"
|
|
4530
4968
|
if ! confirm_destructive; then
|
|
4531
4969
|
echo -e "${YELLOW}Continuing...${ENDCOLOR}"
|
|
4532
4970
|
continue
|
|
@@ -4545,7 +4983,7 @@ function rebase_conflicts {
|
|
|
4545
4983
|
fi
|
|
4546
4984
|
if [ "$choice" == "4" ]; then
|
|
4547
4985
|
echo
|
|
4548
|
-
echo -e "Are you sure you want to ${YELLOW}abort rebase${ENDCOLOR} (y/
|
|
4986
|
+
echo -e "Are you sure you want to ${YELLOW}abort rebase${ENDCOLOR} (y/N)?"
|
|
4549
4987
|
if confirm_destructive; then
|
|
4550
4988
|
echo -e "${YELLOW}Aborting rebase...${ENDCOLOR}"
|
|
4551
4989
|
git rebase --abort
|
|
@@ -4558,7 +4996,7 @@ function rebase_conflicts {
|
|
|
4558
4996
|
if [ "$choice" == "5" ]; then
|
|
4559
4997
|
echo
|
|
4560
4998
|
echo -e "${RED}⚠ This will discard your current changes for these files.${ENDCOLOR}"
|
|
4561
|
-
echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/
|
|
4999
|
+
echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/N)?"
|
|
4562
5000
|
if confirm_destructive; then
|
|
4563
5001
|
echo -e "${YELLOW}Accepting all incoming changes...${ENDCOLOR}"
|
|
4564
5002
|
deleted_files=$(git --no-pager diff --name-only --diff-filter=D --relative 2>/dev/null)
|
|
@@ -4566,7 +5004,7 @@ function rebase_conflicts {
|
|
|
4566
5004
|
echo -e "${YELLOW}⚠ Some files were deleted in the incoming branch:${ENDCOLOR}"
|
|
4567
5005
|
echo "$deleted_files" | sed 's/^/\t/'
|
|
4568
5006
|
echo
|
|
4569
|
-
echo -e "Do you want to continue and accept the deletions (y/
|
|
5007
|
+
echo -e "Do you want to continue and accept the deletions (y/N)?"
|
|
4570
5008
|
if ! confirm_destructive; then
|
|
4571
5009
|
echo -e "${YELLOW}Cancelled. Continuing...${ENDCOLOR}"
|
|
4572
5010
|
continue
|
|
@@ -4626,7 +5064,7 @@ function rebase_conflicts {
|
|
|
4626
5064
|
if [ "$choice" == "6" ]; then
|
|
4627
5065
|
echo
|
|
4628
5066
|
echo -e "${RED}⚠ This will discard the incoming changes for these files.${ENDCOLOR}"
|
|
4629
|
-
echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/
|
|
5067
|
+
echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/N)?"
|
|
4630
5068
|
if confirm_destructive; then
|
|
4631
5069
|
echo -e "${YELLOW}Accepting all current changes...${ENDCOLOR}"
|
|
4632
5070
|
deleted_files=$(git --no-pager diff --name-only --diff-filter=D --relative 2>/dev/null)
|
|
@@ -4634,7 +5072,7 @@ function rebase_conflicts {
|
|
|
4634
5072
|
echo -e "${YELLOW}⚠ Some files were deleted in the current branch:${ENDCOLOR}"
|
|
4635
5073
|
echo "$deleted_files" | sed 's/^/\t/'
|
|
4636
5074
|
echo
|
|
4637
|
-
echo -e "Do you want to continue and accept the deletions (y/
|
|
5075
|
+
echo -e "Do you want to continue and accept the deletions (y/N)?"
|
|
4638
5076
|
if ! confirm_destructive; then
|
|
4639
5077
|
echo -e "${YELLOW}Cancelled. Continuing...${ENDCOLOR}"
|
|
4640
5078
|
continue
|
|
@@ -4928,7 +5366,7 @@ function handle_cherry_pick_conflicts {
|
|
|
4928
5366
|
;;
|
|
4929
5367
|
2)
|
|
4930
5368
|
echo
|
|
4931
|
-
echo -e "Are you sure you want to ${RED}skip${ENDCOLOR} this commit (y/
|
|
5369
|
+
echo -e "Are you sure you want to ${RED}skip${ENDCOLOR} this commit (y/N)?"
|
|
4932
5370
|
if confirm_destructive; then
|
|
4933
5371
|
echo -e "${YELLOW}Skipping commit ${commit_hash::7}${ENDCOLOR}"
|
|
4934
5372
|
git cherry-pick --skip 2>/dev/null
|
|
@@ -4940,7 +5378,7 @@ function handle_cherry_pick_conflicts {
|
|
|
4940
5378
|
;;
|
|
4941
5379
|
3)
|
|
4942
5380
|
echo
|
|
4943
|
-
echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/
|
|
5381
|
+
echo -e "Are you sure you want to ${GREEN}accept all incoming changes${ENDCOLOR} (y/N)?"
|
|
4944
5382
|
if confirm_destructive; then
|
|
4945
5383
|
echo -e "${YELLOW}Accepting all incoming changes...${ENDCOLOR}"
|
|
4946
5384
|
git checkout --theirs . 2>/dev/null
|
|
@@ -4961,7 +5399,7 @@ function handle_cherry_pick_conflicts {
|
|
|
4961
5399
|
;;
|
|
4962
5400
|
4)
|
|
4963
5401
|
echo
|
|
4964
|
-
echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/
|
|
5402
|
+
echo -e "Are you sure you want to ${GREEN}accept all current changes${ENDCOLOR} (y/N)?"
|
|
4965
5403
|
if confirm_destructive; then
|
|
4966
5404
|
echo -e "${YELLOW}Accepting all current changes...${ENDCOLOR}"
|
|
4967
5405
|
git checkout --ours . 2>/dev/null
|
|
@@ -4982,7 +5420,7 @@ function handle_cherry_pick_conflicts {
|
|
|
4982
5420
|
;;
|
|
4983
5421
|
5)
|
|
4984
5422
|
echo
|
|
4985
|
-
echo -e "Are you sure you want to ${YELLOW}abort${ENDCOLOR} the pull operation (y/
|
|
5423
|
+
echo -e "Are you sure you want to ${YELLOW}abort${ENDCOLOR} the pull operation (y/N)?"
|
|
4986
5424
|
if confirm_destructive; then
|
|
4987
5425
|
echo -e "${YELLOW}Aborting pull commits operation...${ENDCOLOR}"
|
|
4988
5426
|
git cherry-pick --abort 2>/dev/null
|
|
@@ -5158,7 +5596,7 @@ Re-do the grouping. Every input hash MUST appear in EXACTLY ONE \"commits\" arra
|
|
|
5158
5596
|
local max_out=$(( commit_count * 120 ))
|
|
5159
5597
|
[ "$max_out" -lt 2048 ] && max_out=2048
|
|
5160
5598
|
[ "$max_out" -gt 16384 ] && max_out=16384
|
|
5161
|
-
call_ai_api "$system_prompt" "$user_prompt" "$max_out" "
|
|
5599
|
+
call_ai_api "$system_prompt" "$user_prompt" "$max_out" "" '{"type":"json_object"}' "low"
|
|
5162
5600
|
}
|
|
5163
5601
|
function squash_parse_ai_plan {
|
|
5164
5602
|
local response="$1"
|
|
@@ -5570,7 +6008,7 @@ function squash_script {
|
|
|
5570
6008
|
size_warning=" ${RED}(large — model accuracy may degrade above ~${SQUASH_MAX_COMMITS} commits)${ENDCOLOR}"
|
|
5571
6009
|
fi
|
|
5572
6010
|
echo -e "${YELLOW}Will analyze ${BOLD}${commit_count}${NORMAL}${YELLOW} commits with AI.${ENDCOLOR}${size_warning}"
|
|
5573
|
-
echo -e "Continue (
|
|
6011
|
+
echo -e "Continue (Y/n)?"
|
|
5574
6012
|
local pre_choice
|
|
5575
6013
|
read -n 1 -s pre_choice || prompt_aborted
|
|
5576
6014
|
if ! is_yes "$pre_choice"; then
|
|
@@ -5620,7 +6058,7 @@ function squash_script {
|
|
|
5620
6058
|
if [ -z "$mode_yes" ]; then
|
|
5621
6059
|
echo -e "${RED}⚠ Applying this plan rewrites your commit history.${ENDCOLOR}"
|
|
5622
6060
|
echo -e "${CYAN}💡 Recover the original branch with ${BOLD}gitb undo rebase${NORMAL}${CYAN} if needed.${ENDCOLOR}"
|
|
5623
|
-
echo -e "Are you sure you want to proceed (y/
|
|
6061
|
+
echo -e "Are you sure you want to proceed (y/N)?"
|
|
5624
6062
|
if ! confirm_destructive; then
|
|
5625
6063
|
echo -e "${YELLOW}Cancelled.${ENDCOLOR}"
|
|
5626
6064
|
exit 0
|
|
@@ -6166,7 +6604,7 @@ function push_script {
|
|
|
6166
6604
|
echo -e "${BLUE}Branch ${YELLOW}${current_branch}${BLUE} does not exist on ${YELLOW}${origin_name}${BLUE} yet.${ENDCOLOR}"
|
|
6167
6605
|
echo
|
|
6168
6606
|
if [ -z "${fast}" ]; then
|
|
6169
|
-
echo -e "Do you want to push the new branch to ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (
|
|
6607
|
+
echo -e "Do you want to push the new branch to ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (Y/n)?"
|
|
6170
6608
|
yes_no_choice "Pushing..."
|
|
6171
6609
|
else
|
|
6172
6610
|
echo -e "${YELLOW}Pushing...${ENDCOLOR}"
|
|
@@ -6229,7 +6667,7 @@ function push_script {
|
|
|
6229
6667
|
if [ "${current_branch}" == "${main_branch}" ]; then
|
|
6230
6668
|
echo -e "${RED}⚠ You are about to push directly to the default branch ${YELLOW}${main_branch}${RED}.${ENDCOLOR}"
|
|
6231
6669
|
fi
|
|
6232
|
-
echo -e "Do you want to push${RED}${force_label}${ENDCOLOR} these commits to ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (
|
|
6670
|
+
echo -e "Do you want to push${RED}${force_label}${ENDCOLOR} these commits to ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (Y/n)?"
|
|
6233
6671
|
yes_no_choice "Pushing..."
|
|
6234
6672
|
else
|
|
6235
6673
|
echo -e "${YELLOW}Pushing...${ENDCOLOR}"
|
|
@@ -6243,7 +6681,7 @@ function push_script {
|
|
|
6243
6681
|
echo
|
|
6244
6682
|
pull "$current_branch" "$origin_name" "$editor" "rebase"
|
|
6245
6683
|
else
|
|
6246
|
-
echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (
|
|
6684
|
+
echo -e "Do you want to pull ${YELLOW}${origin_name}/${current_branch}${ENDCOLOR} (Y/n)?"
|
|
6247
6685
|
yes_no_choice "Pulling..."
|
|
6248
6686
|
pull "$current_branch" "$origin_name" "$editor"
|
|
6249
6687
|
fi
|
|
@@ -6680,7 +7118,7 @@ A feature is defined by WHAT the change accomplishes, read from the actual code
|
|
|
6680
7118
|
|
|
6681
7119
|
Input arrives in XML tags: a list of staged files, the actual diff, a diff summary, recent commit messages (for scope-naming conventions), and folder-derived candidate scope tokens.
|
|
6682
7120
|
|
|
6683
|
-
Task: assign EVERY staged file to exactly one feature group. Use 1 group only if all changes truly form a single feature; otherwise use 2 to ${max_groups} groups. Never exceed ${max_groups} groups — prefer a broader feature over several tiny ones. Name each group with a short lowercase conventional-commit scope describing the FEATURE, matching the style of <recent_commits>. <heuristic_candidates> are folder-derived hints — use one only if it happens to match a feature's purpose. Use \"misc\" only for unrelated incidental files with no feature (e.g., README, top-level config).
|
|
7121
|
+
Task: assign EVERY staged file to exactly one feature group. Use 1 group only if all changes truly form a single feature; otherwise use 2 to ${max_groups} groups. Never exceed ${max_groups} groups — prefer a broader feature over several tiny ones. Name each group with a short lowercase conventional-commit scope describing the FEATURE — the bare scope token only (e.g. \"auth\", never a full prefix like \"feat(auth)\" or a type like \"docs:\"), matching the scope-naming style of <recent_commits>. <heuristic_candidates> are folder-derived hints — use one only if it happens to match a feature's purpose. Use \"misc\" only for unrelated incidental files with no feature (e.g., README, top-level config).
|
|
6684
7122
|
|
|
6685
7123
|
Output format: TSV only. One line per staged file in the form <scope><TAB><file_path>. No header, no prose, no markdown fences, no surrounding quotes. Every file from <staged_files> MUST appear exactly once. The file paths must be byte-identical to those in <staged_files>."
|
|
6686
7124
|
|
|
@@ -6706,7 +7144,7 @@ ${heuristic_hint}
|
|
|
6706
7144
|
|
|
6707
7145
|
Read <diff> to decide which files implement the same feature. Output TSV (scope<TAB>file) for every staged file. No prose."
|
|
6708
7146
|
local ai_response
|
|
6709
|
-
ai_response=$(call_ai_api "$system_prompt" "$user_prompt" "$AI_MAX_TOKENS_FULL" "
|
|
7147
|
+
ai_response=$(call_ai_api "$system_prompt" "$user_prompt" "$AI_MAX_TOKENS_FULL" "" "" "low" 2>/dev/null)
|
|
6710
7148
|
if [ $? -ne 0 ] || [ -z "$ai_response" ]; then
|
|
6711
7149
|
return 1
|
|
6712
7150
|
fi
|
|
@@ -6726,6 +7164,11 @@ Read <diff> to decide which files implement the same feature. Output TSV (scope<
|
|
|
6726
7164
|
[ "$tab_count" -ne 1 ] && continue
|
|
6727
7165
|
scope="${line%% *}"
|
|
6728
7166
|
file="${line#* }"
|
|
7167
|
+
scope="${scope%:}"
|
|
7168
|
+
local type_scope_re='^[a-zA-Z0-9._/-]+\(([a-zA-Z0-9._/-]+)\)$'
|
|
7169
|
+
if [[ "$scope" =~ $type_scope_re ]]; then
|
|
7170
|
+
scope="${BASH_REMATCH[1]}"
|
|
7171
|
+
fi
|
|
6729
7172
|
if ! [[ "$scope" =~ ^[a-zA-Z0-9._/-]+$ ]]; then
|
|
6730
7173
|
continue
|
|
6731
7174
|
fi
|
|
@@ -7225,6 +7668,19 @@ function handle_ai_commit_generation {
|
|
|
7225
7668
|
if [ $? -ne 0 ] || [ -z "$ai_commit_message" ]; then
|
|
7226
7669
|
echo
|
|
7227
7670
|
echo -e "${RED}✗ Cannot generate AI commit message.${ENDCOLOR}"
|
|
7671
|
+
echo
|
|
7672
|
+
read_key choice "Continue with a manual commit message? (Y/n) " || choice="n"
|
|
7673
|
+
echo
|
|
7674
|
+
if is_yes "$choice"; then
|
|
7675
|
+
if [ "$ai_mode" = "subject" ]; then
|
|
7676
|
+
echo
|
|
7677
|
+
fi
|
|
7678
|
+
echo -e "${YELLOW}Falling back to manual commit message creation...${ENDCOLOR}"
|
|
7679
|
+
if [ "$ai_mode" = "subject" ]; then
|
|
7680
|
+
echo
|
|
7681
|
+
fi
|
|
7682
|
+
return
|
|
7683
|
+
fi
|
|
7228
7684
|
cleanup_on_exit "$git_add"
|
|
7229
7685
|
exit 1
|
|
7230
7686
|
fi
|
|
@@ -7289,7 +7745,7 @@ ${ai_commit_message}"
|
|
|
7289
7745
|
echo
|
|
7290
7746
|
echo -e "${YELLOW}⚠ Commit message cannot be empty.${ENDCOLOR}"
|
|
7291
7747
|
echo
|
|
7292
|
-
read -n 1 -p "Try again? (
|
|
7748
|
+
read -n 1 -p "Try again? (Y/n) " -s -e choice || choice="n"
|
|
7293
7749
|
if ! is_yes "$choice"; then
|
|
7294
7750
|
cleanup_on_exit "$git_add"
|
|
7295
7751
|
rm -f "$commitmsg_file"
|
|
@@ -7696,7 +8152,7 @@ function commit_script {
|
|
|
7696
8152
|
if [ -n "$saved_git_add" ]; then
|
|
7697
8153
|
echo
|
|
7698
8154
|
echo -e "${YELLOW}Found previous git add arguments:${ENDCOLOR} ${BOLD}$saved_git_add${ENDCOLOR}"
|
|
7699
|
-
read_key choice "Use them? (
|
|
8155
|
+
read_key choice "Use them? (Y/n) " || choice="n"
|
|
7700
8156
|
echo
|
|
7701
8157
|
if is_yes "$choice"; then
|
|
7702
8158
|
git add $saved_git_add
|
|
@@ -8087,7 +8543,7 @@ ${staged_with_tab}
|
|
|
8087
8543
|
echo
|
|
8088
8544
|
echo -e "${YELLOW}⚠ Commit message cannot be empty.${ENDCOLOR}"
|
|
8089
8545
|
echo
|
|
8090
|
-
read -n 1 -p "Try again? (
|
|
8546
|
+
read -n 1 -p "Try again? (Y/n) " -s -e choice || choice="n"
|
|
8091
8547
|
if ! is_yes "$choice"; then
|
|
8092
8548
|
cleanup_on_exit "$git_add"
|
|
8093
8549
|
rm -f "$commitmsg_file"
|
|
@@ -8293,7 +8749,7 @@ function edit_branch_name {
|
|
|
8293
8749
|
if [ "$old_name" = "$main_branch" ]; then
|
|
8294
8750
|
echo -e "${YELLOW}⚠ ${BOLD}${old_name}${NORMAL} is the configured main branch.${ENDCOLOR}"
|
|
8295
8751
|
echo -e "Renaming it will leave ${YELLOW}gitbasher.branch${ENDCOLOR} pointing at the old name."
|
|
8296
|
-
printf "Rename anyway? (y/
|
|
8752
|
+
printf "Rename anyway? (y/N) "
|
|
8297
8753
|
yes_no_choice_strict
|
|
8298
8754
|
echo
|
|
8299
8755
|
fi
|
|
@@ -8316,7 +8772,7 @@ function edit_branch_name {
|
|
|
8316
8772
|
echo
|
|
8317
8773
|
echo -e "${YELLOW}${BOLD}${old_name}${NORMAL}${YELLOW} also exists on ${origin_name}.${ENDCOLOR}"
|
|
8318
8774
|
echo -e "Push ${BOLD}${new_name}${NORMAL} and delete the old remote branch?"
|
|
8319
|
-
printf "(y/
|
|
8775
|
+
printf "(y/N) "
|
|
8320
8776
|
yes_no_choice_strict
|
|
8321
8777
|
echo
|
|
8322
8778
|
local push_out
|
|
@@ -8481,7 +8937,7 @@ function branch_script {
|
|
|
8481
8937
|
done
|
|
8482
8938
|
echo
|
|
8483
8939
|
echo -e "${RED}⚠ These branches will be force-deleted (${BLUE}git branch -D${RED}).${ENDCOLOR}"
|
|
8484
|
-
echo -e "Are you sure you want to delete them (y/
|
|
8940
|
+
echo -e "Are you sure you want to delete them (y/N)?"
|
|
8485
8941
|
yes_no_choice_strict "Deleting gone branches..."
|
|
8486
8942
|
for branch in "${gone_branches[@]}"; do
|
|
8487
8943
|
delete_output=$(git branch -D "$branch" 2>&1)
|
|
@@ -8501,7 +8957,7 @@ function branch_script {
|
|
|
8501
8957
|
fi
|
|
8502
8958
|
if [[ -n "${tag}" ]]; then
|
|
8503
8959
|
echo -e "${YELLOW}Do you want to fetch remote tags first?${ENDCOLOR}"
|
|
8504
|
-
read -n 1 -p "Fetch remote? (
|
|
8960
|
+
read -n 1 -p "Fetch remote? (Y/n) " choice || choice="n"
|
|
8505
8961
|
echo
|
|
8506
8962
|
if [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
|
|
8507
8963
|
echo
|
|
@@ -8584,7 +9040,7 @@ function branch_script {
|
|
|
8584
9040
|
elif [[ -z "$new" ]] && [[ -n "$delete" ]] && [[ -z "$tag" ]]; then
|
|
8585
9041
|
echo -e "${YELLOW}Clean up local branches that no longer exist on the remote?${ENDCOLOR}"
|
|
8586
9042
|
echo
|
|
8587
|
-
printf "Continue (
|
|
9043
|
+
printf "Continue (Y/n)? "
|
|
8588
9044
|
while [ true ]; do
|
|
8589
9045
|
read -n 1 -s choice || prompt_aborted
|
|
8590
9046
|
if is_yes "$choice"; then
|
|
@@ -8614,7 +9070,7 @@ function branch_script {
|
|
|
8614
9070
|
done
|
|
8615
9071
|
echo
|
|
8616
9072
|
echo -e "${RED}⚠ These branches will be force-deleted (${BLUE}git branch -D${RED}).${ENDCOLOR}"
|
|
8617
|
-
printf "Are you sure you want to delete them (y/
|
|
9073
|
+
printf "Are you sure you want to delete them (y/N)? "
|
|
8618
9074
|
if confirm_destructive; then
|
|
8619
9075
|
echo
|
|
8620
9076
|
for branch in "${branches_to_delete[@]}"; do
|
|
@@ -8654,7 +9110,7 @@ function branch_script {
|
|
|
8654
9110
|
do
|
|
8655
9111
|
printf "\t${merged_branches_without_main[index]}\n"
|
|
8656
9112
|
done
|
|
8657
|
-
printf "\nDelete these merged branches (
|
|
9113
|
+
printf "\nDelete these merged branches (Y/n)? "
|
|
8658
9114
|
while [ true ]; do
|
|
8659
9115
|
read -n 1 -s choice || prompt_aborted
|
|
8660
9116
|
if is_yes "$choice"; then
|
|
@@ -8722,7 +9178,7 @@ function branch_script {
|
|
|
8722
9178
|
done
|
|
8723
9179
|
echo
|
|
8724
9180
|
echo -e "${RED}⚠ These will be force-deleted (${BLUE}git branch -D${RED}).${ENDCOLOR}"
|
|
8725
|
-
printf "Are you sure (y/
|
|
9181
|
+
printf "Are you sure (y/N)? "
|
|
8726
9182
|
if confirm_destructive; then
|
|
8727
9183
|
echo
|
|
8728
9184
|
for branch in "${squash_merged_branches[@]}"; do
|
|
@@ -8750,7 +9206,7 @@ function branch_script {
|
|
|
8750
9206
|
elif [[ ${delete_output} == *"is not fully merged"* ]]; then
|
|
8751
9207
|
echo -e "${YELLOW}⚠ Branch '$branch_name' is not fully merged.${ENDCOLOR}"
|
|
8752
9208
|
echo -e "${RED}⚠ Force-delete (${BLUE}git branch -D${RED}) will discard unmerged commits.${ENDCOLOR}"
|
|
8753
|
-
printf "Force-delete this branch (y/
|
|
9209
|
+
printf "Force-delete this branch (y/N)? "
|
|
8754
9210
|
confirm_destructive
|
|
8755
9211
|
case $? in
|
|
8756
9212
|
0)
|
|
@@ -8774,7 +9230,7 @@ function branch_script {
|
|
|
8774
9230
|
fi
|
|
8775
9231
|
if git rev-parse --verify --quiet "refs/remotes/$origin_name/$branch_name" >/dev/null 2>&1; then
|
|
8776
9232
|
echo
|
|
8777
|
-
printf "Also delete this branch on ${origin_name} (y/
|
|
9233
|
+
printf "Also delete this branch on ${origin_name} (y/N)? "
|
|
8778
9234
|
if confirm_destructive; then
|
|
8779
9235
|
echo
|
|
8780
9236
|
echo -e "${YELLOW}Deleting from remote...${ENDCOLOR}"
|
|
@@ -8945,7 +9401,7 @@ function branch_script {
|
|
|
8945
9401
|
fi
|
|
8946
9402
|
if [ -n "$origin_name" ]; then
|
|
8947
9403
|
echo
|
|
8948
|
-
echo -e "Do you want to push the new branch to ${YELLOW}${origin_name}/${branch_name}${ENDCOLOR} (
|
|
9404
|
+
echo -e "Do you want to push the new branch to ${YELLOW}${origin_name}/${branch_name}${ENDCOLOR} (Y/n)?"
|
|
8949
9405
|
yes_no_choice "Pushing..."
|
|
8950
9406
|
current_branch="$branch_name"
|
|
8951
9407
|
push -u
|
|
@@ -9184,7 +9640,7 @@ function tag_script {
|
|
|
9184
9640
|
fi
|
|
9185
9641
|
if [ -n "${delete}" ] && [ -z "$select" ]; then
|
|
9186
9642
|
echo
|
|
9187
|
-
echo -e "${YELLOW}Do you really want to delete all local tags (y/
|
|
9643
|
+
echo -e "${YELLOW}Do you really want to delete all local tags (y/N)?${ENDCOLOR}"
|
|
9188
9644
|
yes_no_choice_strict "\nDeleting all local tags..."
|
|
9189
9645
|
local _da_failed=0
|
|
9190
9646
|
while IFS= read -r _da_tag; do
|
|
@@ -9225,7 +9681,7 @@ function tag_script {
|
|
|
9225
9681
|
check_code $? "$delete_result" "delete tag"
|
|
9226
9682
|
echo -e "${GREEN}✓ Deleted tag '${tag_name}'${ENDCOLOR}"
|
|
9227
9683
|
echo
|
|
9228
|
-
echo -e "Also delete this tag on ${YELLOW}${origin_name}${ENDCOLOR} (y/
|
|
9684
|
+
echo -e "Also delete this tag on ${YELLOW}${origin_name}${ENDCOLOR} (y/N)?"
|
|
9229
9685
|
yes_no_choice_strict "\nDeleting..."
|
|
9230
9686
|
push_tag $tag_name "true"
|
|
9231
9687
|
exit
|
|
@@ -9286,7 +9742,7 @@ function tag_script {
|
|
|
9286
9742
|
echo
|
|
9287
9743
|
echo -e "${YELLOW}⚠ Tag message cannot be empty.${ENDCOLOR}"
|
|
9288
9744
|
echo
|
|
9289
|
-
read -n 1 -p "Try again? (
|
|
9745
|
+
read -n 1 -p "Try again? (Y/n) " -s -e choice || choice="n"
|
|
9290
9746
|
if ! is_yes "$choice"; then
|
|
9291
9747
|
rm -f "$tag_file"
|
|
9292
9748
|
exit
|
|
@@ -9324,7 +9780,7 @@ function tag_script {
|
|
|
9324
9780
|
echo -e "$tag_message"
|
|
9325
9781
|
fi
|
|
9326
9782
|
echo
|
|
9327
|
-
echo -e "Do you want to push it to ${YELLOW}${origin_name}${ENDCOLOR} (
|
|
9783
|
+
echo -e "Do you want to push it to ${YELLOW}${origin_name}${ENDCOLOR} (Y/n)?"
|
|
9328
9784
|
yes_no_choice "\nPushing..."
|
|
9329
9785
|
push_tag $tag_name
|
|
9330
9786
|
}
|
|
@@ -9418,7 +9874,7 @@ function reset_script {
|
|
|
9418
9874
|
else
|
|
9419
9875
|
echo -e "${YELLOW}⚠ This will move HEAD; reset changes will be left unstaged.${ENDCOLOR}"
|
|
9420
9876
|
fi
|
|
9421
|
-
echo -e "Are you sure you want to continue (y/
|
|
9877
|
+
echo -e "Are you sure you want to continue (y/N)?"
|
|
9422
9878
|
yes_no_choice_strict "Resetting..."
|
|
9423
9879
|
reset_output=$(git reset "$args" "$move_ref" 2>&1)
|
|
9424
9880
|
check_code $? "$reset_output" "reset"
|
|
@@ -9714,7 +10170,7 @@ function stash_script {
|
|
|
9714
10170
|
fi
|
|
9715
10171
|
echo
|
|
9716
10172
|
echo -e "${RED}⚠ Dropping stash $selected_stash cannot be undone.${ENDCOLOR}"
|
|
9717
|
-
echo -e "Are you sure you want to drop it (y/
|
|
10173
|
+
echo -e "Are you sure you want to drop it (y/N)?"
|
|
9718
10174
|
yes_no_choice_strict "Dropping stash"
|
|
9719
10175
|
drop_output=$(git stash drop "$selected_stash" 2>&1)
|
|
9720
10176
|
drop_code=$?
|
|
@@ -9829,7 +10285,7 @@ function cherry_script {
|
|
|
9829
10285
|
echo -e "Files with changes:"
|
|
9830
10286
|
git status --porcelain | sed 's/^/ /'
|
|
9831
10287
|
echo
|
|
9832
|
-
echo -e "Continue with cherry-pick (may cause conflicts) (
|
|
10288
|
+
echo -e "Continue with cherry-pick (may cause conflicts) (Y/n)?"
|
|
9833
10289
|
yes_no_choice "Proceeding..."
|
|
9834
10290
|
echo
|
|
9835
10291
|
fi
|
|
@@ -9909,7 +10365,7 @@ function handle_cherry_pick_conflicts {
|
|
|
9909
10365
|
fi
|
|
9910
10366
|
if [[ $output == *"The previous cherry-pick is now empty"* ]]; then
|
|
9911
10367
|
echo -e "${YELLOW}This commit is empty after cherry-pick (changes already applied).${ENDCOLOR}"
|
|
9912
|
-
echo -e "Skip it (y/
|
|
10368
|
+
echo -e "Skip it (y/N)?"
|
|
9913
10369
|
yes_no_choice_strict "Skipping empty commit..."
|
|
9914
10370
|
skip_output=$(git cherry-pick --skip 2>&1)
|
|
9915
10371
|
if [ $? == 0 ]; then
|
|
@@ -10029,7 +10485,7 @@ function choose_commits_interactive {
|
|
|
10029
10485
|
echo -e " ${commit::7} - $message"
|
|
10030
10486
|
done
|
|
10031
10487
|
echo
|
|
10032
|
-
echo -e "Proceed with cherry-pick? (
|
|
10488
|
+
echo -e "Proceed with cherry-pick? (Y/n)"
|
|
10033
10489
|
yes_no_choice "Proceeding with cherry-pick..."
|
|
10034
10490
|
local success_count=0
|
|
10035
10491
|
local total_count=${#selected_commits[@]}
|
|
@@ -10272,7 +10728,7 @@ function undo_commit {
|
|
|
10272
10728
|
echo -e "\t$cancelled_commit"
|
|
10273
10729
|
echo
|
|
10274
10730
|
echo -e "This will undo the last commit but ${GREEN}keep all changes staged${ENDCOLOR}"
|
|
10275
|
-
echo -e "Do you want to continue (y/
|
|
10731
|
+
echo -e "Do you want to continue (y/N)?"
|
|
10276
10732
|
yes_no_choice_strict "Undoing commit..."
|
|
10277
10733
|
reset_output=$(git reset --soft HEAD~1 2>&1)
|
|
10278
10734
|
reset_code=$?
|
|
@@ -10321,7 +10777,7 @@ function undo_amend {
|
|
|
10321
10777
|
echo -e "${YELLOW}Current (amended):${ENDCOLOR}\t$current_commit"
|
|
10322
10778
|
echo -e "${GREEN}Restore to:${ENDCOLOR}\t\t$pre_amend_commit"
|
|
10323
10779
|
echo
|
|
10324
|
-
echo -e "Do you want to undo the amend (y/
|
|
10780
|
+
echo -e "Do you want to undo the amend (y/N)?"
|
|
10325
10781
|
yes_no_choice_strict "Undoing amend..."
|
|
10326
10782
|
reset_output=$(git reset --soft "$pre_amend_ref" 2>&1)
|
|
10327
10783
|
reset_code=$?
|
|
@@ -10341,7 +10797,7 @@ function undo_amend {
|
|
|
10341
10797
|
function undo_merge {
|
|
10342
10798
|
if [ -f "$(git rev-parse --git-dir)/MERGE_HEAD" ]; then
|
|
10343
10799
|
echo -e "${YELLOW}⚠ A merge is in progress.${ENDCOLOR}"
|
|
10344
|
-
echo -e "Abort the ongoing merge (y/
|
|
10800
|
+
echo -e "Abort the ongoing merge (y/N)?"
|
|
10345
10801
|
yes_no_choice_strict "Aborting merge..."
|
|
10346
10802
|
abort_output=$(git merge --abort 2>&1)
|
|
10347
10803
|
abort_code=$?
|
|
@@ -10370,7 +10826,7 @@ function undo_merge {
|
|
|
10370
10826
|
echo -e "${GREEN}Restore to:${ENDCOLOR}\t$orig_commit"
|
|
10371
10827
|
echo
|
|
10372
10828
|
echo -e "${RED}⚠ This will discard the merge commit and all merge changes.${ENDCOLOR}"
|
|
10373
|
-
echo -e "Are you sure you want to undo the merge (y/
|
|
10829
|
+
echo -e "Are you sure you want to undo the merge (y/N)?"
|
|
10374
10830
|
yes_no_choice_strict "Undoing merge..."
|
|
10375
10831
|
reset_output=$(git reset --merge ORIG_HEAD 2>&1)
|
|
10376
10832
|
reset_code=$?
|
|
@@ -10387,7 +10843,7 @@ function undo_merge {
|
|
|
10387
10843
|
function undo_rebase {
|
|
10388
10844
|
if [ -d "$(git rev-parse --git-dir)/rebase-merge" ] || [ -d "$(git rev-parse --git-dir)/rebase-apply" ]; then
|
|
10389
10845
|
echo -e "${YELLOW}⚠ A rebase is in progress.${ENDCOLOR}"
|
|
10390
|
-
echo -e "Abort the ongoing rebase (y/
|
|
10846
|
+
echo -e "Abort the ongoing rebase (y/N)?"
|
|
10391
10847
|
yes_no_choice_strict "Aborting rebase..."
|
|
10392
10848
|
abort_output=$(git rebase --abort 2>&1)
|
|
10393
10849
|
abort_code=$?
|
|
@@ -10418,7 +10874,7 @@ function undo_rebase {
|
|
|
10418
10874
|
exit 1
|
|
10419
10875
|
fi
|
|
10420
10876
|
echo -e "${RED}⚠ This will discard all rebase changes and restore the original branch state.${ENDCOLOR}"
|
|
10421
|
-
echo -e "Are you sure you want to undo the rebase (y/
|
|
10877
|
+
echo -e "Are you sure you want to undo the rebase (y/N)?"
|
|
10422
10878
|
yes_no_choice_strict "Undoing rebase..."
|
|
10423
10879
|
reset_output=$(git reset --hard ORIG_HEAD 2>&1)
|
|
10424
10880
|
reset_code=$?
|
|
@@ -10442,7 +10898,7 @@ function undo_stash {
|
|
|
10442
10898
|
git_status
|
|
10443
10899
|
echo
|
|
10444
10900
|
echo -e "This will stash all current changes to undo the last stash pop/apply."
|
|
10445
|
-
echo -e "Continue (y/
|
|
10901
|
+
echo -e "Continue (y/N)?"
|
|
10446
10902
|
yes_no_choice_strict "Re-stashing changes..."
|
|
10447
10903
|
stash_output=$(git stash push -m "undo: re-stashed changes" --include-untracked 2>&1)
|
|
10448
10904
|
stash_code=$?
|
|
@@ -11492,7 +11948,7 @@ function log_commit_actions {
|
|
|
11492
11948
|
choice=$((10#$choice))
|
|
11493
11949
|
local file="${files[choice - 1]}"
|
|
11494
11950
|
echo -e "${RED}This will overwrite '${file}' in your working tree with its state from ${hash}.${ENDCOLOR}"
|
|
11495
|
-
printf "Restore it (y/
|
|
11951
|
+
printf "Restore it (y/N)? "
|
|
11496
11952
|
if confirm_destructive; then
|
|
11497
11953
|
result=$(git checkout "$hash" -- "$file" 2>&1)
|
|
11498
11954
|
check_code $? "$result" "restore"
|
|
@@ -12709,7 +13165,7 @@ function _do_worktree_remove {
|
|
|
12709
13165
|
echo -e "${RED}${rm_output}${ENDCOLOR}"
|
|
12710
13166
|
echo
|
|
12711
13167
|
echo -e "${YELLOW}Force remove this worktree? (uncommitted changes will be lost)${ENDCOLOR}"
|
|
12712
|
-
echo -e "Do you want to continue (y/
|
|
13168
|
+
echo -e "Do you want to continue (y/N)?"
|
|
12713
13169
|
yes_no_choice_strict "Force removing worktree..."
|
|
12714
13170
|
rm_output=$(LC_ALL=C git worktree remove --force --force "$selected_worktree_path" 2>&1)
|
|
12715
13171
|
rm_code=$?
|
|
@@ -12742,7 +13198,7 @@ function worktree_prune {
|
|
|
12742
13198
|
echo -e "${YELLOW}Records that would be removed:${ENDCOLOR}"
|
|
12743
13199
|
echo "$dry_run"
|
|
12744
13200
|
echo
|
|
12745
|
-
echo -e "${YELLOW}Continue? (
|
|
13201
|
+
echo -e "${YELLOW}Continue? (Y/n)${ENDCOLOR}"
|
|
12746
13202
|
yes_no_choice "Pruning..."
|
|
12747
13203
|
local prune_output
|
|
12748
13204
|
prune_output=$(git worktree prune --verbose 2>&1)
|
|
@@ -13422,7 +13878,7 @@ function create_hook {
|
|
|
13422
13878
|
if [ -f "$hook_file" ]; then
|
|
13423
13879
|
echo -e "${YELLOW}⚠ Hook '$hook_type' already exists.${ENDCOLOR}"
|
|
13424
13880
|
echo -e "${RED}⚠ Overwriting will replace its contents.${ENDCOLOR}"
|
|
13425
|
-
echo -e "Overwrite (y/
|
|
13881
|
+
echo -e "Overwrite (y/N)?"
|
|
13426
13882
|
if ! confirm_destructive; then
|
|
13427
13883
|
echo -e "${YELLOW}Hook creation cancelled.${ENDCOLOR}"
|
|
13428
13884
|
return 1
|
|
@@ -13468,7 +13924,7 @@ fi
|
|
|
13468
13924
|
# lines for any binary blob that happens to be staged.
|
|
13469
13925
|
if git diff --cached --name-only -z | xargs -0 grep -l --binary-files=without-match \"TODO\\|FIXME\" 2>/dev/null; then
|
|
13470
13926
|
echo \"⚠️ Warning: Found TODO/FIXME comments in staged files\"
|
|
13471
|
-
echo \"Continue anyway? (y/
|
|
13927
|
+
echo \"Continue anyway? (y/N)\"
|
|
13472
13928
|
read -n 1 answer
|
|
13473
13929
|
echo
|
|
13474
13930
|
if [ \"\$answer\" != \"y\" ] && [ \"\$answer\" != \"Y\" ]; then
|
|
@@ -13573,7 +14029,7 @@ exit 0"
|
|
|
13573
14029
|
echo -e "${GREEN}✓ Created executable hook: $hook_type${ENDCOLOR}"
|
|
13574
14030
|
echo -e "${YELLOW}Hook file: $hook_file${ENDCOLOR}"
|
|
13575
14031
|
echo
|
|
13576
|
-
echo -e "Do you want to ${BLUE}edit${ENDCOLOR} the hook now? (
|
|
14032
|
+
echo -e "Do you want to ${BLUE}edit${ENDCOLOR} the hook now? (Y/n)"
|
|
13577
14033
|
read -n 1 -s choice || prompt_aborted
|
|
13578
14034
|
echo
|
|
13579
14035
|
if is_yes "$choice"; then
|
|
@@ -13644,7 +14100,7 @@ function remove_hook {
|
|
|
13644
14100
|
fi
|
|
13645
14101
|
echo -e "${RED}⚠ Deleting hook '$hook_type' cannot be undone.${ENDCOLOR}"
|
|
13646
14102
|
echo -e "${GRAY}File: $hook_file${ENDCOLOR}"
|
|
13647
|
-
echo -e "Are you sure you want to delete it (y/
|
|
14103
|
+
echo -e "Are you sure you want to delete it (y/N)?"
|
|
13648
14104
|
if confirm_destructive; then
|
|
13649
14105
|
rm "$hook_file"
|
|
13650
14106
|
echo -e "${GREEN}✓ Removed hook: $hook_type${ENDCOLOR}"
|
|
@@ -13685,7 +14141,7 @@ function remove_all_hooks {
|
|
|
13685
14141
|
fi
|
|
13686
14142
|
echo
|
|
13687
14143
|
echo -e "${RED}⚠ This will permanently delete $hook_count hooks.${ENDCOLOR}"
|
|
13688
|
-
echo -e "${YELLOW}Are you absolutely sure (
|
|
14144
|
+
echo -e "${YELLOW}Are you absolutely sure (Y/n)?${ENDCOLOR}"
|
|
13689
14145
|
read -n 1 -p "Your choice: " choice < /dev/tty || choice="0"
|
|
13690
14146
|
echo
|
|
13691
14147
|
echo
|
|
@@ -13816,7 +14272,7 @@ function install_samples {
|
|
|
13816
14272
|
fi
|
|
13817
14273
|
echo
|
|
13818
14274
|
echo -e "${YELLOW}Ready to install $sample_count sample hooks.${ENDCOLOR}"
|
|
13819
|
-
echo -e "Continue (
|
|
14275
|
+
echo -e "Continue (Y/n)?"
|
|
13820
14276
|
read -n 1 -p "Your choice: " choice < /dev/tty || choice="n"
|
|
13821
14277
|
echo
|
|
13822
14278
|
echo
|
|
@@ -14264,7 +14720,7 @@ function origin_remove {
|
|
|
14264
14720
|
echo -e "URL: ${GRAY}$existing${ENDCOLOR}"
|
|
14265
14721
|
echo
|
|
14266
14722
|
echo -e "${RED}⚠ This will remove the remote (you can re-add it later).${ENDCOLOR}"
|
|
14267
|
-
echo -e "Are you sure (y/
|
|
14723
|
+
echo -e "Are you sure (y/N)?"
|
|
14268
14724
|
yes_no_choice_strict "" "true"
|
|
14269
14725
|
git remote remove "$target"
|
|
14270
14726
|
echo
|
|
@@ -14423,7 +14879,7 @@ function clone_script {
|
|
|
14423
14879
|
echo
|
|
14424
14880
|
if [ -z "$GITBASHER_TEST_MODE" ] && [ -t 0 ]; then
|
|
14425
14881
|
local clone_cd_choice
|
|
14426
|
-
echo -e "${YELLOW}Enter the new repo now (
|
|
14882
|
+
echo -e "${YELLOW}Enter the new repo now (Y/n)?${ENDCOLOR}"
|
|
14427
14883
|
read -n 1 -s clone_cd_choice
|
|
14428
14884
|
echo
|
|
14429
14885
|
if is_yes "$clone_cd_choice"; then
|
|
@@ -14765,9 +15221,9 @@ function update_script {
|
|
|
14765
15221
|
fi
|
|
14766
15222
|
echo -e "Install location: ${BLUE}${install_path}${ENDCOLOR}"
|
|
14767
15223
|
if [ $cmp -eq 2 ]; then
|
|
14768
|
-
echo -e "Update ${YELLOW}${current_version}${ENDCOLOR} → ${GREEN}${latest_version}${ENDCOLOR}? (
|
|
15224
|
+
echo -e "Update ${YELLOW}${current_version}${ENDCOLOR} → ${GREEN}${latest_version}${ENDCOLOR}? (Y/n)"
|
|
14769
15225
|
else
|
|
14770
|
-
echo -e "Reinstall ${GREEN}${latest_version}${ENDCOLOR} over the current binary? (
|
|
15226
|
+
echo -e "Reinstall ${GREEN}${latest_version}${ENDCOLOR} over the current binary? (Y/n)"
|
|
14771
15227
|
fi
|
|
14772
15228
|
yes_no_choice "Updating..."
|
|
14773
15229
|
local tmp_path
|
|
@@ -15321,7 +15777,7 @@ _gitb() {
|
|
|
15321
15777
|
_values 'tag mode' 'annotated[create annotated tag]' 'commit[tag a commit]' 'all[tag everything]' 'push[push tag]' 'push-all[push all tags]' 'delete[delete tag]' 'delete-all[delete all]' 'list[list tags]' 'remote[remote tags]' 'help[show help]'
|
|
15322
15778
|
;;
|
|
15323
15779
|
config|cf|cfg|conf)
|
|
15324
|
-
_values 'config key' 'default[default branch]' 'separator[separator char]' 'editor[editor]' 'ticket[ticket prefix]' 'scopes[scope list]' 'ai[AI key]' 'provider[AI provider]' 'model[AI model]' 'proxy[AI proxy]' 'history[AI history]' 'diff[AI diff payload]' 'push-size[warn on large pushes]' 'delete[unset a key]' 'user[user name/email]' 'auto[manage tab completion]' 'help[show help]'
|
|
15780
|
+
_values 'config key' 'default[default branch]' 'separator[separator char]' 'editor[editor]' 'ticket[ticket prefix]' 'scopes[scope list]' 'ai[AI setup wizard]' 'key[AI API key]' 'provider[AI provider]' 'model[AI model]' 'proxy[AI proxy]' 'history[AI history]' 'diff[AI diff payload]' 'push-size[warn on large pushes]' 'delete[unset a key]' 'user[user name/email]' 'auto[manage tab completion]' 'help[show help]'
|
|
15325
15781
|
;;
|
|
15326
15782
|
undo|un)
|
|
15327
15783
|
_values 'undo target' 'commit[undo last commit]' 'amend[undo amend]' 'merge[undo merge]' 'rebase[undo rebase]' 'stash[undo stash]' 'help[show help]'
|
|
@@ -15523,7 +15979,7 @@ complete -c gitb -n "$__gitb_branch" -a "list remote main new newd delete prev r
|
|
|
15523
15979
|
set -l __gitb_tag "__gitb_using_cmd tag t tg; and __gitb_at_position 2"
|
|
15524
15980
|
complete -c gitb -n "$__gitb_tag" -a "annotated commit all push push-all delete delete-all list remote help"
|
|
15525
15981
|
set -l __gitb_config "__gitb_using_cmd config cf cfg conf; and __gitb_at_position 2"
|
|
15526
|
-
complete -c gitb -n "$__gitb_config" -a "default separator editor ticket scopes ai provider model proxy history diff push-size delete user auto help"
|
|
15982
|
+
complete -c gitb -n "$__gitb_config" -a "default separator editor ticket scopes ai key provider model proxy history diff push-size delete user auto help"
|
|
15527
15983
|
set -l __gitb_undo "__gitb_using_cmd undo un; and __gitb_at_position 2"
|
|
15528
15984
|
complete -c gitb -n "$__gitb_undo" -a "commit amend merge rebase stash help"
|
|
15529
15985
|
set -l __gitb_reset "__gitb_using_cmd reset res; and __gitb_at_position 2"
|