gitbasher 5.2.0 → 5.3.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 +18 -0
- package/README.md +6 -2
- package/dist/gitb +219 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to gitbasher are generated automatically by [semantic-release](https://github.com/semantic-release/semantic-release) from [Conventional Commits](https://www.conventionalcommits.org/) on `main`. The project follows [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [5.3.0](https://github.com/maxbolgarin/gitbasher/compare/v5.2.1...v5.3.0) (2026-09-30)
|
|
6
|
+
|
|
7
|
+
### 🚀 Features
|
|
8
|
+
|
|
9
|
+
* **config:** add `gitb cfg fast-split` to toggle split in fast modes ([c56e173](https://github.com/maxbolgarin/gitbasher/commit/c56e1735efeaea8c71f83e5d983cd24ff26fcd2b))
|
|
10
|
+
* **commit:** ask before splitting, add undo split and fast-split setting ([1de1fcd](https://github.com/maxbolgarin/gitbasher/commit/1de1fcd523b5080da0c8248347558a421c8d72bb))
|
|
11
|
+
* **commit:** show undo-split hint in the split type menu ([520e66a](https://github.com/maxbolgarin/gitbasher/commit/520e66aec25b43b7fb83b27f863cc6af4a0ab0e2))
|
|
12
|
+
|
|
13
|
+
### 🐛 Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **branch:** pass HEAD to --merged when deleting merged branches ([2cdaf58](https://github.com/maxbolgarin/gitbasher/commit/2cdaf58c3a92e9f26eba47bd180b582cb9676d04))
|
|
16
|
+
|
|
17
|
+
## [5.2.1](https://github.com/maxbolgarin/gitbasher/compare/v5.2.0...v5.2.1) (2026-07-11)
|
|
18
|
+
|
|
19
|
+
### 🐛 Bug Fixes
|
|
20
|
+
|
|
21
|
+
* **commit:** gate AI split by scope count and stop discarding near-complete groupings ([90becec](https://github.com/maxbolgarin/gitbasher/commit/90becec0f49a7a3e57e57e11a71b6909884ab337))
|
|
22
|
+
|
|
5
23
|
## [5.2.0](https://github.com/maxbolgarin/gitbasher/compare/v5.1.0...v5.2.0) (2026-07-07)
|
|
6
24
|
|
|
7
25
|
### 🚀 Features
|
package/README.md
CHANGED
|
@@ -360,7 +360,7 @@ gitb commit <combined> # compact form: ff, aifp, fastsp, ...
|
|
|
360
360
|
| `staged` | `st` | Use already-staged files (skip the add step) |
|
|
361
361
|
| `push` | `p` `pu` | Push after the commit succeeds |
|
|
362
362
|
| `scope` | `s` | Force a scope: `type(scope): message` (useful with fast mode) |
|
|
363
|
-
| `no-split` | `nsp` `nsl` |
|
|
363
|
+
| `no-split` | `nos` `nsp` `nsl` | Don't offer to split this commit (fast modes split by default) |
|
|
364
364
|
| `ai` | `i` `llm` | Generate the commit message with AI |
|
|
365
365
|
| `msg` | `m` | Open `$EDITOR` for a multiline message body |
|
|
366
366
|
| `ticket` | `t` `j` `jira` | Append ticket info to the header |
|
|
@@ -383,6 +383,8 @@ gitb commit <combined> # compact form: ff, aifp, fastsp, ...
|
|
|
383
383
|
- Word order doesn't matter: `ai fast push` == `push fast ai` == `aifp`.
|
|
384
384
|
- Modifiers stack on actions: `ai+fixup`, `fast+amend`, `split+push`, `ai+staged`, …
|
|
385
385
|
- `fast` and `staged` are mutually exclusive (one stages all, the other uses what's staged).
|
|
386
|
+
- Splitting: a regular commit **asks first** whether to try a split (before any grouping or AI work), then asks again to confirm the resulting groups. Fast modes (`fast`, `ff`, …) split without asking unless you turn it off with `gitb cfg fast-split`.
|
|
387
|
+
- Press `u` at any split prompt to **undo the split**: commits already made by the split are dropped, staging is restored, and you continue with a single commit. `ff` never stops, so use the setting or `no-split` there.
|
|
386
388
|
- `revert` and `ff` only accept `push` (as `revp` / `ffp`); to rewrite the last commit message use [`gitb edit`](#gitb-edit).
|
|
387
389
|
|
|
388
390
|
### `gitb edit`
|
|
@@ -804,6 +806,7 @@ gitb origin remove # delete the remote
|
|
|
804
806
|
| `history` | `hist` | How many recent commits to include in AI prompts |
|
|
805
807
|
| `diff` | `payload` | AI diff payload size (line and character caps) |
|
|
806
808
|
| `push-size` | `ps` `pushsize` | Warn before pushing more than N MB (0 disables) |
|
|
809
|
+
| `fast-split` | `fs` `fastsplit` | Split automatically in fast commit modes (on/off) |
|
|
807
810
|
| `completion` | `comp` | Install / uninstall bash & zsh tab completion |
|
|
808
811
|
| `delete` | `unset` `del` | Remove global config |
|
|
809
812
|
|
|
@@ -890,7 +893,8 @@ Overview-first diffs built for the gitbasher workflow — no flag memorization.
|
|
|
890
893
|
| `gitbasher.ai-commit-history-limit` | `gitb cfg history` | Recent commits included in AI prompts (default `10`) |
|
|
891
894
|
| `gitbasher.push-warn-size` | `gitb cfg push-size` | Warn before pushing more than N MB (default `50`, `0` disables) |
|
|
892
895
|
| `gitbasher.worktreebase` | `git config gitbasher.worktreebase <dir>` | Base directory for new worktrees (default `.worktree` under the repo root) |
|
|
893
|
-
| `gitbasher.commit-auto-split` | `git config gitbasher.commit-auto-split <ask\|always\|never>` | Offer to split a commit per scope (default `ask
|
|
896
|
+
| `gitbasher.commit-auto-split` | `git config gitbasher.commit-auto-split <ask\|always\|never>` | Offer to split a commit per scope (default `ask`: ask before splitting, then confirm the groups) |
|
|
897
|
+
| `gitbasher.commit-fast-split` | `gitb cfg fast-split` | Fast modes split automatically (default `true`); `false` makes `fast` ask and `ff` commit as one |
|
|
894
898
|
| `gitbasher.commit-max-split-groups` | `git config gitbasher.commit-max-split-groups <2..20>` | Cap on split commits per run (default `7`) |
|
|
895
899
|
| `gitbasher.commit-split-order` | `git config gitbasher.commit-split-order <auto\|alpha>` | Order split commits by dependency (`auto`, default) or alphabetically (`alpha`) |
|
|
896
900
|
| `gitbasher.commit-ai-grouping` | `git config gitbasher.commit-ai-grouping <never\|auto\|always>` | AI feature grouping for commit splits (default `auto`) |
|
package/dist/gitb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
set -o pipefail
|
|
3
|
-
GITBASHER_VERSION="5.
|
|
3
|
+
GITBASHER_VERSION="5.3.0"
|
|
4
4
|
if [ "$1" == "init" ] || [ "$1" == "i" ]; then
|
|
5
5
|
git init
|
|
6
6
|
fi
|
|
@@ -948,6 +948,11 @@ function print_configuration {
|
|
|
948
948
|
else
|
|
949
949
|
echo -e "\tPush warn:\t${GREEN}${push_warn} MB${ENDCOLOR} $(config_source_tag gitbasher.push-warn-size)"
|
|
950
950
|
fi
|
|
951
|
+
if [ "$(get_fast_split)" = "true" ]; then
|
|
952
|
+
echo -e "\tFast split:\t${GREEN}auto${ENDCOLOR} $(config_source_tag gitbasher.commit-fast-split)"
|
|
953
|
+
else
|
|
954
|
+
echo -e "\tFast split:\t${YELLOW}off${ENDCOLOR} $(config_source_tag gitbasher.commit-fast-split)"
|
|
955
|
+
fi
|
|
951
956
|
echo
|
|
952
957
|
echo -e "${GRAY}Source:${ENDCOLOR} ${BLUE}(project)${ENDCOLOR}${GRAY} this repo,${ENDCOLOR} ${PURPLE}(global)${ENDCOLOR}${GRAY} ~/.gitconfig,${ENDCOLOR} ${GRAY}(default) built-in fallback${ENDCOLOR}"
|
|
953
958
|
}
|
|
@@ -1204,6 +1209,14 @@ function get_push_warn_size {
|
|
|
1204
1209
|
function set_push_warn_size {
|
|
1205
1210
|
set_config_value gitbasher.push-warn-size "$1"
|
|
1206
1211
|
}
|
|
1212
|
+
function get_fast_split {
|
|
1213
|
+
local v
|
|
1214
|
+
v=$(get_config_value gitbasher.commit-fast-split "true")
|
|
1215
|
+
if [ "$v" = "false" ]; then echo "false"; else echo "true"; fi
|
|
1216
|
+
}
|
|
1217
|
+
function set_fast_split {
|
|
1218
|
+
set_config_value gitbasher.commit-fast-split "$1"
|
|
1219
|
+
}
|
|
1207
1220
|
function human_size {
|
|
1208
1221
|
local bytes="${1:-0}"
|
|
1209
1222
|
case "$bytes" in ''|*[!0-9]*) bytes=0;; esac
|
|
@@ -3691,6 +3704,40 @@ function configure_push_warn_size {
|
|
|
3691
3704
|
yes_no_choice "\nSet push size warning globally" "true"
|
|
3692
3705
|
git config --global gitbasher.push-warn-size "$limit_input"
|
|
3693
3706
|
}
|
|
3707
|
+
function configure_fast_split {
|
|
3708
|
+
echo -e "${YELLOW}Configure Fast Mode Split${ENDCOLOR}"
|
|
3709
|
+
echo
|
|
3710
|
+
if [ "$(get_fast_split)" = "true" ]; then
|
|
3711
|
+
echo -e "Current: ${GREEN}auto${ENDCOLOR} (fast modes split without asking)"
|
|
3712
|
+
else
|
|
3713
|
+
echo -e "Current: ${YELLOW}off${ENDCOLOR} (fast asks first, ff commits everything as one)"
|
|
3714
|
+
fi
|
|
3715
|
+
echo
|
|
3716
|
+
echo -e "When staged changes span several scopes, fast commit modes (${BLUE}fast${ENDCOLOR}, ${BLUE}ff${ENDCOLOR}, ...)"
|
|
3717
|
+
echo -e "split them into atomic commits without asking. Turn it off to have ${BLUE}fast${ENDCOLOR}"
|
|
3718
|
+
echo -e "ask first (like a regular commit) and ${BLUE}ff${ENDCOLOR} make a single commit."
|
|
3719
|
+
echo
|
|
3720
|
+
echo -e "1. ${GREEN}auto${ENDCOLOR}:\tsplit automatically in fast modes"
|
|
3721
|
+
echo -e "2. ${YELLOW}off${ENDCOLOR}:\tdon't split by default in fast modes"
|
|
3722
|
+
echo -e "0. Exit without changes"
|
|
3723
|
+
local choice value label
|
|
3724
|
+
while true; do
|
|
3725
|
+
read_key choice || exit
|
|
3726
|
+
case "$choice" in
|
|
3727
|
+
1) value="true"; label="auto"; break ;;
|
|
3728
|
+
2) value="false"; label="off"; break ;;
|
|
3729
|
+
0) exit ;;
|
|
3730
|
+
esac
|
|
3731
|
+
done
|
|
3732
|
+
echo
|
|
3733
|
+
set_fast_split "$value" >/dev/null
|
|
3734
|
+
echo -e "${GREEN}✓ Set fast mode split to ${label} for '${project_name}'${ENDCOLOR}"
|
|
3735
|
+
echo
|
|
3736
|
+
[ "$GITBASHER_NO_REPO" = "true" ] && exit
|
|
3737
|
+
echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
|
|
3738
|
+
yes_no_choice "\nSet fast mode split globally" "true"
|
|
3739
|
+
set_config_value gitbasher.commit-fast-split "$value" "true" >/dev/null
|
|
3740
|
+
}
|
|
3694
3741
|
function configure_ai_diff {
|
|
3695
3742
|
echo -e "${YELLOW}Configure AI Diff Payload Size${ENDCOLOR}"
|
|
3696
3743
|
echo
|
|
@@ -3852,6 +3899,8 @@ function delete_global {
|
|
|
3852
3899
|
_delete_global_add "gitbasher.ai-commit-history-limit" "AI commit history limit" "$global_ai_history" "$GREEN"
|
|
3853
3900
|
local global_push_warn=$(git config --global --get gitbasher.push-warn-size)
|
|
3854
3901
|
_delete_global_add "gitbasher.push-warn-size" "Push size warning" "${global_push_warn:+${global_push_warn} MB}" "$GREEN"
|
|
3902
|
+
local global_fast_split=$(git config --global --get gitbasher.commit-fast-split)
|
|
3903
|
+
_delete_global_add "gitbasher.commit-fast-split" "Fast mode split" "$global_fast_split" "$GREEN"
|
|
3855
3904
|
local global_ai_diff_lines=$(git config --global --get gitbasher.ai-diff-limit)
|
|
3856
3905
|
local global_ai_diff_chars=$(git config --global --get gitbasher.ai-diff-max-chars)
|
|
3857
3906
|
if [ -n "$global_ai_diff_lines" ] || [ -n "$global_ai_diff_chars" ]; then
|
|
@@ -3949,6 +3998,7 @@ function config_script {
|
|
|
3949
3998
|
history|hist) set_ai_history_cfg="true";;
|
|
3950
3999
|
diff|payload) set_ai_diff_cfg="true";;
|
|
3951
4000
|
push-size|pushsize|ps) set_push_warn_cfg="true";;
|
|
4001
|
+
fast-split|fastsplit|fs) set_fast_split_cfg="true";;
|
|
3952
4002
|
delete|unset|del) delete_cfg="true";;
|
|
3953
4003
|
user|name|email|u) set_user_cfg="true";;
|
|
3954
4004
|
auto|completion|comp) auto_cfg="true";;
|
|
@@ -3982,6 +4032,8 @@ function config_script {
|
|
|
3982
4032
|
header="$header AI DIFF PAYLOAD"
|
|
3983
4033
|
elif [ -n "${set_push_warn_cfg}" ]; then
|
|
3984
4034
|
header="$header PUSH SIZE WARNING"
|
|
4035
|
+
elif [ -n "${set_fast_split_cfg}" ]; then
|
|
4036
|
+
header="$header FAST MODE SPLIT"
|
|
3985
4037
|
elif [ -n "${delete_cfg}" ]; then
|
|
3986
4038
|
header="$header UNSET GLOBAL CONFIG"
|
|
3987
4039
|
elif [ -n "${set_user_cfg}" ]; then
|
|
@@ -4055,6 +4107,10 @@ function config_script {
|
|
|
4055
4107
|
configure_push_warn_size
|
|
4056
4108
|
exit
|
|
4057
4109
|
fi
|
|
4110
|
+
if [ "$set_fast_split_cfg" == "true" ]; then
|
|
4111
|
+
configure_fast_split
|
|
4112
|
+
exit
|
|
4113
|
+
fi
|
|
4058
4114
|
if [ "$delete_cfg" == "true" ]; then
|
|
4059
4115
|
delete_global
|
|
4060
4116
|
exit
|
|
@@ -4083,6 +4139,7 @@ function config_script {
|
|
|
4083
4139
|
print_help_row $PAD "history" "hist" "Set how many recent commits to include in AI prompts"
|
|
4084
4140
|
print_help_row $PAD "diff" "payload" "Set the diff payload size (lines and char cap) sent to AI"
|
|
4085
4141
|
print_help_row $PAD "push-size" "ps, pushsize" "Warn before pushing more than N MB (0 disables)"
|
|
4142
|
+
print_help_row $PAD "fast-split" "fs, fastsplit" "Split automatically in fast commit modes (on/off)"
|
|
4086
4143
|
print_help_row $PAD "auto" "completion, comp" "Install/remove tab completion for bash, zsh, or fish"
|
|
4087
4144
|
print_help_row $PAD "delete" "unset, del" "Unset a global gitbasher configuration value"
|
|
4088
4145
|
print_help_row $PAD "help" "h" "Show this help"
|
|
@@ -6972,6 +7029,18 @@ function get_max_split_groups {
|
|
|
6972
7029
|
[ "$v" -gt 20 ] && v=20
|
|
6973
7030
|
printf '%s' "$v"
|
|
6974
7031
|
}
|
|
7032
|
+
function should_attempt_ai_grouping {
|
|
7033
|
+
local scope_count="$1" mode="$2"
|
|
7034
|
+
case "$mode" in
|
|
7035
|
+
always) return 0 ;;
|
|
7036
|
+
never) return 1 ;;
|
|
7037
|
+
esac
|
|
7038
|
+
case "$scope_count" in
|
|
7039
|
+
''|*[!0-9]*) return 1 ;;
|
|
7040
|
+
esac
|
|
7041
|
+
[ "$scope_count" -ge 3 ] && return 0
|
|
7042
|
+
return 1
|
|
7043
|
+
}
|
|
6975
7044
|
#
|
|
6976
7045
|
#
|
|
6977
7046
|
#
|
|
@@ -7146,6 +7215,7 @@ Read <diff> to decide which files implement the same feature. Output TSV (scope<
|
|
|
7146
7215
|
local ai_response
|
|
7147
7216
|
ai_response=$(call_ai_api "$system_prompt" "$user_prompt" "$AI_MAX_TOKENS_FULL" "" "" "low" 2>/dev/null)
|
|
7148
7217
|
if [ $? -ne 0 ] || [ -z "$ai_response" ]; then
|
|
7218
|
+
ai_grouping_fail_reason="API error"
|
|
7149
7219
|
return 1
|
|
7150
7220
|
fi
|
|
7151
7221
|
ai_response=$(printf '%s' "$ai_response" | LC_ALL=C sed -e 's/^```[a-zA-Z]*$//' -e 's/^```$//')
|
|
@@ -7153,6 +7223,14 @@ Read <diff> to decide which files implement the same feature. Output TSV (scope<
|
|
|
7153
7223
|
while IFS= read -r f; do
|
|
7154
7224
|
[ -n "$f" ] && gset_add staged_set "$f"
|
|
7155
7225
|
done <<< "$staged_files"
|
|
7226
|
+
gmap_clear folder_scope_of
|
|
7227
|
+
local _fk _ff
|
|
7228
|
+
for _fk in "${split_group_keys[@]}"; do
|
|
7229
|
+
while IFS= read -r _ff; do
|
|
7230
|
+
[ -n "$_ff" ] && gmap_set folder_scope_of "$_ff" "$_fk"
|
|
7231
|
+
done <<< "$(gmap_get split_groups "$_fk")"
|
|
7232
|
+
done
|
|
7233
|
+
gmap_clear assigned_set
|
|
7156
7234
|
gmap_clear new_groups
|
|
7157
7235
|
local -a new_keys=()
|
|
7158
7236
|
local line scope file tab_count
|
|
@@ -7174,6 +7252,8 @@ Read <diff> to decide which files implement the same feature. Output TSV (scope<
|
|
|
7174
7252
|
fi
|
|
7175
7253
|
scope=$(printf '%s' "$scope" | LC_ALL=C tr '[:upper:]' '[:lower:]')
|
|
7176
7254
|
gset_has staged_set "$file" || continue
|
|
7255
|
+
gset_has assigned_set "$file" && continue
|
|
7256
|
+
gset_add assigned_set "$file"
|
|
7177
7257
|
if ! gmap_has new_groups "$scope"; then
|
|
7178
7258
|
new_keys+=("$scope")
|
|
7179
7259
|
gmap_set new_groups "$scope" "$file"
|
|
@@ -7181,15 +7261,44 @@ Read <diff> to decide which files implement the same feature. Output TSV (scope<
|
|
|
7181
7261
|
gmap_set new_groups "$scope" "$(gmap_get new_groups "$scope")"$'\n'"$file"
|
|
7182
7262
|
fi
|
|
7183
7263
|
done <<< "$ai_response"
|
|
7184
|
-
local total_assigned=0 total_staged
|
|
7264
|
+
local total_assigned=0 total_staged unmatched
|
|
7185
7265
|
local key
|
|
7186
7266
|
for key in "${new_keys[@]}"; do
|
|
7187
7267
|
total_assigned=$((total_assigned + $(printf '%s\n' "$(gmap_get new_groups "$key")" | grep -c .)))
|
|
7188
7268
|
done
|
|
7189
7269
|
total_staged=$(printf '%s\n' "$staged_files" | grep -c .)
|
|
7190
|
-
if [
|
|
7270
|
+
if [ ${#new_keys[@]} -lt 1 ] || [ "$total_assigned" -eq 0 ]; then
|
|
7271
|
+
ai_grouping_fail_reason="no usable grouping returned"
|
|
7191
7272
|
return 1
|
|
7192
7273
|
fi
|
|
7274
|
+
unmatched=$((total_staged - total_assigned))
|
|
7275
|
+
[ "$unmatched" -lt 0 ] && unmatched=0 # dedup should prevent this; be safe
|
|
7276
|
+
if [ "$unmatched" -gt 0 ] && [ $((unmatched * 5)) -gt "$total_staged" ]; then
|
|
7277
|
+
ai_grouping_fail_reason="${unmatched} of ${total_staged} files unassigned"
|
|
7278
|
+
return 1
|
|
7279
|
+
fi
|
|
7280
|
+
if [ "$unmatched" -gt 0 ]; then
|
|
7281
|
+
local single_group=""
|
|
7282
|
+
[ ${#new_keys[@]} -eq 1 ] && single_group="${new_keys[0]}"
|
|
7283
|
+
local sf leftover_scope
|
|
7284
|
+
while IFS= read -r sf; do
|
|
7285
|
+
[ -z "$sf" ] && continue
|
|
7286
|
+
gset_has assigned_set "$sf" && continue
|
|
7287
|
+
if [ -n "$single_group" ]; then
|
|
7288
|
+
leftover_scope="$single_group"
|
|
7289
|
+
else
|
|
7290
|
+
leftover_scope=$(gmap_get folder_scope_of "$sf")
|
|
7291
|
+
[ -z "$leftover_scope" ] && leftover_scope="misc"
|
|
7292
|
+
fi
|
|
7293
|
+
if ! gmap_has new_groups "$leftover_scope"; then
|
|
7294
|
+
new_keys+=("$leftover_scope")
|
|
7295
|
+
gmap_set new_groups "$leftover_scope" "$sf"
|
|
7296
|
+
else
|
|
7297
|
+
gmap_set new_groups "$leftover_scope" "$(gmap_get new_groups "$leftover_scope")"$'\n'"$sf"
|
|
7298
|
+
fi
|
|
7299
|
+
gset_add assigned_set "$sf"
|
|
7300
|
+
done <<< "$staged_files"
|
|
7301
|
+
fi
|
|
7193
7302
|
gmap_clear split_groups
|
|
7194
7303
|
split_group_keys=()
|
|
7195
7304
|
for key in "${new_keys[@]}"; do
|
|
@@ -7243,11 +7352,35 @@ function _restore_split_snapshot {
|
|
|
7243
7352
|
done < "$snapshot"
|
|
7244
7353
|
rm -f "$snapshot"
|
|
7245
7354
|
}
|
|
7355
|
+
function _undo_commit_split {
|
|
7356
|
+
local start_head="$1"
|
|
7357
|
+
local orig_tree="$2"
|
|
7358
|
+
local snapshot="$3"
|
|
7359
|
+
local made="$4"
|
|
7360
|
+
if [ "${made:-0}" -gt 0 ]; then
|
|
7361
|
+
if [ -n "$start_head" ]; then
|
|
7362
|
+
git reset -q --soft "$start_head" >/dev/null 2>&1
|
|
7363
|
+
else
|
|
7364
|
+
git update-ref -d HEAD >/dev/null 2>&1
|
|
7365
|
+
fi
|
|
7366
|
+
fi
|
|
7367
|
+
if [ -n "$orig_tree" ] && git read-tree "$orig_tree" >/dev/null 2>&1; then
|
|
7368
|
+
rm -f "$snapshot"
|
|
7369
|
+
else
|
|
7370
|
+
_restore_split_snapshot "$snapshot"
|
|
7371
|
+
fi
|
|
7372
|
+
}
|
|
7246
7373
|
function print_split_type_menu {
|
|
7247
7374
|
local scope="$1"
|
|
7248
7375
|
local ai_available="$2"
|
|
7376
|
+
local made="${3:-0}"
|
|
7249
7377
|
echo -e "${YELLOW}What type of changes for ${BLUE}${BOLD}${scope}${ENDCOLOR}${YELLOW}?${ENDCOLOR}"
|
|
7250
7378
|
echo -e "Final message will be ${YELLOW}<type>${ENDCOLOR}(${BLUE}<scope>${ENDCOLOR}): ${BLUE}<summary>${ENDCOLOR}"
|
|
7379
|
+
if [ "$made" -gt 0 ]; then
|
|
7380
|
+
echo -e "${GRAY}Changes were split into groups — press ${ENDCOLOR}${BOLD}u${NORMAL}${GRAY} to undo the split (drops the ${made} split commit(s) made so far) and commit everything at once.${ENDCOLOR}"
|
|
7381
|
+
else
|
|
7382
|
+
echo -e "${GRAY}Changes were split into groups — press ${ENDCOLOR}${BOLD}u${NORMAL}${GRAY} to undo the split and commit everything at once.${ENDCOLOR}"
|
|
7383
|
+
fi
|
|
7251
7384
|
echo -e "1. ${BOLD}feat${ENDCOLOR}:\tnew feature, logic change or performance improvement"
|
|
7252
7385
|
echo -e "2. ${BOLD}fix${ENDCOLOR}:\t\tsmall changes, eg. bug fix"
|
|
7253
7386
|
echo -e "3. ${BOLD}refactor${ENDCOLOR}:\tcode change that neither fixes a bug nor adds a feature, style changes"
|
|
@@ -7261,6 +7394,7 @@ function print_split_type_menu {
|
|
|
7261
7394
|
echo -e "g. ${GREEN}${BOLD}ai${ENDCOLOR}:\t\tgenerate commit message using AI"
|
|
7262
7395
|
fi
|
|
7263
7396
|
echo -e "s. ${YELLOW}${BOLD}skip${ENDCOLOR}:\tSkip this group and leave its files unstaged"
|
|
7397
|
+
echo -e "u. ${CYAN}${BOLD}undo${ENDCOLOR}:\tUndo split and commit all changes as a single commit"
|
|
7264
7398
|
echo -e "0. ${RED}${BOLD}abort${ENDCOLOR}:\tAbort split and restore original staging"
|
|
7265
7399
|
echo
|
|
7266
7400
|
}
|
|
@@ -7334,7 +7468,7 @@ function run_split_ai_for_scope {
|
|
|
7334
7468
|
echo
|
|
7335
7469
|
echo -e "${GREEN}AI suggestion:${ENDCOLOR} ${BOLD}$ai_msg${ENDCOLOR}"
|
|
7336
7470
|
echo
|
|
7337
|
-
read_key choice "Use it? (y/e to edit/r to regenerate/s to skip group/0 to abort) " || choice="0"
|
|
7471
|
+
read_key choice "Use it? (y/e to edit/r to regenerate/s to skip group/u to undo split/0 to abort) " || choice="0"
|
|
7338
7472
|
echo
|
|
7339
7473
|
normalize_key "$choice"
|
|
7340
7474
|
while [ "$normalized_key" = "r" ]; do
|
|
@@ -7354,7 +7488,7 @@ ${ai_msg}"
|
|
|
7354
7488
|
echo
|
|
7355
7489
|
echo -e "${GREEN}AI suggestion:${ENDCOLOR} ${BOLD}$ai_msg${ENDCOLOR}"
|
|
7356
7490
|
echo
|
|
7357
|
-
read_key choice "Use it? (y/e to edit/r to regenerate/s to skip group/0 to abort) " || choice="0"
|
|
7491
|
+
read_key choice "Use it? (y/e to edit/r to regenerate/s to skip group/u to undo split/0 to abort) " || choice="0"
|
|
7358
7492
|
echo
|
|
7359
7493
|
normalize_key "$choice"
|
|
7360
7494
|
done
|
|
@@ -7371,6 +7505,8 @@ ${ai_msg}"
|
|
|
7371
7505
|
[ -n "$f" ] && git restore --staged -- ":(top,literal)$f" >/dev/null 2>&1
|
|
7372
7506
|
done <<< "$_files_str"
|
|
7373
7507
|
return 2
|
|
7508
|
+
elif [ "$normalized_key" = "u" ]; then
|
|
7509
|
+
return 4
|
|
7374
7510
|
elif [ "$choice" = "0" ]; then
|
|
7375
7511
|
return 3
|
|
7376
7512
|
fi
|
|
@@ -7384,6 +7520,9 @@ function perform_commit_split {
|
|
|
7384
7520
|
return 1
|
|
7385
7521
|
fi
|
|
7386
7522
|
_capture_split_statuses
|
|
7523
|
+
local start_head orig_tree
|
|
7524
|
+
start_head=$(git rev-parse -q --verify HEAD 2>/dev/null)
|
|
7525
|
+
orig_tree=$(git write-tree 2>/dev/null)
|
|
7387
7526
|
local snapshot_file
|
|
7388
7527
|
snapshot_file=$(mktemp "${TMPDIR:-/tmp}/gitb-split-snapshot.XXXXXX")
|
|
7389
7528
|
chmod 600 "$snapshot_file" 2>/dev/null || true
|
|
@@ -7406,6 +7545,10 @@ function perform_commit_split {
|
|
|
7406
7545
|
fi
|
|
7407
7546
|
local scope files_str msg ai_msg choice scope_for_msg prefix manual_input
|
|
7408
7547
|
local -a files_array
|
|
7548
|
+
if [ -z "$auto_accept" ] && [ -n "$llm" ] && [ "$ai_ok" = "true" ]; then
|
|
7549
|
+
echo
|
|
7550
|
+
echo -e "${GRAY}Tip: press ${ENDCOLOR}${BOLD}u${NORMAL}${GRAY} at a split prompt to undo the split and make a single commit instead.${ENDCOLOR}"
|
|
7551
|
+
fi
|
|
7409
7552
|
for scope in "${split_group_keys[@]}"; do
|
|
7410
7553
|
idx=$((idx + 1))
|
|
7411
7554
|
echo
|
|
@@ -7454,6 +7597,13 @@ function perform_commit_split {
|
|
|
7454
7597
|
run_split_ai_for_scope "$scope" "$scope_for_msg" "$files_str"
|
|
7455
7598
|
case $? in
|
|
7456
7599
|
2) continue ;;
|
|
7600
|
+
4)
|
|
7601
|
+
_undo_commit_split "$start_head" "$orig_tree" "$snapshot_file" "$commit_count"
|
|
7602
|
+
trap - INT TERM
|
|
7603
|
+
echo
|
|
7604
|
+
echo -e "${YELLOW}Split undone, continuing with a single commit.${ENDCOLOR}"
|
|
7605
|
+
return 3
|
|
7606
|
+
;;
|
|
7457
7607
|
3)
|
|
7458
7608
|
_restore_split_snapshot "$snapshot_file"
|
|
7459
7609
|
trap - INT TERM
|
|
@@ -7474,11 +7624,11 @@ function perform_commit_split {
|
|
|
7474
7624
|
local commit_type=""
|
|
7475
7625
|
local is_empty_msg=""
|
|
7476
7626
|
echo
|
|
7477
|
-
print_split_type_menu "$scope" "$ai_ok"
|
|
7627
|
+
print_split_type_menu "$scope" "$ai_ok" "$commit_count"
|
|
7478
7628
|
local tchoice
|
|
7479
7629
|
while true; do
|
|
7480
7630
|
read_key tchoice || tchoice="0"
|
|
7481
|
-
if ! sanitize_choice_input "$tchoice" "^[0-
|
|
7631
|
+
if ! sanitize_choice_input "$tchoice" "^[0-9sgu]$"; then
|
|
7482
7632
|
continue
|
|
7483
7633
|
fi
|
|
7484
7634
|
tchoice="$sanitized_choice"
|
|
@@ -7500,6 +7650,13 @@ function perform_commit_split {
|
|
|
7500
7650
|
case $? in
|
|
7501
7651
|
0) break ;;
|
|
7502
7652
|
2) continue 2 ;;
|
|
7653
|
+
4)
|
|
7654
|
+
_undo_commit_split "$start_head" "$orig_tree" "$snapshot_file" "$commit_count"
|
|
7655
|
+
trap - INT TERM
|
|
7656
|
+
echo
|
|
7657
|
+
echo -e "${YELLOW}Split undone, continuing with a single commit.${ENDCOLOR}"
|
|
7658
|
+
return 3
|
|
7659
|
+
;;
|
|
7503
7660
|
3)
|
|
7504
7661
|
_restore_split_snapshot "$snapshot_file"
|
|
7505
7662
|
trap - INT TERM
|
|
@@ -7509,10 +7666,17 @@ function perform_commit_split {
|
|
|
7509
7666
|
;;
|
|
7510
7667
|
*)
|
|
7511
7668
|
echo
|
|
7512
|
-
print_split_type_menu "$scope" "$ai_ok"
|
|
7669
|
+
print_split_type_menu "$scope" "$ai_ok" "$commit_count"
|
|
7513
7670
|
;;
|
|
7514
7671
|
esac
|
|
7515
7672
|
;;
|
|
7673
|
+
u)
|
|
7674
|
+
_undo_commit_split "$start_head" "$orig_tree" "$snapshot_file" "$commit_count"
|
|
7675
|
+
trap - INT TERM
|
|
7676
|
+
echo
|
|
7677
|
+
echo -e "${YELLOW}Split undone, continuing with a single commit.${ENDCOLOR}"
|
|
7678
|
+
return 3
|
|
7679
|
+
;;
|
|
7516
7680
|
s)
|
|
7517
7681
|
echo -e "${YELLOW}Skipping scope '${scope}' (files left unstaged)${ENDCOLOR}"
|
|
7518
7682
|
while IFS= read -r f; do
|
|
@@ -7606,18 +7770,35 @@ function try_offer_commit_split {
|
|
|
7606
7770
|
local ai_grouping
|
|
7607
7771
|
ai_grouping=$(get_config_value gitbasher.commit-ai-grouping "auto")
|
|
7608
7772
|
local should_use_ai="false"
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7773
|
+
if [ -n "$llm" ] && should_attempt_ai_grouping "${#split_group_keys[@]}" "$ai_grouping"; then
|
|
7774
|
+
should_use_ai="true"
|
|
7775
|
+
fi
|
|
7776
|
+
if [ ${#split_group_keys[@]} -lt 2 ] && [ "$should_use_ai" != "true" ]; then
|
|
7777
|
+
if [ -n "$force_split" ]; then
|
|
7778
|
+
echo -e "${YELLOW}Cannot split: all changes look like a single feature.${ENDCOLOR}"
|
|
7779
|
+
fi
|
|
7780
|
+
return 1
|
|
7781
|
+
fi
|
|
7782
|
+
local choice
|
|
7783
|
+
if [ -z "$force_split" ] && [ -z "$auto_yes" ] && [ "$auto_split" != "always" ]; then
|
|
7784
|
+
echo
|
|
7785
|
+
if [ ${#split_group_keys[@]} -ge 2 ]; then
|
|
7786
|
+
echo -e "${YELLOW}Staged changes span ${#split_group_keys[@]} scopes:${ENDCOLOR} ${BLUE}${split_group_keys[*]}${ENDCOLOR}"
|
|
7787
|
+
fi
|
|
7788
|
+
read_key choice "Try to split them into atomic commits? (y/N) " || choice=""
|
|
7789
|
+
echo
|
|
7790
|
+
if [ -z "$choice" ] || ! is_yes "$choice"; then
|
|
7791
|
+
return 1
|
|
7792
|
+
fi
|
|
7615
7793
|
fi
|
|
7616
7794
|
if [ "$should_use_ai" = "true" ] && check_ai_available 2>/dev/null; then
|
|
7617
7795
|
echo
|
|
7618
7796
|
echo -e "${YELLOW}Grouping changes by feature with AI...${ENDCOLOR}"
|
|
7797
|
+
local ai_grouping_fail_reason=""
|
|
7619
7798
|
if ! group_files_by_feature_with_ai; then
|
|
7620
|
-
|
|
7799
|
+
local reason_suffix=""
|
|
7800
|
+
[ -n "$ai_grouping_fail_reason" ] && reason_suffix=" (${ai_grouping_fail_reason})"
|
|
7801
|
+
echo -e "${YELLOW}AI feature grouping failed${reason_suffix}, using folder-based grouping.${ENDCOLOR}"
|
|
7621
7802
|
fi
|
|
7622
7803
|
consolidate_split_groups "$(get_max_split_groups)"
|
|
7623
7804
|
fi
|
|
@@ -7631,7 +7812,6 @@ function try_offer_commit_split {
|
|
|
7631
7812
|
echo
|
|
7632
7813
|
print_split_groups_preview
|
|
7633
7814
|
echo
|
|
7634
|
-
local choice
|
|
7635
7815
|
if [ -n "$force_split" ] || [ -n "$auto_yes" ] || [ "$auto_split" = "always" ]; then
|
|
7636
7816
|
choice="y"
|
|
7637
7817
|
else
|
|
@@ -7641,8 +7821,13 @@ function try_offer_commit_split {
|
|
|
7641
7821
|
if [ -z "$choice" ] || ! is_yes "$choice"; then
|
|
7642
7822
|
return 1
|
|
7643
7823
|
fi
|
|
7644
|
-
|
|
7645
|
-
|
|
7824
|
+
local split_rc=0
|
|
7825
|
+
perform_commit_split || split_rc=$?
|
|
7826
|
+
case "$split_rc" in
|
|
7827
|
+
0) return 0 ;;
|
|
7828
|
+
3) return 3 ;;
|
|
7829
|
+
*) return 2 ;;
|
|
7830
|
+
esac
|
|
7646
7831
|
}
|
|
7647
7832
|
function handle_ai_commit_generation {
|
|
7648
7833
|
local step="$1"
|
|
@@ -8071,7 +8256,7 @@ function commit_script {
|
|
|
8071
8256
|
print_help_row $FPAD "staged" "st" "Use already-staged files (skip the add step)"
|
|
8072
8257
|
print_help_row $FPAD "push" "p, pu" "Push after the commit succeeds"
|
|
8073
8258
|
print_help_row $FPAD "scope" "s" "Force a scope: 'type(scope): message' (useful with fast mode)"
|
|
8074
|
-
print_help_row $FPAD "no-split" "nos, nsp, nsl" "
|
|
8259
|
+
print_help_row $FPAD "no-split" "nos, nsp, nsl" "Don't offer to split this commit (fast modes split by default)"
|
|
8075
8260
|
print_help_row $FPAD "ai" "i, llm" "Generate the commit message with AI"
|
|
8076
8261
|
print_help_row $FPAD "msg" "m" "Open \$EDITOR for a multiline message body"
|
|
8077
8262
|
print_help_row $FPAD "ticket" "t, j, jira" "Append ticket info to the header"
|
|
@@ -8090,6 +8275,8 @@ function commit_script {
|
|
|
8090
8275
|
echo -e " ${BLUE}•${ENDCOLOR} Word order doesn't matter: ${GREEN}ai fast push${ENDCOLOR} == ${GREEN}push fast ai${ENDCOLOR} == ${GREEN}aifp${ENDCOLOR}"
|
|
8091
8276
|
echo -e " ${BLUE}•${ENDCOLOR} Modifiers stack on actions: ${GREEN}ai+fixup${ENDCOLOR}, ${GREEN}fast+amend${ENDCOLOR}, ${GREEN}split+push${ENDCOLOR}, ${GREEN}ai+staged${ENDCOLOR}, ..."
|
|
8092
8277
|
echo -e " ${BLUE}•${ENDCOLOR} ${BOLD}fast${NORMAL} and ${BOLD}staged${NORMAL} are mutually exclusive (one stages all, the other uses what's staged)"
|
|
8278
|
+
echo -e " ${BLUE}•${ENDCOLOR} Regular commits ask before splitting; fast modes split by default (toggle: ${GREEN}gitb cfg fast-split${ENDCOLOR})"
|
|
8279
|
+
echo -e " ${BLUE}•${ENDCOLOR} Press ${BOLD}u${NORMAL} at a split prompt to undo the split and commit everything as one"
|
|
8093
8280
|
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}"
|
|
8094
8281
|
git config --unset gitbasher.cached-git-add 2>/dev/null || true
|
|
8095
8282
|
exit 0
|
|
@@ -8244,14 +8431,19 @@ function commit_script {
|
|
|
8244
8431
|
else
|
|
8245
8432
|
staged_files_list="$(sed 's/^/\t/' <<< "$(git -c core.quotePath=false diff --no-renames --name-only --cached)")"
|
|
8246
8433
|
fi
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8434
|
+
_split_skip="${no_split}"
|
|
8435
|
+
_split_auto_yes=""
|
|
8436
|
+
if [ -z "$split" ] && { [ -n "$fast" ] || [ -n "$auto_accept" ]; }; then
|
|
8437
|
+
if [ "$(get_fast_split)" = "false" ]; then
|
|
8438
|
+
[ -n "$auto_accept" ] && _split_skip="true"
|
|
8439
|
+
else
|
|
8250
8440
|
_split_auto_yes="true"
|
|
8251
8441
|
fi
|
|
8442
|
+
fi
|
|
8443
|
+
if [ -z "${amend}" ] && [ -z "${fixup}" ] && [ -z "${msg}" ] && [ -z "${ticket}" ] && [ -z "${_split_skip}" ]; then
|
|
8252
8444
|
try_offer_commit_split "$split" "$_split_auto_yes"
|
|
8253
8445
|
_split_rc=$?
|
|
8254
|
-
if [ -n "$split" ]; then
|
|
8446
|
+
if [ -n "$split" ] && [ "$_split_rc" -ne 3 ]; then
|
|
8255
8447
|
if [ "$_split_rc" -ge 2 ]; then
|
|
8256
8448
|
exit 1
|
|
8257
8449
|
fi
|
|
@@ -9123,7 +9315,7 @@ function branch_script {
|
|
|
9123
9315
|
"$main_branch"|"$current_branch"|master|main|develop) continue ;;
|
|
9124
9316
|
esac
|
|
9125
9317
|
merged_to_delete+=("$_mb")
|
|
9126
|
-
done < <(git branch --merged --format='%(refname:short)')
|
|
9318
|
+
done < <(git branch --merged HEAD --format='%(refname:short)')
|
|
9127
9319
|
for branch_to_delete in "${merged_to_delete[@]}"; do
|
|
9128
9320
|
delete_output=$(git branch -d "$branch_to_delete" 2>&1)
|
|
9129
9321
|
delete_code=$?
|
|
@@ -15428,7 +15620,7 @@ _gitb_sub_sync="push p merge m mergep mp pm dry d dr help h"
|
|
|
15428
15620
|
_gitb_sub_wip="up u down d help h"
|
|
15429
15621
|
_gitb_sub_branch="list l remote r re main def m new n c newd nd cd delete del d prev p - recent rc gone g tag t help h"
|
|
15430
15622
|
_gitb_sub_tag="annotated a an commit c co cm all al push ps ph p push-all pa delete del d delete-all da list log l remote re r fetch help h"
|
|
15431
|
-
_gitb_sub_config="default def d b main separator sep editor ed e ticket jira ti t scopes scope sc s ai llm key provider prov model m proxy prx p history hist diff payload push-size pushsize ps delete unset del user name email u auto completion comp help h"
|
|
15623
|
+
_gitb_sub_config="default def d b main separator sep editor ed e ticket jira ti t scopes scope sc s ai llm key provider prov model m proxy prx p history hist diff payload push-size pushsize ps fast-split fastsplit fs delete unset del user name email u auto completion comp help h"
|
|
15432
15624
|
_gitb_sub_undo="commit c amend a merge m rebase r stash s help h"
|
|
15433
15625
|
_gitb_sub_reset="soft s undo u interactive i ref r help h"
|
|
15434
15626
|
_gitb_sub_stash="select sel all list l pop p show s drop d apply a help h"
|
|
@@ -15777,7 +15969,7 @@ _gitb() {
|
|
|
15777
15969
|
_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]'
|
|
15778
15970
|
;;
|
|
15779
15971
|
config|cf|cfg|conf)
|
|
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]'
|
|
15972
|
+
_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]' 'fast-split[split in fast commit modes]' 'delete[unset a key]' 'user[user name/email]' 'auto[manage tab completion]' 'help[show help]'
|
|
15781
15973
|
;;
|
|
15782
15974
|
undo|un)
|
|
15783
15975
|
_values 'undo target' 'commit[undo last commit]' 'amend[undo amend]' 'merge[undo merge]' 'rebase[undo rebase]' 'stash[undo stash]' 'help[show help]'
|
|
@@ -15979,7 +16171,7 @@ complete -c gitb -n "$__gitb_branch" -a "list remote main new newd delete prev r
|
|
|
15979
16171
|
set -l __gitb_tag "__gitb_using_cmd tag t tg; and __gitb_at_position 2"
|
|
15980
16172
|
complete -c gitb -n "$__gitb_tag" -a "annotated commit all push push-all delete delete-all list remote help"
|
|
15981
16173
|
set -l __gitb_config "__gitb_using_cmd config cf cfg conf; and __gitb_at_position 2"
|
|
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"
|
|
16174
|
+
complete -c gitb -n "$__gitb_config" -a "default separator editor ticket scopes ai key provider model proxy history diff push-size fast-split delete user auto help"
|
|
15983
16175
|
set -l __gitb_undo "__gitb_using_cmd undo un; and __gitb_at_position 2"
|
|
15984
16176
|
complete -c gitb -n "$__gitb_undo" -a "commit amend merge rebase stash help"
|
|
15985
16177
|
set -l __gitb_reset "__gitb_using_cmd reset res; and __gitb_at_position 2"
|