gitbasher 4.0.0 → 4.1.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 +21 -0
- package/README.md +40 -3
- package/dist/gitb +637 -111
- package/package.json +1 -1
package/dist/gitb
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -o pipefail
|
|
3
|
-
GITBASHER_VERSION="4.
|
|
3
|
+
GITBASHER_VERSION="4.1.0"
|
|
4
4
|
if [ "$1" == "init" ] || [ "$1" == "i" ]; then
|
|
5
5
|
git init
|
|
6
6
|
fi
|
|
7
|
+
GITBASHER_NO_REPO=""
|
|
7
8
|
git_check=$(git branch --show-current 2>&1)
|
|
8
9
|
if [[ "$git_check" == *"fatal: not a git repository"* ]]; then
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
case "${1:-}" in
|
|
11
|
+
""|help|man|--help|-h|version|--version|-v|\
|
|
12
|
+
config|cf|cfg|conf|\
|
|
13
|
+
update|up|upd|\
|
|
14
|
+
uninstall|uns|uni|\
|
|
15
|
+
clone|cl|clo|\
|
|
16
|
+
init|i)
|
|
17
|
+
GITBASHER_NO_REPO="true"
|
|
18
|
+
export GITBASHER_NO_REPO
|
|
19
|
+
;;
|
|
20
|
+
*)
|
|
21
|
+
echo "You can use 'gitb $1' only in a git repository."
|
|
22
|
+
echo "Run 'gitb help' to see commands that work anywhere (config, update, uninstall, clone)."
|
|
23
|
+
exit 1
|
|
24
|
+
;;
|
|
25
|
+
esac
|
|
11
26
|
fi
|
|
12
27
|
if ((BASH_VERSINFO[0] < 4)); then
|
|
13
28
|
_gitb_candidate=$(command -v bash 2>/dev/null)
|
|
@@ -27,11 +42,15 @@ if ((BASH_VERSINFO[0] < 4)); then
|
|
|
27
42
|
unset _gitb_candidate
|
|
28
43
|
printf "Sorry, you need at least bash-4.0 to run gitbasher.\n\n"
|
|
29
44
|
printf "Linux (Debian/Ubuntu):\n sudo apt update && sudo apt install --only-upgrade bash\n\n"
|
|
30
|
-
printf "macOS:\n 1) Install Homebrew (if missing):\n /bin/bash -c \"
|
|
45
|
+
printf "macOS:\n 1) Install Homebrew (if missing):\n /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n 2) Install newer bash:\n brew install bash\n 3) Optional: make it your default shell (then restart Terminal):\n sudo sh -c 'echo /opt/homebrew/bin/bash >> /etc/shells' && chsh -s /opt/homebrew/bin/bash\n\n"
|
|
31
46
|
printf "Or run gitb explicitly with the newer bash when installed:\n /opt/homebrew/bin/bash $0 \"\$@\"\n\n"
|
|
32
47
|
exit 1;
|
|
33
48
|
fi
|
|
34
|
-
|
|
49
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
50
|
+
git_dir=""
|
|
51
|
+
else
|
|
52
|
+
git_dir=$(git rev-parse --git-dir 2>/dev/null)
|
|
53
|
+
fi
|
|
35
54
|
if [ -n "$git_dir" ] && [ -e "$git_dir/index.lock" ]; then
|
|
36
55
|
printf "\033[33mDetected %s/index.lock from a possibly interrupted git operation.\033[0m\n" "$git_dir"
|
|
37
56
|
printf "Another git process may be running. Remove the lock and continue?\n"
|
|
@@ -383,6 +402,11 @@ function wrong_mode {
|
|
|
383
402
|
exit
|
|
384
403
|
fi
|
|
385
404
|
}
|
|
405
|
+
function is_network_error {
|
|
406
|
+
local output="$1"
|
|
407
|
+
echo "$output" | grep -qiE \
|
|
408
|
+
"Could not read from remote repository|Could not resolve host|Connection timed out|Operation timed out|connect to host|Connection refused|Connection reset|Network is unreachable|kex_exchange_identification|remote end hung up unexpectedly|unable to access|Failed to connect|early EOF|RPC failed|timed out|ssh: connect|Temporary failure in name resolution|[Bb]roken pipe|[Rr]ecv failure|[Ss]end failure|TLS connection|SSL connection|no route to host"
|
|
409
|
+
}
|
|
386
410
|
function get_repo {
|
|
387
411
|
local remote_name=${origin_name:-origin}
|
|
388
412
|
local repo
|
|
@@ -1189,9 +1213,9 @@ ENDCOLOR="\e[0m"
|
|
|
1189
1213
|
BOLD="\033[1m"
|
|
1190
1214
|
NORMAL="\033[0m"
|
|
1191
1215
|
function get_config_value {
|
|
1192
|
-
value=$(git config --local --get "$1")
|
|
1216
|
+
value=$(git config --local --get "$1" 2>/dev/null)
|
|
1193
1217
|
if [ "$value" == "" ]; then
|
|
1194
|
-
value=$(git config --global --get "$1")
|
|
1218
|
+
value=$(git config --global --get "$1" 2>/dev/null)
|
|
1195
1219
|
if [ "$value" == "" ]; then
|
|
1196
1220
|
value=$2
|
|
1197
1221
|
fi
|
|
@@ -1199,7 +1223,7 @@ function get_config_value {
|
|
|
1199
1223
|
echo -e "$value"
|
|
1200
1224
|
}
|
|
1201
1225
|
function set_config_value {
|
|
1202
|
-
if [ -z "$3" ]; then
|
|
1226
|
+
if [ -z "$3" ] && [ "$GITBASHER_NO_REPO" != "true" ]; then
|
|
1203
1227
|
git config --local "$1" "$2"
|
|
1204
1228
|
else
|
|
1205
1229
|
git config --global "$1" "$2"
|
|
@@ -1207,6 +1231,10 @@ function set_config_value {
|
|
|
1207
1231
|
echo -e "$2"
|
|
1208
1232
|
}
|
|
1209
1233
|
function unset_config_value {
|
|
1234
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
1235
|
+
git config --global --unset "$1" 2>/dev/null
|
|
1236
|
+
return
|
|
1237
|
+
fi
|
|
1210
1238
|
git config --unset "$1"
|
|
1211
1239
|
local global_config=$(git config --global --get "$1" 2>/dev/null)
|
|
1212
1240
|
if [ -n "$global_config" ]; then
|
|
@@ -1238,9 +1266,16 @@ function validate_git_url {
|
|
|
1238
1266
|
fi
|
|
1239
1267
|
return 1
|
|
1240
1268
|
}
|
|
1269
|
+
#
|
|
1270
|
+
_gitb_init_run_queries="true"
|
|
1241
1271
|
if [ -n "$GITBASHER_SKIP_INIT_QUERIES" ]; then
|
|
1272
|
+
_gitb_init_run_queries=""
|
|
1242
1273
|
return 0
|
|
1243
1274
|
fi
|
|
1275
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
1276
|
+
_gitb_init_run_queries=""
|
|
1277
|
+
fi
|
|
1278
|
+
if [ -n "$_gitb_init_run_queries" ]; then
|
|
1244
1279
|
current_branch=$(git branch --show-current)
|
|
1245
1280
|
main_branch=$(get_config_value gitbasher.branch "main")
|
|
1246
1281
|
if [[ "$( git branch | grep -E "^[[:space:]*]*main[[:space:]]*$" )" == "" ]] && [[ "$( git branch | grep -E "^[[:space:]*]*master[[:space:]]*$" )" != "" ]]; then
|
|
@@ -1252,7 +1287,7 @@ if [ "$(get_config_value gitbasher.branch "")" == "" ]; then
|
|
|
1252
1287
|
git config --local gitbasher.branch "$main_branch"
|
|
1253
1288
|
fi
|
|
1254
1289
|
origin_name=$(git remote -v | head -n 1 | sed 's/\t.*//')
|
|
1255
|
-
if [ "$origin_name" == "" ]; then
|
|
1290
|
+
if [ "$origin_name" == "" ]; then
|
|
1256
1291
|
if [ -z "$GITBASHER_TEST_MODE" ] && [ -t 0 ]; then
|
|
1257
1292
|
echo -e "${YELLOW}⚠ No remote is configured in this repo.${ENDCOLOR}"
|
|
1258
1293
|
echo
|
|
@@ -1308,6 +1343,24 @@ if [ -n "$scopes" ]; then
|
|
|
1308
1343
|
fi
|
|
1309
1344
|
is_first=$(get_config_value gitbasher.isfirst "true")
|
|
1310
1345
|
git config --local gitbasher.isfirst "false"
|
|
1346
|
+
else
|
|
1347
|
+
current_branch=""
|
|
1348
|
+
main_branch=$(get_config_value gitbasher.branch "main")
|
|
1349
|
+
origin_name=""
|
|
1350
|
+
sep=$(get_config_value gitbasher.sep "-")
|
|
1351
|
+
editor=$(get_config_value core.editor "vi")
|
|
1352
|
+
ticket_name=$(get_config_value gitbasher.ticket "")
|
|
1353
|
+
scopes=$(get_config_value gitbasher.scopes "")
|
|
1354
|
+
if [ -n "$scopes" ]; then
|
|
1355
|
+
if ! validate_scope_list "$scopes" >/dev/null 2>&1; then
|
|
1356
|
+
scopes=""
|
|
1357
|
+
else
|
|
1358
|
+
scopes="$validated_scopes"
|
|
1359
|
+
fi
|
|
1360
|
+
fi
|
|
1361
|
+
is_first="false"
|
|
1362
|
+
fi
|
|
1363
|
+
unset _gitb_init_run_queries
|
|
1311
1364
|
readonly OPENROUTER_API_URL="https://openrouter.ai/api/v1/chat/completions"
|
|
1312
1365
|
readonly OPENAI_API_URL="https://api.openai.com/v1/chat/completions"
|
|
1313
1366
|
readonly OLLAMA_API_URL="http://localhost:11434/v1/chat/completions"
|
|
@@ -2131,6 +2184,11 @@ function clear_sensitive_vars {
|
|
|
2131
2184
|
fi
|
|
2132
2185
|
}
|
|
2133
2186
|
function set_default_branch {
|
|
2187
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
2188
|
+
echo -e "${RED}✗ Cannot pick a default branch outside a git repository.${ENDCOLOR}" >&2
|
|
2189
|
+
echo -e "Run this inside a repo, or set one directly with ${GREEN}git config --global gitbasher.branch <name>${ENDCOLOR}." >&2
|
|
2190
|
+
exit 1
|
|
2191
|
+
fi
|
|
2134
2192
|
echo -e "${YELLOW}Fetching remote branches...${ENDCOLOR}"
|
|
2135
2193
|
echo
|
|
2136
2194
|
fetch_output=$(git fetch 2>&1)
|
|
@@ -2144,6 +2202,7 @@ function set_default_branch {
|
|
|
2144
2202
|
main_branch=$(set_config_value gitbasher.branch $branch_name)
|
|
2145
2203
|
echo -e "${GREEN}✓ Set '${branch_name}' as the default gitbasher branch in '${project_name}'${ENDCOLOR}"
|
|
2146
2204
|
echo
|
|
2205
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2147
2206
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2148
2207
|
yes_no_choice "\nSet '${branch_name}' globally" "true"
|
|
2149
2208
|
main_branch=$(set_config_value gitbasher.branch $branch_name "true")
|
|
@@ -2190,6 +2249,7 @@ function set_sep {
|
|
|
2190
2249
|
sep=$(set_config_value gitbasher.sep $new_sep)
|
|
2191
2250
|
echo -e "${GREEN}✓ Set '${sep}' as the branch name separator in '${project_name}'${ENDCOLOR}"
|
|
2192
2251
|
echo
|
|
2252
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2193
2253
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2194
2254
|
yes_no_choice "\nSet '${sep}' globally" "true"
|
|
2195
2255
|
sep=$(set_config_value gitbasher.sep "$new_sep" "true")
|
|
@@ -2218,6 +2278,7 @@ function set_editor {
|
|
|
2218
2278
|
editor=$(set_config_value core.editor $choice)
|
|
2219
2279
|
echo -e "${GREEN}✓ Using editor '$editor' (${which_output})${ENDCOLOR}"
|
|
2220
2280
|
echo
|
|
2281
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2221
2282
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2222
2283
|
yes_no_choice "\nSet '${editor}' globally" "true"
|
|
2223
2284
|
editor=$(set_config_value core.editor "$choice" "true")
|
|
@@ -2250,6 +2311,7 @@ function set_ticket {
|
|
|
2250
2311
|
ticket_name=$(set_config_value gitbasher.ticket $ticket_name)
|
|
2251
2312
|
echo -e "${GREEN}✓ Set '${ticket_name}' as the ticket prefix in '${project_name}'${ENDCOLOR}"
|
|
2252
2313
|
echo
|
|
2314
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2253
2315
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2254
2316
|
yes_no_choice "\nSet '${ticket_name}' globally" "true"
|
|
2255
2317
|
ticket_name=$(set_config_value gitbasher.ticket $ticket_name "true")
|
|
@@ -2338,6 +2400,7 @@ function configure_ai_key {
|
|
|
2338
2400
|
ai_api_key=$(set_ai_api_key "$ai_key_input")
|
|
2339
2401
|
echo -e "${GREEN}✓ Configured AI API key for '${provider}' in '${project_name}':${ENDCOLOR} ${BLUE}$(mask_api_key "$ai_api_key")${ENDCOLOR}"
|
|
2340
2402
|
echo
|
|
2403
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2341
2404
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2342
2405
|
echo -e "${RED}⚠ Global API keys are stored in plaintext in ~/.gitconfig.${ENDCOLOR}"
|
|
2343
2406
|
echo -e "${CYAN}💡 For better security, set ${BLUE}GITB_AI_API_KEY_${provider_upper}${CYAN} as an environment variable instead.${ENDCOLOR}"
|
|
@@ -2391,9 +2454,11 @@ function configure_ai_provider {
|
|
|
2391
2454
|
;;
|
|
2392
2455
|
esac
|
|
2393
2456
|
echo
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2457
|
+
if [ "$GITBASHER_NO_REPO" != "true" ]; then
|
|
2458
|
+
echo -e "Do you want to set this provider ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2459
|
+
yes_no_choice "\nSet AI provider globally" "true"
|
|
2460
|
+
set_config_value gitbasher.ai-provider "$new_provider" "true"
|
|
2461
|
+
fi
|
|
2397
2462
|
if ai_provider_requires_api_key && [ -z "$(get_ai_api_key)" ]; then
|
|
2398
2463
|
echo
|
|
2399
2464
|
echo -e "${YELLOW}No API key is set for '${new_provider}'.${ENDCOLOR}"
|
|
@@ -2470,6 +2535,7 @@ function configure_ai_model {
|
|
|
2470
2535
|
set_ai_model "$model_input"
|
|
2471
2536
|
echo -e "${GREEN}✓ Set AI model to '$model_input' for '${project_name}'${ENDCOLOR}"
|
|
2472
2537
|
echo
|
|
2538
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2473
2539
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2474
2540
|
yes_no_choice "\nSet AI model globally" "true"
|
|
2475
2541
|
set_config_value gitbasher.ai-model "$model_input" "true"
|
|
@@ -2517,6 +2583,7 @@ function configure_ai_proxy {
|
|
|
2517
2583
|
echo -e " ${BLUE}gitb commit ai${ENDCOLOR} - Generate commit with AI through proxy"
|
|
2518
2584
|
echo -e " ${BLUE}gitb commit aif${ENDCOLOR} - Fast AI commit through proxy"
|
|
2519
2585
|
echo
|
|
2586
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2520
2587
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2521
2588
|
yes_no_choice "\nSet AI proxy globally" "true"
|
|
2522
2589
|
set_ai_proxy "$ai_proxy_input"
|
|
@@ -2560,6 +2627,7 @@ function configure_ai_history {
|
|
|
2560
2627
|
set_ai_commit_history_limit "$limit_input"
|
|
2561
2628
|
echo -e "${GREEN}✓ Set AI commit history limit to ${limit_input} for '${project_name}'${ENDCOLOR}"
|
|
2562
2629
|
echo
|
|
2630
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2563
2631
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2564
2632
|
yes_no_choice "\nSet AI commit history limit globally" "true"
|
|
2565
2633
|
git config --global gitbasher.ai-commit-history-limit "$limit_input"
|
|
@@ -2621,6 +2689,7 @@ function configure_ai_diff {
|
|
|
2621
2689
|
exit
|
|
2622
2690
|
fi
|
|
2623
2691
|
echo
|
|
2692
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2624
2693
|
echo -e "Do you want to apply ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2625
2694
|
yes_no_choice "\nApply AI diff settings globally" "true"
|
|
2626
2695
|
if [ -n "$lines_input" ]; then
|
|
@@ -2658,10 +2727,17 @@ function set_scopes {
|
|
|
2658
2727
|
exit 1
|
|
2659
2728
|
fi
|
|
2660
2729
|
scopes_raw="$validated_scopes"
|
|
2730
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
2731
|
+
git config --global --replace-all gitbasher.scopes "$scopes_raw"
|
|
2732
|
+
scopes="$scopes_raw"
|
|
2733
|
+
echo -e "${GREEN}✓ Set '${scopes}' as the global scopes list${ENDCOLOR}"
|
|
2734
|
+
return
|
|
2735
|
+
fi
|
|
2661
2736
|
git config --local --replace-all gitbasher.scopes "$scopes_raw"
|
|
2662
2737
|
scopes="$scopes_raw"
|
|
2663
2738
|
echo -e "${GREEN}✓ Set '${scopes}' as the scopes list in '${project_name}'${ENDCOLOR}"
|
|
2664
2739
|
echo
|
|
2740
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
2665
2741
|
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (y/n)?"
|
|
2666
2742
|
yes_no_choice "\nSet '${scopes}' globally" "true"
|
|
2667
2743
|
git config --global --replace-all gitbasher.scopes "$scopes_raw"
|
|
@@ -2772,13 +2848,17 @@ function set_user {
|
|
|
2772
2848
|
user_email="$validated_email"
|
|
2773
2849
|
fi
|
|
2774
2850
|
echo
|
|
2851
|
+
local _user_scope="--local"
|
|
2852
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
2853
|
+
_user_scope="--global"
|
|
2854
|
+
fi
|
|
2775
2855
|
if [ "$user_name" != "" ]; then
|
|
2776
2856
|
echo -e "${GREEN}✓ Set user name to '${user_name}'${ENDCOLOR}"
|
|
2777
|
-
git config
|
|
2857
|
+
git config "$_user_scope" --replace-all user.name "$user_name"
|
|
2778
2858
|
fi
|
|
2779
2859
|
if [ "$user_email" != "" ]; then
|
|
2780
2860
|
echo -e "${GREEN}✓ Set user email to '${user_email}'${ENDCOLOR}"
|
|
2781
|
-
git config
|
|
2861
|
+
git config "$_user_scope" --replace-all user.email "$user_email"
|
|
2782
2862
|
fi
|
|
2783
2863
|
}
|
|
2784
2864
|
function config_script {
|
|
@@ -5041,7 +5121,7 @@ function pull_script {
|
|
|
5041
5121
|
else
|
|
5042
5122
|
echo -e "${GREEN}✓ Fetched '$origin_name/$current_branch'${ENDCOLOR}"
|
|
5043
5123
|
fi
|
|
5044
|
-
if [ "$fetch_output" != "" ]; then
|
|
5124
|
+
if [ "$fetch_output" != "" ] && [ -z "$fetch_progress_shown" ]; then
|
|
5045
5125
|
echo
|
|
5046
5126
|
echo -e "$fetch_output"
|
|
5047
5127
|
fi
|
|
@@ -5087,19 +5167,40 @@ function pull_script {
|
|
|
5087
5167
|
exit
|
|
5088
5168
|
}
|
|
5089
5169
|
function fetch {
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5170
|
+
fetch_progress_shown=""
|
|
5171
|
+
if [ -t 1 ] && [ -t 2 ]; then
|
|
5172
|
+
local tmp_file
|
|
5173
|
+
tmp_file=$(mktemp 2>/dev/null)
|
|
5174
|
+
if [ -n "$tmp_file" ]; then
|
|
5175
|
+
if [ -n "$3" ]; then
|
|
5176
|
+
git fetch --all --progress 2>&1 | tee "$tmp_file"
|
|
5177
|
+
fetch_code=${PIPESTATUS[0]}
|
|
5178
|
+
else
|
|
5179
|
+
git fetch --progress "$2" "$1" 2>&1 | tee "$tmp_file"
|
|
5180
|
+
fetch_code=${PIPESTATUS[0]}
|
|
5181
|
+
fi
|
|
5182
|
+
fetch_output=$(cat "$tmp_file")
|
|
5183
|
+
rm -f "$tmp_file"
|
|
5184
|
+
fetch_progress_shown="true"
|
|
5185
|
+
fi
|
|
5186
|
+
fi
|
|
5187
|
+
if [ -z "$fetch_progress_shown" ]; then
|
|
5188
|
+
if [ -n "$3" ]; then
|
|
5189
|
+
fetch_output=$(git fetch --all 2>&1)
|
|
5190
|
+
fetch_code=$?
|
|
5191
|
+
else
|
|
5192
|
+
fetch_output=$(git fetch "$2" "$1" 2>&1)
|
|
5193
|
+
fetch_code=$?
|
|
5194
|
+
fi
|
|
5096
5195
|
fi
|
|
5097
5196
|
if [ $fetch_code == 0 ] ; then
|
|
5098
5197
|
return
|
|
5099
5198
|
fi
|
|
5100
5199
|
if [[ ${fetch_output} != *"couldn't find remote ref"* ]]; then
|
|
5101
5200
|
echo -e "${RED}✗ Cannot fetch '$1'.${ENDCOLOR}"
|
|
5102
|
-
|
|
5201
|
+
if [ -z "$fetch_progress_shown" ]; then
|
|
5202
|
+
echo -e "${fetch_output}"
|
|
5203
|
+
fi
|
|
5103
5204
|
exit $fetch_code
|
|
5104
5205
|
fi
|
|
5105
5206
|
echo -e "${YELLOW}Branch '$1' does not exist on $2.${ENDCOLOR}"
|
|
@@ -5190,65 +5291,84 @@ function pull {
|
|
|
5190
5291
|
fi
|
|
5191
5292
|
}
|
|
5192
5293
|
function push {
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
print_link "New PR" "$link"
|
|
5216
|
-
else
|
|
5217
|
-
new_pr_url=$(get_new_pr_url "${main_branch}" "${current_branch}" "$repo")
|
|
5218
|
-
print_link "New PR" "$new_pr_url"
|
|
5294
|
+
local push_max_retries=4
|
|
5295
|
+
local push_retry_delay=2
|
|
5296
|
+
local push_attempt=0
|
|
5297
|
+
while true; do
|
|
5298
|
+
push_output=$(git push "$@" "${origin_name}" "${current_branch}" 2>&1)
|
|
5299
|
+
push_code=$?
|
|
5300
|
+
if [ $push_code -eq 0 ] ; then
|
|
5301
|
+
echo -e "${GREEN}✓ Pushed to ${origin_name}/${current_branch}${ENDCOLOR}"
|
|
5302
|
+
repo=$(get_repo)
|
|
5303
|
+
host=$(get_repo_host "$repo")
|
|
5304
|
+
head_hash=$(git rev-parse HEAD 2>/dev/null)
|
|
5305
|
+
print_link "Repo" "$repo"
|
|
5306
|
+
if [ -n "$head_hash" ]; then
|
|
5307
|
+
commit_url=$(get_commit_url "$head_hash" "$repo")
|
|
5308
|
+
if [ -n "$commit_url" ]; then
|
|
5309
|
+
print_link "Commit" "$commit_url"
|
|
5310
|
+
fi
|
|
5311
|
+
fi
|
|
5312
|
+
if [[ ${current_branch} != ${main_branch} ]]; then
|
|
5313
|
+
branch_url=$(get_branch_url "${current_branch}" "$repo")
|
|
5314
|
+
if [ -n "$branch_url" ]; then
|
|
5315
|
+
print_link "Branch" "$branch_url"
|
|
5219
5316
|
fi
|
|
5220
|
-
|
|
5221
|
-
if [
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
print_link "New MR" "$link"
|
|
5317
|
+
link=$(echo "$push_output" | grep -Eo "https?://[^[:space:]]+" | head -n 1 | sed 's|^remote:[[:space:]]*||')
|
|
5318
|
+
if [ "$host" = "github" ]; then
|
|
5319
|
+
if [ -n "$link" ]; then
|
|
5320
|
+
print_link "New PR" "$link"
|
|
5225
5321
|
else
|
|
5226
|
-
|
|
5322
|
+
new_pr_url=$(get_new_pr_url "${main_branch}" "${current_branch}" "$repo")
|
|
5323
|
+
print_link "New PR" "$new_pr_url"
|
|
5324
|
+
fi
|
|
5325
|
+
elif [ "$host" = "gitlab" ]; then
|
|
5326
|
+
if [ -n "$link" ]; then
|
|
5327
|
+
is_new=$(echo "$push_output" | grep -i "create a merge request")
|
|
5328
|
+
if [ -n "$is_new" ]; then
|
|
5329
|
+
print_link "New MR" "$link"
|
|
5330
|
+
else
|
|
5331
|
+
print_link "MR" "$link"
|
|
5332
|
+
fi
|
|
5333
|
+
else
|
|
5334
|
+
new_mr_url=$(get_new_pr_url "${main_branch}" "${current_branch}" "$repo")
|
|
5335
|
+
print_link "New MR" "$new_mr_url"
|
|
5336
|
+
fi
|
|
5337
|
+
elif [ "$host" = "bitbucket" ]; then
|
|
5338
|
+
if [ -n "$link" ]; then
|
|
5339
|
+
print_link "New PR" "$link"
|
|
5340
|
+
else
|
|
5341
|
+
new_pr_url=$(get_new_pr_url "${main_branch}" "${current_branch}" "$repo")
|
|
5342
|
+
print_link "New PR" "$new_pr_url"
|
|
5227
5343
|
fi
|
|
5228
|
-
else
|
|
5229
|
-
new_mr_url=$(get_new_pr_url "${main_branch}" "${current_branch}" "$repo")
|
|
5230
|
-
print_link "New MR" "$new_mr_url"
|
|
5231
|
-
fi
|
|
5232
|
-
elif [ "$host" = "bitbucket" ]; then
|
|
5233
|
-
if [ -n "$link" ]; then
|
|
5234
|
-
print_link "New PR" "$link"
|
|
5235
|
-
else
|
|
5236
|
-
new_pr_url=$(get_new_pr_url "${main_branch}" "${current_branch}" "$repo")
|
|
5237
|
-
print_link "New PR" "$new_pr_url"
|
|
5238
5344
|
fi
|
|
5239
5345
|
fi
|
|
5346
|
+
ci_url=$(get_ci_url "${current_branch}" "$repo")
|
|
5347
|
+
if [ -n "$ci_url" ]; then
|
|
5348
|
+
print_link "$(get_ci_label "$repo")" "$ci_url"
|
|
5349
|
+
fi
|
|
5350
|
+
exit
|
|
5240
5351
|
fi
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5352
|
+
if [[ $push_output == *"[rejected]"* ]]; then
|
|
5353
|
+
return
|
|
5354
|
+
fi
|
|
5355
|
+
if is_network_error "$push_output" && [ $push_attempt -lt $push_max_retries ]; then
|
|
5356
|
+
push_attempt=$((push_attempt + 1))
|
|
5357
|
+
echo -e "${YELLOW}⚠ Network error while pushing — retrying in ${push_retry_delay}s (attempt ${push_attempt}/${push_max_retries})...${ENDCOLOR}"
|
|
5358
|
+
echo "$push_output" | head -n 2
|
|
5359
|
+
echo
|
|
5360
|
+
sleep $push_retry_delay
|
|
5361
|
+
push_retry_delay=$((push_retry_delay * 2))
|
|
5362
|
+
continue
|
|
5244
5363
|
fi
|
|
5245
|
-
exit
|
|
5246
|
-
fi
|
|
5247
|
-
if [[ $push_output != *"[rejected]"* ]]; then
|
|
5248
5364
|
echo -e "${RED}✗ Cannot push.${ENDCOLOR}"
|
|
5249
5365
|
echo "$push_output"
|
|
5366
|
+
if [ $push_attempt -gt 0 ]; then
|
|
5367
|
+
echo
|
|
5368
|
+
echo -e "${YELLOW}⚠ Still failing after ${push_attempt} retries — check your network/VPN, then run ${GREEN}gitb push${YELLOW} again.${ENDCOLOR}"
|
|
5369
|
+
fi
|
|
5250
5370
|
exit $push_code
|
|
5251
|
-
|
|
5371
|
+
done
|
|
5252
5372
|
}
|
|
5253
5373
|
function push_script {
|
|
5254
5374
|
case "$1" in
|
|
@@ -5502,11 +5622,16 @@ function detect_scopes_from_staged_files {
|
|
|
5502
5622
|
detected_scopes_sorted=("${filename_sorted[@]}" "${directory_sorted[@]}")
|
|
5503
5623
|
final_scopes=()
|
|
5504
5624
|
count=0
|
|
5625
|
+
declare -A seen_norm=()
|
|
5505
5626
|
for entry in "${detected_scopes_sorted[@]}"; do
|
|
5506
5627
|
if [ $count -ge 9 ]; then
|
|
5507
5628
|
break
|
|
5508
5629
|
fi
|
|
5509
5630
|
token="${entry##*:}" # Extract token after last colon
|
|
5631
|
+
token=$(normalize_scope_name "$token")
|
|
5632
|
+
[ -z "$token" ] && continue
|
|
5633
|
+
[ -n "${seen_norm[$token]:-}" ] && continue
|
|
5634
|
+
seen_norm["$token"]=1
|
|
5510
5635
|
final_scopes+=("$token")
|
|
5511
5636
|
count=$((count + 1))
|
|
5512
5637
|
done
|
|
@@ -5514,6 +5639,12 @@ function detect_scopes_from_staged_files {
|
|
|
5514
5639
|
fi
|
|
5515
5640
|
fi
|
|
5516
5641
|
}
|
|
5642
|
+
function normalize_scope_name {
|
|
5643
|
+
local s="$1"
|
|
5644
|
+
while [[ "$s" == [._-]* ]]; do s="${s#?}"; done
|
|
5645
|
+
while [[ "$s" == *[._-] ]]; do s="${s%?}"; done
|
|
5646
|
+
printf '%s' "$s"
|
|
5647
|
+
}
|
|
5517
5648
|
function build_split_groups_from_staged {
|
|
5518
5649
|
detect_scopes_from_staged_files
|
|
5519
5650
|
unset split_groups split_group_keys
|
|
@@ -5542,6 +5673,8 @@ function build_split_groups_from_staged {
|
|
|
5542
5673
|
comp_lower="${comp,,}"
|
|
5543
5674
|
comp_no_ext_lower="${comp_lower%.*}"
|
|
5544
5675
|
[ -z "$comp_no_ext_lower" ] && comp_no_ext_lower="$comp_lower"
|
|
5676
|
+
comp_lower=$(normalize_scope_name "$comp_lower")
|
|
5677
|
+
comp_no_ext_lower=$(normalize_scope_name "$comp_no_ext_lower")
|
|
5545
5678
|
for scope in "${scopes_arr[@]}"; do
|
|
5546
5679
|
if [ "$comp_lower" = "$scope" ] || [ "$comp_no_ext_lower" = "$scope" ]; then
|
|
5547
5680
|
assigned="$scope"
|
|
@@ -5553,6 +5686,7 @@ function build_split_groups_from_staged {
|
|
|
5553
5686
|
local stem="${comps[$last_idx_inner]}"
|
|
5554
5687
|
stem="${stem%.*}" # drop the extension
|
|
5555
5688
|
stem="${stem,,}" # lowercase
|
|
5689
|
+
stem=$(normalize_scope_name "$stem")
|
|
5556
5690
|
if [ -n "$stem" ] && [[ ! "$stem" =~ ^(index|main|mod|init|app|__init__|setup|config|types|utils|util|helpers|helper|common|shared)$ ]]; then
|
|
5557
5691
|
assigned="$stem"
|
|
5558
5692
|
fi
|
|
@@ -5565,11 +5699,102 @@ function build_split_groups_from_staged {
|
|
|
5565
5699
|
split_groups[$assigned]+=$'\n'"$file"
|
|
5566
5700
|
fi
|
|
5567
5701
|
done <<< "$staged_files"
|
|
5702
|
+
consolidate_split_groups "$(get_max_split_groups)"
|
|
5568
5703
|
if [ ${#split_group_keys[@]} -lt 2 ]; then
|
|
5569
5704
|
return 1
|
|
5570
5705
|
fi
|
|
5571
5706
|
return 0
|
|
5572
5707
|
}
|
|
5708
|
+
function get_max_split_groups {
|
|
5709
|
+
local v
|
|
5710
|
+
v=$(get_config_value gitbasher.commit-max-split-groups "7")
|
|
5711
|
+
case "$v" in
|
|
5712
|
+
''|*[!0-9]*) v=7 ;;
|
|
5713
|
+
esac
|
|
5714
|
+
[ "$v" -lt 2 ] && v=2
|
|
5715
|
+
[ "$v" -gt 20 ] && v=20
|
|
5716
|
+
printf '%s' "$v"
|
|
5717
|
+
}
|
|
5718
|
+
function consolidate_split_groups {
|
|
5719
|
+
local max="$1"
|
|
5720
|
+
case "$max" in ''|*[!0-9]*) max=7 ;; esac
|
|
5721
|
+
[ "$max" -lt 2 ] && max=2
|
|
5722
|
+
[ ${#split_group_keys[@]} -le "$max" ] && return 0
|
|
5723
|
+
local -A rebucket=()
|
|
5724
|
+
local -a rebucket_keys=()
|
|
5725
|
+
local key file bucket comp_lower i last_idx
|
|
5726
|
+
local -a comps
|
|
5727
|
+
for key in "${split_group_keys[@]}"; do
|
|
5728
|
+
while IFS= read -r file; do
|
|
5729
|
+
[ -z "$file" ] && continue
|
|
5730
|
+
IFS='/' read -r -a comps <<< "$file"
|
|
5731
|
+
last_idx=$((${#comps[@]} - 1))
|
|
5732
|
+
bucket=""
|
|
5733
|
+
for i in "${!comps[@]}"; do
|
|
5734
|
+
[ "$i" -eq "$last_idx" ] && continue # skip the filename
|
|
5735
|
+
comp_lower="${comps[$i],,}"
|
|
5736
|
+
[ -z "$comp_lower" ] && continue
|
|
5737
|
+
if [[ "$comp_lower" =~ ^(src|lib|libs|scripts|bin|node_modules|vendor|tmp|temp|cache|logs|log|services|apps|packages|modules|components|cmd|internal)$ ]]; then
|
|
5738
|
+
continue
|
|
5739
|
+
fi
|
|
5740
|
+
bucket=$(normalize_scope_name "$comp_lower") # .github -> github
|
|
5741
|
+
[ -z "$bucket" ] && continue
|
|
5742
|
+
break
|
|
5743
|
+
done
|
|
5744
|
+
[ -z "$bucket" ] && bucket="misc"
|
|
5745
|
+
if [ -z "${rebucket[$bucket]:-}" ]; then
|
|
5746
|
+
rebucket_keys+=("$bucket")
|
|
5747
|
+
rebucket[$bucket]="$file"
|
|
5748
|
+
else
|
|
5749
|
+
rebucket[$bucket]+=$'\n'"$file"
|
|
5750
|
+
fi
|
|
5751
|
+
done <<< "${split_groups[$key]}"
|
|
5752
|
+
done
|
|
5753
|
+
unset split_groups split_group_keys
|
|
5754
|
+
declare -gA split_groups=()
|
|
5755
|
+
split_group_keys=()
|
|
5756
|
+
for key in "${rebucket_keys[@]}"; do
|
|
5757
|
+
split_groups[$key]="${rebucket[$key]}"
|
|
5758
|
+
split_group_keys+=("$key")
|
|
5759
|
+
done
|
|
5760
|
+
[ ${#split_group_keys[@]} -le "$max" ] && return 0
|
|
5761
|
+
local -a sized=()
|
|
5762
|
+
local n
|
|
5763
|
+
for key in "${split_group_keys[@]}"; do
|
|
5764
|
+
n=$(printf '%s\n' "${split_groups[$key]}" | grep -c .)
|
|
5765
|
+
sized+=("${n}"$'\t'"${key}")
|
|
5766
|
+
done
|
|
5767
|
+
IFS=$'\n' sized=($(printf '%s\n' "${sized[@]}" | sort -t$'\t' -k1,1nr -k2,2))
|
|
5768
|
+
unset IFS
|
|
5769
|
+
local -A kept=()
|
|
5770
|
+
local -a kept_keys=()
|
|
5771
|
+
local keep_limit=$((max - 1))
|
|
5772
|
+
local misc_files="" idx=0 entry
|
|
5773
|
+
for entry in "${sized[@]}"; do
|
|
5774
|
+
key="${entry#*$'\t'}"
|
|
5775
|
+
if [ "$idx" -lt "$keep_limit" ] && [ "$key" != "misc" ]; then
|
|
5776
|
+
kept_keys+=("$key")
|
|
5777
|
+
kept[$key]="${split_groups[$key]}"
|
|
5778
|
+
idx=$((idx + 1))
|
|
5779
|
+
elif [ -z "$misc_files" ]; then
|
|
5780
|
+
misc_files="${split_groups[$key]}"
|
|
5781
|
+
else
|
|
5782
|
+
misc_files+=$'\n'"${split_groups[$key]}"
|
|
5783
|
+
fi
|
|
5784
|
+
done
|
|
5785
|
+
unset split_groups split_group_keys
|
|
5786
|
+
declare -gA split_groups=()
|
|
5787
|
+
split_group_keys=()
|
|
5788
|
+
for key in "${kept_keys[@]}"; do
|
|
5789
|
+
split_groups[$key]="${kept[$key]}"
|
|
5790
|
+
split_group_keys+=("$key")
|
|
5791
|
+
done
|
|
5792
|
+
if [ -n "$misc_files" ]; then
|
|
5793
|
+
split_group_keys+=("misc")
|
|
5794
|
+
split_groups[misc]="$misc_files"
|
|
5795
|
+
fi
|
|
5796
|
+
return 0
|
|
5797
|
+
}
|
|
5573
5798
|
function is_heuristic_weak {
|
|
5574
5799
|
local total
|
|
5575
5800
|
total=$(git -c core.quotePath=false diff --name-only --cached | grep -c .)
|
|
@@ -5601,10 +5826,12 @@ function refine_groups_with_ai {
|
|
|
5601
5826
|
if [ -n "$detected_scopes" ]; then
|
|
5602
5827
|
heuristic_hint="$detected_scopes"
|
|
5603
5828
|
fi
|
|
5604
|
-
local
|
|
5829
|
+
local max_groups
|
|
5830
|
+
max_groups=$(get_max_split_groups)
|
|
5831
|
+
local system_prompt="You group staged git files into logical scopes for atomic commits.
|
|
5605
5832
|
Input arrives in XML tags: a list of staged files, a diff summary, recent commit messages (for codebase scope conventions), and heuristic-detected candidate scope tokens.
|
|
5606
|
-
Task: assign EVERY staged file to a single scope. Use 2
|
|
5607
|
-
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>.
|
|
5833
|
+
Task: assign EVERY staged file to a single scope. Use 2 to ${max_groups} groups when there is meaningful separation; use exactly 1 group only if all files truly belong together. Never exceed ${max_groups} groups — when many files share a theme, prefer a single broader scope over several tiny ones. Use lowercase scope names that match the style of <recent_commits>. Prefer scope names from <heuristic_candidates> when they fit the file path or change; invent new ones only when none of the candidates apply. Use \"misc\" for files with no clear scope (e.g., README, top-level config).
|
|
5834
|
+
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>."
|
|
5608
5835
|
local user_prompt="<recent_commits>
|
|
5609
5836
|
${recent_commits}
|
|
5610
5837
|
</recent_commits>
|
|
@@ -6093,6 +6320,7 @@ function try_offer_commit_split {
|
|
|
6093
6320
|
if ! refine_groups_with_ai; then
|
|
6094
6321
|
echo -e "${YELLOW}AI grouping failed, falling back to heuristic.${ENDCOLOR}"
|
|
6095
6322
|
fi
|
|
6323
|
+
consolidate_split_groups "$(get_max_split_groups)"
|
|
6096
6324
|
fi
|
|
6097
6325
|
if [ ${#split_group_keys[@]} -lt 2 ]; then
|
|
6098
6326
|
if [ -n "$force_split" ]; then
|
|
@@ -6286,8 +6514,8 @@ function set_commit_flag_from_token {
|
|
|
6286
6514
|
fixup|fix|x) fixup="true";;
|
|
6287
6515
|
amend|am|a) amend="true";;
|
|
6288
6516
|
split|sp|sl) split="true";;
|
|
6289
|
-
last|l) last="true";;
|
|
6290
6517
|
revert|rev) revert="true";;
|
|
6518
|
+
last|l) last="true";; # deprecated alias of `gitb edit`; kept for back-compat
|
|
6291
6519
|
help|h) help="true";;
|
|
6292
6520
|
*) return 1;;
|
|
6293
6521
|
esac
|
|
@@ -6295,30 +6523,26 @@ function set_commit_flag_from_token {
|
|
|
6295
6523
|
}
|
|
6296
6524
|
function validate_commit_flag_combo {
|
|
6297
6525
|
local actions=() invalid=()
|
|
6298
|
-
[ -n "$last" ] && actions+=("last")
|
|
6299
6526
|
[ -n "$revert" ] && actions+=("revert")
|
|
6300
6527
|
[ -n "$fixup" ] && actions+=("fixup")
|
|
6301
6528
|
[ -n "$amend" ] && actions+=("amend")
|
|
6302
6529
|
[ -n "$split" ] && actions+=("split")
|
|
6303
6530
|
if [ ${#actions[@]} -gt 1 ]; then
|
|
6304
6531
|
echo -e "${RED}✗ Cannot combine actions: ${actions[*]}${ENDCOLOR}"
|
|
6305
|
-
echo -e "Pick one of:
|
|
6532
|
+
echo -e "Pick one of: revert, fixup, amend, split."
|
|
6306
6533
|
exit 1
|
|
6307
6534
|
fi
|
|
6308
|
-
|
|
6309
|
-
[ -n "$last" ] && lone="last"
|
|
6310
|
-
[ -n "$revert" ] && lone="revert"
|
|
6311
|
-
if [ -n "$lone" ]; then
|
|
6535
|
+
if [ -n "$revert" ]; then
|
|
6312
6536
|
[ -n "$llm" ] && invalid+=("ai")
|
|
6313
6537
|
[ -n "$fast" ] && invalid+=("fast")
|
|
6314
|
-
[ -n "$push" ] && invalid+=("push")
|
|
6315
6538
|
[ -n "$scope" ] && invalid+=("scope")
|
|
6316
6539
|
[ -n "$msg" ] && invalid+=("msg")
|
|
6317
6540
|
[ -n "$ticket" ] && invalid+=("ticket")
|
|
6318
6541
|
[ -n "$staged" ] && invalid+=("staged")
|
|
6319
6542
|
[ -n "$no_split" ] && invalid+=("no-split")
|
|
6320
6543
|
if [ ${#invalid[@]} -gt 0 ]; then
|
|
6321
|
-
echo -e "${RED}✗ '
|
|
6544
|
+
echo -e "${RED}✗ 'revert' does not use: ${invalid[*]}${ENDCOLOR}"
|
|
6545
|
+
echo -e "Only push applies (the revert commit message is auto-generated)."
|
|
6322
6546
|
exit 1
|
|
6323
6547
|
fi
|
|
6324
6548
|
fi
|
|
@@ -6347,8 +6571,6 @@ function summarize_commit_intent {
|
|
|
6347
6571
|
local action="commit" mods=()
|
|
6348
6572
|
if [ -n "$auto_accept" ]; then
|
|
6349
6573
|
action="ultrafast commit (ai + fast + auto-accept)"
|
|
6350
|
-
elif [ -n "$last" ]; then
|
|
6351
|
-
action="amend last commit (reuse message)"
|
|
6352
6574
|
elif [ -n "$revert" ]; then
|
|
6353
6575
|
action="revert a commit"
|
|
6354
6576
|
elif [ -n "$amend" ]; then
|
|
@@ -6403,8 +6625,9 @@ function commit_script {
|
|
|
6403
6625
|
amend|am|a) amend="true";;
|
|
6404
6626
|
amendst|ast|sta) amend="true"; staged="true";;
|
|
6405
6627
|
amendf|amf|af|fa) amend="true"; fast="true";;
|
|
6406
|
-
last|l) last="true";;
|
|
6628
|
+
last|l) last="true";; # deprecated alias of `gitb edit`; kept for back-compat
|
|
6407
6629
|
revert|rev) revert="true";;
|
|
6630
|
+
revertp|revp|rp) revert="true"; push="true";;
|
|
6408
6631
|
llm|ai|i) llm="true";;
|
|
6409
6632
|
llmf|aif|if) llm="true"; fast="true";;
|
|
6410
6633
|
llmp|aip|ip) llm="true"; push="true";;
|
|
@@ -6421,6 +6644,10 @@ function commit_script {
|
|
|
6421
6644
|
wrong_mode "commit" $1
|
|
6422
6645
|
esac
|
|
6423
6646
|
fi
|
|
6647
|
+
if [ -n "$last" ]; then
|
|
6648
|
+
edit_script ""
|
|
6649
|
+
exit
|
|
6650
|
+
fi
|
|
6424
6651
|
validate_commit_flag_combo
|
|
6425
6652
|
header_msg="GIT COMMIT"
|
|
6426
6653
|
if [ -n "${llm}" ]; then
|
|
@@ -6466,8 +6693,6 @@ function commit_script {
|
|
|
6466
6693
|
header_msg="$header_msg TICKET"
|
|
6467
6694
|
elif [ -n "${amend}" ]; then
|
|
6468
6695
|
header_msg="$header_msg AMEND LAST"
|
|
6469
|
-
elif [ -n "${last}" ]; then
|
|
6470
|
-
header_msg="$header_msg LAST"
|
|
6471
6696
|
elif [ -n "${revert}" ]; then
|
|
6472
6697
|
header_msg="$header_msg REVERT"
|
|
6473
6698
|
fi
|
|
@@ -6488,7 +6713,6 @@ function commit_script {
|
|
|
6488
6713
|
print_help_row $PAD "split" "sp, sl" "Split staged changes into one commit per detected scope"
|
|
6489
6714
|
print_help_row $PAD "fixup" "x, fix" "Create a ${GREEN}--fixup${ENDCOLOR} commit against an older commit"
|
|
6490
6715
|
print_help_row $PAD "amend" "a, am" "Add changes into the last commit (no message edit)"
|
|
6491
|
-
print_help_row $PAD "last" "l" "Rewrite the last commit message"
|
|
6492
6716
|
print_help_row $PAD "revert" "rev" "Revert a commit (${GREEN}git revert --no-edit${ENDCOLOR})"
|
|
6493
6717
|
print_help_row $PAD "ff" "" "Ultrafast: ${BOLD}ai + split + fast${NORMAL} with no prompts (use ${BOLD}ffp${NORMAL} to also push)"
|
|
6494
6718
|
print_help_row $PAD "help" "h, --help, -h" "Show this help"
|
|
@@ -6519,18 +6743,13 @@ function commit_script {
|
|
|
6519
6743
|
echo -e " ${BLUE}•${ENDCOLOR} Word order doesn't matter: ${GREEN}ai fast push${ENDCOLOR} == ${GREEN}push fast ai${ENDCOLOR} == ${GREEN}aifp${ENDCOLOR}"
|
|
6520
6744
|
echo -e " ${BLUE}•${ENDCOLOR} Modifiers stack on actions: ${GREEN}ai+fixup${ENDCOLOR}, ${GREEN}fast+amend${ENDCOLOR}, ${GREEN}split+push${ENDCOLOR}, ${GREEN}ai+staged${ENDCOLOR}, ..."
|
|
6521
6745
|
echo -e " ${BLUE}•${ENDCOLOR} ${BOLD}fast${NORMAL} and ${BOLD}staged${NORMAL} are mutually exclusive (one stages all, the other uses what's staged)"
|
|
6522
|
-
echo -e " ${BLUE}•${ENDCOLOR} ${BOLD}
|
|
6746
|
+
echo -e " ${BLUE}•${ENDCOLOR} ${BOLD}revert${NORMAL} and ${BOLD}ff${NORMAL} only accept ${BOLD}push${NORMAL} (as ${BOLD}revp${NORMAL}/${BOLD}ffp${NORMAL}); to rewrite the last message use ${GREEN}gitb edit${ENDCOLOR}"
|
|
6523
6747
|
git config --unset gitbasher.cached-git-add 2>/dev/null || true
|
|
6524
6748
|
exit 0
|
|
6525
6749
|
fi
|
|
6526
6750
|
summarize_commit_intent
|
|
6527
6751
|
echo
|
|
6528
6752
|
warn_if_detached_head "commit"
|
|
6529
|
-
if [ -n "$last" ]; then
|
|
6530
|
-
git config --unset gitbasher.cached-git-add 2>/dev/null
|
|
6531
|
-
git commit --amend
|
|
6532
|
-
exit
|
|
6533
|
-
fi
|
|
6534
6753
|
is_clean=$(LC_ALL=C git status --porcelain)
|
|
6535
6754
|
if [ -z "$is_clean" ]; then
|
|
6536
6755
|
if [ -z "${revert}" ]; then
|
|
@@ -6570,6 +6789,10 @@ function commit_script {
|
|
|
6570
6789
|
result=$(git revert --no-edit ${commit_hash} 2>&1)
|
|
6571
6790
|
check_code $? "$result" "revert"
|
|
6572
6791
|
after_commit "revert"
|
|
6792
|
+
if [ -n "${push}" ]; then
|
|
6793
|
+
echo
|
|
6794
|
+
push_script y
|
|
6795
|
+
fi
|
|
6573
6796
|
exit
|
|
6574
6797
|
fi
|
|
6575
6798
|
if [ -z "${fast}" ] && [ -z "${staged}" ]; then
|
|
@@ -6773,6 +6996,10 @@ function commit_script {
|
|
|
6773
6996
|
git config --unset gitbasher.cached-commit-message 2>/dev/null
|
|
6774
6997
|
echo
|
|
6775
6998
|
after_commit "amend"
|
|
6999
|
+
if [ -n "${push}" ]; then
|
|
7000
|
+
echo
|
|
7001
|
+
push_script y
|
|
7002
|
+
fi
|
|
6776
7003
|
exit
|
|
6777
7004
|
fi
|
|
6778
7005
|
echo
|
|
@@ -7042,6 +7269,184 @@ ${staged_with_tab}
|
|
|
7042
7269
|
push_script y
|
|
7043
7270
|
fi
|
|
7044
7271
|
}
|
|
7272
|
+
function edit_script {
|
|
7273
|
+
local mode="amend"
|
|
7274
|
+
case "$1" in
|
|
7275
|
+
help|h|--help|-h)
|
|
7276
|
+
echo -e "${YELLOW}GIT EDIT${ENDCOLOR}"
|
|
7277
|
+
echo
|
|
7278
|
+
echo -e "usage: ${YELLOW}gitb edit [mode] [arg]${ENDCOLOR}"
|
|
7279
|
+
echo
|
|
7280
|
+
echo -e "Rewrite a commit message or rename the current branch."
|
|
7281
|
+
echo
|
|
7282
|
+
echo -e "${YELLOW}Modes${ENDCOLOR}"
|
|
7283
|
+
echo -e " ${BOLD}<empty>${NORMAL} Reword the ${BOLD}last${NORMAL} commit (${GREEN}git commit --amend${ENDCOLOR})"
|
|
7284
|
+
echo -e " ${BOLD}pick${NORMAL} (${BOLD}p${NORMAL}, ${BOLD}c${NORMAL}) Choose any recent commit and reword it via rebase"
|
|
7285
|
+
echo -e " ${BOLD}branch${NORMAL} (${BOLD}b${NORMAL}, ${BOLD}br${NORMAL}, ${BOLD}rename${NORMAL}) Rename the current branch (${GREEN}git branch -m${ENDCOLOR})"
|
|
7286
|
+
echo -e " ${BOLD}help${NORMAL} (${BOLD}h${NORMAL}) Show this help"
|
|
7287
|
+
echo
|
|
7288
|
+
echo -e "${YELLOW}Notes${ENDCOLOR}"
|
|
7289
|
+
echo -e " ${BLUE}•${ENDCOLOR} ${BOLD}pick${NORMAL} requires a clean working tree (the rebase replays commits)."
|
|
7290
|
+
echo -e " ${BLUE}•${ENDCOLOR} Merge commits and the root commit cannot be reworded this way."
|
|
7291
|
+
echo -e " ${BLUE}•${ENDCOLOR} If a reworded commit was already pushed, run ${YELLOW}gitb push force${ENDCOLOR} afterwards."
|
|
7292
|
+
echo -e " ${BLUE}•${ENDCOLOR} ${BOLD}branch${NORMAL} accepts an optional new name: ${YELLOW}gitb edit branch feat/new-name${ENDCOLOR}."
|
|
7293
|
+
echo -e " ${BLUE}•${ENDCOLOR} To add staged changes into the last commit, use ${YELLOW}gitb commit amend${ENDCOLOR}."
|
|
7294
|
+
echo -e " ${BLUE}•${ENDCOLOR} To undo the amend/rebase, use ${YELLOW}gitb undo amend${ENDCOLOR} / ${YELLOW}gitb undo rebase${ENDCOLOR}."
|
|
7295
|
+
exit 0
|
|
7296
|
+
;;
|
|
7297
|
+
"")
|
|
7298
|
+
;;
|
|
7299
|
+
pick|p|c|choose)
|
|
7300
|
+
mode="pick"
|
|
7301
|
+
;;
|
|
7302
|
+
branch|b|br|rename|ren)
|
|
7303
|
+
mode="branch"
|
|
7304
|
+
;;
|
|
7305
|
+
*)
|
|
7306
|
+
wrong_mode "edit" "$1"
|
|
7307
|
+
;;
|
|
7308
|
+
esac
|
|
7309
|
+
echo -e "${YELLOW}GIT EDIT${ENDCOLOR}"
|
|
7310
|
+
echo
|
|
7311
|
+
warn_if_detached_head "edit"
|
|
7312
|
+
if [ "$mode" = "branch" ]; then
|
|
7313
|
+
edit_branch_name "$2"
|
|
7314
|
+
return
|
|
7315
|
+
fi
|
|
7316
|
+
if ! git rev-parse --verify HEAD >/dev/null 2>&1; then
|
|
7317
|
+
echo -e "${RED}✗ No commits yet — nothing to edit.${ENDCOLOR}"
|
|
7318
|
+
exit 1
|
|
7319
|
+
fi
|
|
7320
|
+
git config --unset gitbasher.cached-git-add 2>/dev/null
|
|
7321
|
+
if [ "$mode" = "amend" ]; then
|
|
7322
|
+
git commit --amend
|
|
7323
|
+
return
|
|
7324
|
+
fi
|
|
7325
|
+
local dirty
|
|
7326
|
+
dirty=$(LC_ALL=C git status --porcelain)
|
|
7327
|
+
if [ -n "$dirty" ]; then
|
|
7328
|
+
echo -e "${RED}✗ Working tree has uncommitted changes — cannot reword via rebase.${ENDCOLOR}"
|
|
7329
|
+
echo -e "Commit, stash, or save them first (try ${YELLOW}gitb wip up${ENDCOLOR})."
|
|
7330
|
+
exit 1
|
|
7331
|
+
fi
|
|
7332
|
+
echo -e "${YELLOW}Step 1.${ENDCOLOR} Select a commit to ${YELLOW}reword${ENDCOLOR}:"
|
|
7333
|
+
choose_commit 19
|
|
7334
|
+
if [ -z "$commit_hash" ]; then
|
|
7335
|
+
exit
|
|
7336
|
+
fi
|
|
7337
|
+
local head_hash
|
|
7338
|
+
head_hash=$(git rev-parse --short HEAD)
|
|
7339
|
+
if [ "$commit_hash" = "$head_hash" ]; then
|
|
7340
|
+
echo
|
|
7341
|
+
git commit --amend
|
|
7342
|
+
return
|
|
7343
|
+
fi
|
|
7344
|
+
if git rev-parse --verify "${commit_hash}^2" >/dev/null 2>&1; then
|
|
7345
|
+
echo
|
|
7346
|
+
echo -e "${RED}✗ Cannot reword a merge commit via rebase.${ENDCOLOR}"
|
|
7347
|
+
exit 1
|
|
7348
|
+
fi
|
|
7349
|
+
if ! git rev-parse --verify "${commit_hash}^" >/dev/null 2>&1; then
|
|
7350
|
+
echo
|
|
7351
|
+
echo -e "${RED}✗ Cannot reword the root commit this way.${ENDCOLOR}"
|
|
7352
|
+
echo -e "Use ${YELLOW}git rebase -i --root${ENDCOLOR} manually."
|
|
7353
|
+
exit 1
|
|
7354
|
+
fi
|
|
7355
|
+
local seq_script
|
|
7356
|
+
seq_script=$(mktemp "${TMPDIR:-/tmp}/gitb-edit-seq.XXXXXX")
|
|
7357
|
+
chmod 600 "$seq_script"
|
|
7358
|
+
trap 'rm -f "$seq_script"' EXIT INT TERM
|
|
7359
|
+
cat > "$seq_script" <<SEQ_EOF
|
|
7360
|
+
todo="\$1"
|
|
7361
|
+
tmpfile="\$(mktemp "\${todo}.XXXXXX")"
|
|
7362
|
+
awk -v target="${commit_hash}" '
|
|
7363
|
+
\$1 == "pick" && (index(\$2, target) == 1 || index(target, \$2) == 1) { \$1 = "reword" }
|
|
7364
|
+
{ print }
|
|
7365
|
+
' "\$todo" > "\$tmpfile"
|
|
7366
|
+
mv "\$tmpfile" "\$todo"
|
|
7367
|
+
SEQ_EOF
|
|
7368
|
+
echo
|
|
7369
|
+
echo -e "${YELLOW}Rewording ${commit_hash}...${ENDCOLOR}"
|
|
7370
|
+
GIT_SEQUENCE_EDITOR="bash $seq_script" git rebase -i "${commit_hash}^"
|
|
7371
|
+
}
|
|
7372
|
+
function edit_branch_name {
|
|
7373
|
+
local new_name="$1"
|
|
7374
|
+
local old_name="$current_branch"
|
|
7375
|
+
if [ -z "$old_name" ]; then
|
|
7376
|
+
echo -e "${RED}✗ Could not determine the current branch.${ENDCOLOR}"
|
|
7377
|
+
exit 1
|
|
7378
|
+
fi
|
|
7379
|
+
if [ -z "$new_name" ]; then
|
|
7380
|
+
echo -e "${YELLOW}Renaming branch ${BOLD}${old_name}${NORMAL}${ENDCOLOR}"
|
|
7381
|
+
echo -e "Press Enter to keep the current name (no-op), or 0 to cancel"
|
|
7382
|
+
read_editable_input new_name "New name: " "$old_name"
|
|
7383
|
+
echo
|
|
7384
|
+
if [ "$new_name" = "0" ] || [ -z "$new_name" ]; then
|
|
7385
|
+
echo -e "${YELLOW}Cancelled.${ENDCOLOR}"
|
|
7386
|
+
exit
|
|
7387
|
+
fi
|
|
7388
|
+
fi
|
|
7389
|
+
if ! sanitize_git_name "$new_name"; then
|
|
7390
|
+
show_sanitization_error "branch name" "Use only letters, numbers, dots, dashes, underscores, and slashes."
|
|
7391
|
+
exit 1
|
|
7392
|
+
fi
|
|
7393
|
+
new_name="$sanitized_git_name"
|
|
7394
|
+
if [ "$new_name" = "$old_name" ]; then
|
|
7395
|
+
echo -e "${YELLOW}New name matches the current name — nothing to do.${ENDCOLOR}"
|
|
7396
|
+
exit
|
|
7397
|
+
fi
|
|
7398
|
+
if git show-ref --verify --quiet "refs/heads/${new_name}"; then
|
|
7399
|
+
echo -e "${RED}✗ A local branch named ${BOLD}${new_name}${NORMAL} already exists.${ENDCOLOR}"
|
|
7400
|
+
exit 1
|
|
7401
|
+
fi
|
|
7402
|
+
if [ "$old_name" = "$main_branch" ]; then
|
|
7403
|
+
echo -e "${YELLOW}⚠ ${BOLD}${old_name}${NORMAL} is the configured main branch.${ENDCOLOR}"
|
|
7404
|
+
echo -e "Renaming it will leave ${YELLOW}gitbasher.branch${ENDCOLOR} pointing at the old name."
|
|
7405
|
+
printf "Rename anyway? (y/n) "
|
|
7406
|
+
yes_no_choice
|
|
7407
|
+
echo
|
|
7408
|
+
fi
|
|
7409
|
+
local result
|
|
7410
|
+
result=$(git branch -m "$old_name" "$new_name" 2>&1)
|
|
7411
|
+
if [ $? -ne 0 ]; then
|
|
7412
|
+
echo -e "${RED}✗ Could not rename branch.${ENDCOLOR}"
|
|
7413
|
+
echo "$result"
|
|
7414
|
+
exit 1
|
|
7415
|
+
fi
|
|
7416
|
+
current_branch="$new_name"
|
|
7417
|
+
echo -e "${GREEN}✓ Renamed ${BOLD}${old_name}${NORMAL}${GREEN} → ${BOLD}${new_name}${NORMAL}${ENDCOLOR}"
|
|
7418
|
+
if [ -z "$origin_name" ]; then
|
|
7419
|
+
return
|
|
7420
|
+
fi
|
|
7421
|
+
if ! git ls-remote --exit-code --heads "$origin_name" "$old_name" >/dev/null 2>&1; then
|
|
7422
|
+
echo -e "Push the renamed branch when ready: ${YELLOW}gitb push${ENDCOLOR}"
|
|
7423
|
+
return
|
|
7424
|
+
fi
|
|
7425
|
+
echo
|
|
7426
|
+
echo -e "${YELLOW}${BOLD}${old_name}${NORMAL}${YELLOW} also exists on ${origin_name}.${ENDCOLOR}"
|
|
7427
|
+
echo -e "Push ${BOLD}${new_name}${NORMAL} and delete the old remote branch?"
|
|
7428
|
+
printf "(y/n) "
|
|
7429
|
+
yes_no_choice
|
|
7430
|
+
echo
|
|
7431
|
+
local push_out
|
|
7432
|
+
push_out=$(git push -u "$origin_name" "$new_name" 2>&1)
|
|
7433
|
+
if [ $? -ne 0 ]; then
|
|
7434
|
+
echo -e "${RED}✗ Could not push ${BOLD}${new_name}${NORMAL}${RED} to ${origin_name}.${ENDCOLOR}"
|
|
7435
|
+
echo "$push_out"
|
|
7436
|
+
echo -e "Old remote branch ${BOLD}${old_name}${NORMAL} left intact."
|
|
7437
|
+
exit 1
|
|
7438
|
+
fi
|
|
7439
|
+
echo -e "${GREEN}✓ Pushed ${BOLD}${new_name}${NORMAL}${GREEN} to ${origin_name}${ENDCOLOR}"
|
|
7440
|
+
local del_out
|
|
7441
|
+
del_out=$(git push "$origin_name" --delete "$old_name" 2>&1)
|
|
7442
|
+
if [ $? -ne 0 ]; then
|
|
7443
|
+
echo -e "${YELLOW}⚠ Could not delete ${BOLD}${old_name}${NORMAL}${YELLOW} on ${origin_name}.${ENDCOLOR}"
|
|
7444
|
+
echo "$del_out"
|
|
7445
|
+
echo -e "Delete it manually with ${YELLOW}git push ${origin_name} --delete ${old_name}${ENDCOLOR} when ready."
|
|
7446
|
+
return
|
|
7447
|
+
fi
|
|
7448
|
+
echo -e "${GREEN}✓ Deleted ${BOLD}${old_name}${NORMAL}${GREEN} on ${origin_name}${ENDCOLOR}"
|
|
7449
|
+
}
|
|
7045
7450
|
function branch_script {
|
|
7046
7451
|
case "$1" in
|
|
7047
7452
|
list|l) list="true";;
|
|
@@ -11993,6 +12398,97 @@ function origin_script {
|
|
|
11993
12398
|
exit
|
|
11994
12399
|
fi
|
|
11995
12400
|
}
|
|
12401
|
+
function _gitb_clone_dest_from_url {
|
|
12402
|
+
local url="$1"
|
|
12403
|
+
local stripped="${url%.git}"
|
|
12404
|
+
local last="${stripped##*/}"
|
|
12405
|
+
last="${last##*:}"
|
|
12406
|
+
echo "$last"
|
|
12407
|
+
}
|
|
12408
|
+
function _gitb_print_clone_help {
|
|
12409
|
+
local PAD=14
|
|
12410
|
+
echo -e "usage: ${YELLOW}gitb clone <url> [destination]${ENDCOLOR}"
|
|
12411
|
+
echo
|
|
12412
|
+
echo -e "Clone a remote repository, show progress, and initialize gitbasher in it."
|
|
12413
|
+
echo
|
|
12414
|
+
print_help_header $PAD
|
|
12415
|
+
print_help_row $PAD "<url>" "" "Git URL to clone (https, ssh, or git@)"
|
|
12416
|
+
print_help_row $PAD "[destination]" "" "Optional target directory (default: repo name)"
|
|
12417
|
+
print_help_row $PAD "help" "h" "Show this help"
|
|
12418
|
+
echo
|
|
12419
|
+
echo -e "${YELLOW}Examples${ENDCOLOR}"
|
|
12420
|
+
echo -e " ${GREEN}gitb clone https://github.com/user/repo.git${ENDCOLOR}"
|
|
12421
|
+
echo -e " ${GREEN}gitb clone git@github.com:user/repo.git my-repo${ENDCOLOR}"
|
|
12422
|
+
exit
|
|
12423
|
+
}
|
|
12424
|
+
function clone_script {
|
|
12425
|
+
echo -e "${YELLOW}GIT CLONE${ENDCOLOR}"
|
|
12426
|
+
echo
|
|
12427
|
+
case "$1" in
|
|
12428
|
+
""|help|h) _gitb_print_clone_help ;;
|
|
12429
|
+
esac
|
|
12430
|
+
local raw_url="$1"
|
|
12431
|
+
local dest="$2"
|
|
12432
|
+
if ! validate_git_url "$raw_url"; then
|
|
12433
|
+
echo -e "${RED}✗ Invalid git URL format: $raw_url${ENDCOLOR}" >&2
|
|
12434
|
+
echo -e "${YELLOW}Expected one of:${ENDCOLOR}" >&2
|
|
12435
|
+
echo -e " https://host/user/repo.git" >&2
|
|
12436
|
+
echo -e " git@host:user/repo.git" >&2
|
|
12437
|
+
echo -e " ssh://git@host/repo.git" >&2
|
|
12438
|
+
exit 1
|
|
12439
|
+
fi
|
|
12440
|
+
local url="$validated_url"
|
|
12441
|
+
if [ -z "$dest" ]; then
|
|
12442
|
+
dest=$(_gitb_clone_dest_from_url "$url")
|
|
12443
|
+
fi
|
|
12444
|
+
if [ -z "$dest" ]; then
|
|
12445
|
+
echo -e "${RED}✗ Could not determine target directory from URL.${ENDCOLOR}" >&2
|
|
12446
|
+
echo -e "${YELLOW}Pass a destination explicitly: ${GREEN}gitb clone <url> <dir>${ENDCOLOR}" >&2
|
|
12447
|
+
exit 1
|
|
12448
|
+
fi
|
|
12449
|
+
if ! sanitize_file_path "$dest"; then
|
|
12450
|
+
echo -e "${RED}✗ Invalid destination path: $dest${ENDCOLOR}" >&2
|
|
12451
|
+
exit 1
|
|
12452
|
+
fi
|
|
12453
|
+
dest="$sanitized_file_path"
|
|
12454
|
+
if [ -e "$dest" ]; then
|
|
12455
|
+
echo -e "${RED}✗ Destination '$dest' already exists.${ENDCOLOR}" >&2
|
|
12456
|
+
echo -e "${YELLOW}Remove it first or pass a different destination.${ENDCOLOR}" >&2
|
|
12457
|
+
exit 1
|
|
12458
|
+
fi
|
|
12459
|
+
echo -e "Source: ${BLUE}$url${ENDCOLOR}"
|
|
12460
|
+
echo -e "Target: ${BLUE}$dest${ENDCOLOR}"
|
|
12461
|
+
echo
|
|
12462
|
+
if ! git clone --progress "$url" "$dest"; then
|
|
12463
|
+
echo
|
|
12464
|
+
echo -e "${RED}✗ Clone failed.${ENDCOLOR}" >&2
|
|
12465
|
+
exit 1
|
|
12466
|
+
fi
|
|
12467
|
+
echo
|
|
12468
|
+
echo -e "${GREEN}✓ Cloned into '$dest'${ENDCOLOR}"
|
|
12469
|
+
local target_abs
|
|
12470
|
+
target_abs=$(cd "$dest" 2>/dev/null && pwd)
|
|
12471
|
+
if [ -z "$target_abs" ]; then
|
|
12472
|
+
echo -e "${RED}✗ Failed to enter directory '$dest'.${ENDCOLOR}" >&2
|
|
12473
|
+
exit 1
|
|
12474
|
+
fi
|
|
12475
|
+
if ! cd "$target_abs"; then
|
|
12476
|
+
echo -e "${RED}✗ Failed to cd into '$target_abs'.${ENDCOLOR}" >&2
|
|
12477
|
+
exit 1
|
|
12478
|
+
fi
|
|
12479
|
+
local cloned_branch
|
|
12480
|
+
cloned_branch=$(git branch --show-current 2>/dev/null)
|
|
12481
|
+
if [ -z "$cloned_branch" ]; then
|
|
12482
|
+
cloned_branch="main"
|
|
12483
|
+
fi
|
|
12484
|
+
git config --local gitbasher.branch "$cloned_branch" 2>/dev/null
|
|
12485
|
+
git config --local gitbasher.scopes "" 2>/dev/null
|
|
12486
|
+
git config --local gitbasher.isfirst "true" 2>/dev/null
|
|
12487
|
+
echo -e "${GREEN}✓ Initialized gitbasher in '$dest'${ENDCOLOR}"
|
|
12488
|
+
echo
|
|
12489
|
+
echo -e "${CYAN}💡 Enter the new repo with:${ENDCOLOR}"
|
|
12490
|
+
echo -e " ${GREEN}cd $target_abs${ENDCOLOR}"
|
|
12491
|
+
}
|
|
11996
12492
|
GITBASHER_REPO="maxbolgarin/gitbasher"
|
|
11997
12493
|
GITBASHER_RELEASES_URL="https://github.com/${GITBASHER_REPO}/releases"
|
|
11998
12494
|
function _normalize_version {
|
|
@@ -12480,6 +12976,7 @@ function _gitb_bash_completion_content {
|
|
|
12480
12976
|
cat <<'GITB_BASH_EOF'
|
|
12481
12977
|
_gitb_commands="
|
|
12482
12978
|
commit c co com
|
|
12979
|
+
edit ed ee
|
|
12483
12980
|
push p ps pus
|
|
12484
12981
|
pull pu pl pul
|
|
12485
12982
|
merge m me
|
|
@@ -12508,8 +13005,8 @@ update up upd
|
|
|
12508
13005
|
uninstall uns uni
|
|
12509
13006
|
help man
|
|
12510
13007
|
"
|
|
12511
|
-
_gitb_sub_commit="ai llm i fast f fasts fs sf ff ffp ffpush push pu p fastp fp pf fastsp fsp fps scope s msg m ticket jira j t staged st no-split nosplit nsp nsl fixup fix x fixupp fixp xp px fixupst xst stx fastfix fx xf fastfixp fxp xfp amend am a amendst ast sta amendf amf af fa split sp sl splitp spp slp aisplit isplit aispl ispl aisplitp isplitp aisplp isplp llmf aif if llmp aip ip llmst aist ist llmfp aifp ifp ipf llms ais is llmsf aisf isf llmsfp aisfp isfp llmm aim im llmmf aimf imf llmmfp aimfp imfp
|
|
12512
|
-
_gitb_sub_commit_simple="ai llm i fast f push pu p scope s msg m ticket jira j t staged no-split nosplit nsp nsl fixup fix x amend am a split sp sl
|
|
13008
|
+
_gitb_sub_commit="ai llm i fast f fasts fs sf ff ffp ffpush push pu p fastp fp pf fastsp fsp fps scope s msg m ticket jira j t staged st no-split nosplit nsp nsl fixup fix x fixupp fixp xp px fixupst xst stx fastfix fx xf fastfixp fxp xfp amend am a amendst ast sta amendf amf af fa split sp sl splitp spp slp aisplit isplit aispl ispl aisplitp isplitp aisplp isplp llmf aif if llmp aip ip llmst aist ist llmfp aifp ifp ipf llms ais is llmsf aisf isf llmsfp aisfp isfp llmm aim im llmmf aimf imf llmmfp aimfp imfp revert rev revertp revp rp help h"
|
|
13009
|
+
_gitb_sub_commit_simple="ai llm i fast f push pu p scope s msg m ticket jira j t staged no-split nosplit nsp nsl fixup fix x amend am a split sp sl revert rev help h"
|
|
12513
13010
|
_gitb_sub_wip_backend="stash s branch b worktree w wt tree nopush np n"
|
|
12514
13011
|
_gitb_sub_push="yes y force f list log l help h"
|
|
12515
13012
|
_gitb_sub_pull="fetch fe all fa upd u ffonly ff merge m rebase r interactive ri rs dry d dr help h"
|
|
@@ -12530,10 +13027,12 @@ _gitb_sub_origin="set add new a change update c u set-url rename mv ren remove d
|
|
|
12530
13027
|
_gitb_sub_log="branch b compare comp c search s help h"
|
|
12531
13028
|
_gitb_sub_hook="list create edit toggle test show remove select install help"
|
|
12532
13029
|
_gitb_sub_log_branch="local l remote r all a help h"
|
|
13030
|
+
_gitb_sub_edit="pick p c choose branch b br rename ren help h"
|
|
12533
13031
|
_gitb_sub_config_auto="up u on install enable down d off uninstall disable remove status st print cat p help h"
|
|
12534
13032
|
_gitb_canonical() {
|
|
12535
13033
|
case "$1" in
|
|
12536
13034
|
commit|c|co|com) echo commit ;;
|
|
13035
|
+
edit|ed|ee) echo edit ;;
|
|
12537
13036
|
push|p|ps|pus) echo push ;;
|
|
12538
13037
|
pull|pu|pl|pul) echo pull ;;
|
|
12539
13038
|
merge|m|me) echo merge ;;
|
|
@@ -12564,7 +13063,6 @@ _gitb_commit_state_aware() {
|
|
|
12564
13063
|
for ((i=2; i<COMP_CWORD; i++)); do
|
|
12565
13064
|
tok="${COMP_WORDS[$i]}"
|
|
12566
13065
|
case "$tok" in
|
|
12567
|
-
last|l) has_action="last" ;;
|
|
12568
13066
|
revert|rev) has_action="revert" ;;
|
|
12569
13067
|
fixup|fix|x) has_action="fixup" ;;
|
|
12570
13068
|
amend|am|a) has_action="amend" ;;
|
|
@@ -12581,8 +13079,8 @@ _gitb_commit_state_aware() {
|
|
|
12581
13079
|
done
|
|
12582
13080
|
local candidates=()
|
|
12583
13081
|
case "$has_action" in
|
|
12584
|
-
|
|
12585
|
-
|
|
13082
|
+
revert)
|
|
13083
|
+
[ -z "$has_push" ] && candidates+=("push")
|
|
12586
13084
|
;;
|
|
12587
13085
|
amend|fixup)
|
|
12588
13086
|
[ -z "${has_fast}${has_staged}" ] && candidates+=("fast" "staged")
|
|
@@ -12594,7 +13092,7 @@ _gitb_commit_state_aware() {
|
|
|
12594
13092
|
if [ -z "$has_action" ]; then
|
|
12595
13093
|
candidates+=("split")
|
|
12596
13094
|
[ -z "$has_strict_mod" ] && candidates+=("amend" "fixup")
|
|
12597
|
-
[ -z "$has_any_mod" ] && candidates+=("
|
|
13095
|
+
[ -z "$has_any_mod" ] && candidates+=("revert")
|
|
12598
13096
|
fi
|
|
12599
13097
|
[ -z "$has_ai" ] && candidates+=("ai")
|
|
12600
13098
|
[ -z "${has_fast}${has_staged}" ] && candidates+=("fast" "staged")
|
|
@@ -12690,7 +13188,7 @@ _gitb() {
|
|
|
12690
13188
|
'staged[only staged files]' 'no-split[disable split]'
|
|
12691
13189
|
'fixup[create fixup commit]' 'amend[amend last commit]'
|
|
12692
13190
|
'split[split into atomic commits]'
|
|
12693
|
-
'
|
|
13191
|
+
'revert[revert a commit]' 'help[show help]'
|
|
12694
13192
|
)
|
|
12695
13193
|
_values 'commit flag' $flags
|
|
12696
13194
|
return
|
|
@@ -12702,7 +13200,6 @@ _gitb() {
|
|
|
12702
13200
|
for ((i=3; i<CURRENT; i++)); do
|
|
12703
13201
|
tok="${words[$i]}"
|
|
12704
13202
|
case "$tok" in
|
|
12705
|
-
last|l) has_action="last" ;;
|
|
12706
13203
|
revert|rev) has_action="revert" ;;
|
|
12707
13204
|
fixup|fix|x) has_action="fixup" ;;
|
|
12708
13205
|
amend|am|a) has_action="amend" ;;
|
|
@@ -12719,7 +13216,8 @@ _gitb() {
|
|
|
12719
13216
|
done
|
|
12720
13217
|
flags=()
|
|
12721
13218
|
case "$has_action" in
|
|
12722
|
-
|
|
13219
|
+
revert)
|
|
13220
|
+
[ -z "$has_push" ] && flags+=('push[commit and push]')
|
|
12723
13221
|
;;
|
|
12724
13222
|
amend|fixup)
|
|
12725
13223
|
[ -z "${has_fast}${has_staged}" ] && flags+=('fast[fast commit (git add .)]' 'staged[only staged files]')
|
|
@@ -12731,7 +13229,7 @@ _gitb() {
|
|
|
12731
13229
|
if [ -z "$has_action" ]; then
|
|
12732
13230
|
flags+=('split[split into atomic commits]')
|
|
12733
13231
|
[ -z "$has_strict_mod" ] && flags+=('amend[amend last commit]' 'fixup[create fixup commit]')
|
|
12734
|
-
[ -z "$has_any_mod" ] && flags+=('
|
|
13232
|
+
[ -z "$has_any_mod" ] && flags+=('revert[revert a commit]')
|
|
12735
13233
|
fi
|
|
12736
13234
|
[ -z "$has_ai" ] && flags+=('ai[AI-generated message]')
|
|
12737
13235
|
[ -z "${has_fast}${has_staged}" ] && flags+=('fast[fast commit (git add .)]' 'staged[only staged files]')
|
|
@@ -12770,6 +13268,7 @@ _gitb() {
|
|
|
12770
13268
|
local -a cmds
|
|
12771
13269
|
cmds=(
|
|
12772
13270
|
'commit:Interactive commit (alias: c, co, com)'
|
|
13271
|
+
'edit:Rewrite the last commit message (alias: ed, ee)'
|
|
12773
13272
|
'push:Push current branch (alias: p, ps, pus)'
|
|
12774
13273
|
'pull:Pull updates (alias: pu, pl, pul)'
|
|
12775
13274
|
'merge:Merge a branch (alias: m, me)'
|
|
@@ -12810,7 +13309,10 @@ _gitb() {
|
|
|
12810
13309
|
'no-split[disable split]' 'fixup[create fixup commit]' \
|
|
12811
13310
|
'amend[amend last commit]' 'split[split into atomic commits]' \
|
|
12812
13311
|
'splitp[split and push]' \
|
|
12813
|
-
'
|
|
13312
|
+
'revert[revert a commit]' 'help[show help]'
|
|
13313
|
+
;;
|
|
13314
|
+
edit|ed|ee)
|
|
13315
|
+
_values 'edit mode' 'pick[reword any recent commit]' 'branch[rename current branch]' 'help[show help]'
|
|
12814
13316
|
;;
|
|
12815
13317
|
push|p|ps|pus)
|
|
12816
13318
|
_values 'push mode' 'yes[skip confirmation]' 'force[force push]' 'list[show pending pushes]' 'help[show help]'
|
|
@@ -12944,6 +13446,9 @@ complete -c gitb -n __gitb_no_subcmd -a commit -d 'Interactive commit'
|
|
|
12944
13446
|
complete -c gitb -n __gitb_no_subcmd -a c -d 'Alias of commit'
|
|
12945
13447
|
complete -c gitb -n __gitb_no_subcmd -a co -d 'Alias of commit'
|
|
12946
13448
|
complete -c gitb -n __gitb_no_subcmd -a com -d 'Alias of commit'
|
|
13449
|
+
complete -c gitb -n __gitb_no_subcmd -a edit -d 'Rewrite the last commit message'
|
|
13450
|
+
complete -c gitb -n __gitb_no_subcmd -a ed -d 'Alias of edit'
|
|
13451
|
+
complete -c gitb -n __gitb_no_subcmd -a ee -d 'Alias of edit'
|
|
12947
13452
|
complete -c gitb -n __gitb_no_subcmd -a push -d 'Push current branch'
|
|
12948
13453
|
complete -c gitb -n __gitb_no_subcmd -a p -d 'Alias of push'
|
|
12949
13454
|
complete -c gitb -n __gitb_no_subcmd -a pull -d 'Pull updates'
|
|
@@ -13004,8 +13509,10 @@ function __gitb_at_commit_extra
|
|
|
13004
13509
|
test (count $tokens) -ge 3; or return 1
|
|
13005
13510
|
contains -- $tokens[2] commit c co com
|
|
13006
13511
|
end
|
|
13007
|
-
complete -c gitb -n __gitb_at_commit_pos2 -a "ai fast fasts ff ffp push fastp scope msg ticket staged no-split fixup amend split splitp aisplit aisplitp aip aif aifp
|
|
13008
|
-
complete -c gitb -n __gitb_at_commit_extra -a "ai fast push scope msg ticket staged no-split fixup amend split
|
|
13512
|
+
complete -c gitb -n __gitb_at_commit_pos2 -a "ai fast fasts ff ffp push fastp scope msg ticket staged no-split fixup amend split splitp aisplit aisplitp aip aif aifp revert revertp revp rp help"
|
|
13513
|
+
complete -c gitb -n __gitb_at_commit_extra -a "ai fast push scope msg ticket staged no-split fixup amend split revert help"
|
|
13514
|
+
set -l __gitb_edit "__gitb_using_cmd edit ed ee; and __gitb_at_position 2"
|
|
13515
|
+
complete -c gitb -n "$__gitb_edit" -a "pick branch help"
|
|
13009
13516
|
set -l __gitb_push "__gitb_using_cmd push p ps pus; and __gitb_at_position 2"
|
|
13010
13517
|
complete -c gitb -n "$__gitb_push" -a "yes force list help"
|
|
13011
13518
|
set -l __gitb_pull "__gitb_using_cmd pull pu pl pul; and __gitb_at_position 2"
|
|
@@ -13330,12 +13837,19 @@ function print_help {
|
|
|
13330
13837
|
echo
|
|
13331
13838
|
echo -e "usage: ${YELLOW}gitb <command> [mode]${ENDCOLOR}"
|
|
13332
13839
|
echo
|
|
13840
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
13841
|
+
echo -e "${YELLOW}⚠ Not inside a git repository.${ENDCOLOR}"
|
|
13842
|
+
echo -e "Only ${BOLD}clone${NORMAL}, ${BOLD}init${NORMAL}, ${BOLD}config${NORMAL}, ${BOLD}update${NORMAL}, and ${BOLD}uninstall${NORMAL} are available here."
|
|
13843
|
+
echo -e "Run ${GREEN}gitb clone <url>${ENDCOLOR} to clone a repo, ${GREEN}gitb init${ENDCOLOR} to start a new one, or ${GREEN}cd${ENDCOLOR} into an existing one."
|
|
13844
|
+
echo
|
|
13845
|
+
fi
|
|
13333
13846
|
local CMD=26
|
|
13334
13847
|
local hdr="${YELLOW}%s${ENDCOLOR}\n"
|
|
13335
13848
|
local row=" ${BOLD}%-*s${NORMAL} %s\n"
|
|
13336
13849
|
printf "$hdr" "DAILY"
|
|
13337
13850
|
printf "$row" "$CMD" "status (s)" "Show repo state and changed files"
|
|
13338
13851
|
printf "$row" "$CMD" "commit (c, co, com)" "Create commits — interactive, AI, amend, revert"
|
|
13852
|
+
printf "$row" "$CMD" "edit (ed, ee)" "Rewrite the last commit message (${GREEN}git commit --amend${ENDCOLOR})"
|
|
13339
13853
|
printf "$row" "$CMD" "push (p, ps, pus)" "Push current branch safely"
|
|
13340
13854
|
printf "$row" "$CMD" "pull (pu, pl, pul)" "Pull from remote"
|
|
13341
13855
|
printf "$row" "$CMD" "sync (sy)" "Sync current branch with $main_branch"
|
|
@@ -13362,6 +13876,7 @@ function print_help {
|
|
|
13362
13876
|
printf "$row" "$CMD" "wip (w)" "Stash work-in-progress, optionally back up remote"
|
|
13363
13877
|
echo
|
|
13364
13878
|
printf "$hdr" "SETUP"
|
|
13879
|
+
printf "$row" "$CMD" "clone (cl, clo)" "Clone a remote repo and initialize gitbasher in it"
|
|
13365
13880
|
printf "$row" "$CMD" "origin (or, o, remote)" "Manage remotes"
|
|
13366
13881
|
printf "$row" "$CMD" "hook (ho, hk)" "Manage git hooks"
|
|
13367
13882
|
printf "$row" "$CMD" "worktree (wt, tree)" "Manage git worktrees"
|
|
@@ -13372,9 +13887,14 @@ function print_help {
|
|
|
13372
13887
|
echo -e "Run ${YELLOW}gitb <command> help${ENDCOLOR} for modes and examples"
|
|
13373
13888
|
exit
|
|
13374
13889
|
}
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
|
|
13890
|
+
if [ "$GITBASHER_NO_REPO" = "true" ]; then
|
|
13891
|
+
project_name="global config"
|
|
13892
|
+
repo_url=""
|
|
13893
|
+
else
|
|
13894
|
+
project_name="$(get_repo_name)"
|
|
13895
|
+
repo_url="$(get_repo)"
|
|
13896
|
+
fi
|
|
13897
|
+
if [[ $is_first == "true" ]]; then
|
|
13378
13898
|
git config --local gitbasher.scopes ""
|
|
13379
13899
|
echo -e "${GREEN}Thanks for using gitbasher in project '$project_name'${ENDCOLOR}"
|
|
13380
13900
|
print_configuration
|
|
@@ -13406,6 +13926,9 @@ case "$1" in
|
|
|
13406
13926
|
commit|c|co|com)
|
|
13407
13927
|
commit_script "${@:2}"
|
|
13408
13928
|
;;
|
|
13929
|
+
edit|ed|ee)
|
|
13930
|
+
edit_script "${@:2}"
|
|
13931
|
+
;;
|
|
13409
13932
|
push|p|ps|pus)
|
|
13410
13933
|
push_script $2
|
|
13411
13934
|
;;
|
|
@@ -13457,6 +13980,9 @@ case "$1" in
|
|
|
13457
13980
|
origin|or|o|remote)
|
|
13458
13981
|
origin_script "$2" "$3"
|
|
13459
13982
|
;;
|
|
13983
|
+
clone|cl|clo)
|
|
13984
|
+
clone_script "$2" "$3"
|
|
13985
|
+
;;
|
|
13460
13986
|
update|up|upd)
|
|
13461
13987
|
update_script "$2"
|
|
13462
13988
|
;;
|