gitbasher 5.2.1 → 5.3.1

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 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.1](https://github.com/maxbolgarin/gitbasher/compare/v5.3.0...v5.3.1) (2026-10-13)
6
+
7
+ ### 🐛 Bug Fixes
8
+
9
+ * **push:** avoid fatal errors when pushing a branch with no own commits ([d8ff3f8](https://github.com/maxbolgarin/gitbasher/commit/d8ff3f83340101f8d4992bcecebac443d5fa8e8e))
10
+
11
+ ## [5.3.0](https://github.com/maxbolgarin/gitbasher/compare/v5.2.1...v5.3.0) (2026-09-30)
12
+
13
+ ### 🚀 Features
14
+
15
+ * **config:** add `gitb cfg fast-split` to toggle split in fast modes ([c56e173](https://github.com/maxbolgarin/gitbasher/commit/c56e1735efeaea8c71f83e5d983cd24ff26fcd2b))
16
+ * **commit:** ask before splitting, add undo split and fast-split setting ([1de1fcd](https://github.com/maxbolgarin/gitbasher/commit/1de1fcd523b5080da0c8248347558a421c8d72bb))
17
+ * **commit:** show undo-split hint in the split type menu ([520e66a](https://github.com/maxbolgarin/gitbasher/commit/520e66aec25b43b7fb83b27f863cc6af4a0ab0e2))
18
+
19
+ ### 🐛 Bug Fixes
20
+
21
+ * **branch:** pass HEAD to --merged when deleting merged branches ([2cdaf58](https://github.com/maxbolgarin/gitbasher/commit/2cdaf58c3a92e9f26eba47bd180b582cb9676d04))
22
+
5
23
  ## [5.2.1](https://github.com/maxbolgarin/gitbasher/compare/v5.2.0...v5.2.1) (2026-07-11)
6
24
 
7
25
  ### 🐛 Bug Fixes
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` | Disable automatic split detection for this commit |
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.2.1"
3
+ GITBASHER_VERSION="5.3.1"
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
@@ -1259,17 +1272,18 @@ function get_push_list {
1259
1272
  fi
1260
1273
  fi
1261
1274
  base_commit=$(diff -u <(git rev-list --first-parent "$1" 2>/dev/null) <(git rev-list --first-parent "$2" 2>/dev/null) | sed -ne 's/^ //p' | head -1 || true)
1275
+ if [ -z "$base_commit" ]; then
1276
+ base_commit=$(git merge-base "$1" "$2" 2>/dev/null || true)
1277
+ fi
1262
1278
  if [ -n "$base_commit" ]; then
1263
1279
  push_list=$(commit_list 999 "tab" "$base_commit..HEAD")
1264
1280
  history_from="${base_commit::7}"
1281
+ elif git rev-parse --verify --quiet "$origin/$2" >/dev/null 2>&1; then
1282
+ push_list=$(commit_list 999 "tab" "$origin/$2..HEAD")
1283
+ history_from="$origin/$2"
1265
1284
  else
1266
- if [ -z "$origin" ]; then
1267
- push_list=""
1268
- history_from=""
1269
- else
1270
- push_list=$(commit_list 999 "tab" "$origin/$2..HEAD")
1271
- history_from="$origin/$2"
1272
- fi
1285
+ push_list=$(commit_list 999 "tab")
1286
+ history_from="HEAD"
1273
1287
  fi
1274
1288
  }
1275
1289
  function list_branches {
@@ -3691,6 +3705,40 @@ function configure_push_warn_size {
3691
3705
  yes_no_choice "\nSet push size warning globally" "true"
3692
3706
  git config --global gitbasher.push-warn-size "$limit_input"
3693
3707
  }
3708
+ function configure_fast_split {
3709
+ echo -e "${YELLOW}Configure Fast Mode Split${ENDCOLOR}"
3710
+ echo
3711
+ if [ "$(get_fast_split)" = "true" ]; then
3712
+ echo -e "Current: ${GREEN}auto${ENDCOLOR} (fast modes split without asking)"
3713
+ else
3714
+ echo -e "Current: ${YELLOW}off${ENDCOLOR} (fast asks first, ff commits everything as one)"
3715
+ fi
3716
+ echo
3717
+ echo -e "When staged changes span several scopes, fast commit modes (${BLUE}fast${ENDCOLOR}, ${BLUE}ff${ENDCOLOR}, ...)"
3718
+ echo -e "split them into atomic commits without asking. Turn it off to have ${BLUE}fast${ENDCOLOR}"
3719
+ echo -e "ask first (like a regular commit) and ${BLUE}ff${ENDCOLOR} make a single commit."
3720
+ echo
3721
+ echo -e "1. ${GREEN}auto${ENDCOLOR}:\tsplit automatically in fast modes"
3722
+ echo -e "2. ${YELLOW}off${ENDCOLOR}:\tdon't split by default in fast modes"
3723
+ echo -e "0. Exit without changes"
3724
+ local choice value label
3725
+ while true; do
3726
+ read_key choice || exit
3727
+ case "$choice" in
3728
+ 1) value="true"; label="auto"; break ;;
3729
+ 2) value="false"; label="off"; break ;;
3730
+ 0) exit ;;
3731
+ esac
3732
+ done
3733
+ echo
3734
+ set_fast_split "$value" >/dev/null
3735
+ echo -e "${GREEN}✓ Set fast mode split to ${label} for '${project_name}'${ENDCOLOR}"
3736
+ echo
3737
+ [ "$GITBASHER_NO_REPO" = "true" ] && exit
3738
+ echo -e "Do you want to set it ${YELLOW}globally${ENDCOLOR} for all projects (Y/n)?"
3739
+ yes_no_choice "\nSet fast mode split globally" "true"
3740
+ set_config_value gitbasher.commit-fast-split "$value" "true" >/dev/null
3741
+ }
3694
3742
  function configure_ai_diff {
3695
3743
  echo -e "${YELLOW}Configure AI Diff Payload Size${ENDCOLOR}"
3696
3744
  echo
@@ -3852,6 +3900,8 @@ function delete_global {
3852
3900
  _delete_global_add "gitbasher.ai-commit-history-limit" "AI commit history limit" "$global_ai_history" "$GREEN"
3853
3901
  local global_push_warn=$(git config --global --get gitbasher.push-warn-size)
3854
3902
  _delete_global_add "gitbasher.push-warn-size" "Push size warning" "${global_push_warn:+${global_push_warn} MB}" "$GREEN"
3903
+ local global_fast_split=$(git config --global --get gitbasher.commit-fast-split)
3904
+ _delete_global_add "gitbasher.commit-fast-split" "Fast mode split" "$global_fast_split" "$GREEN"
3855
3905
  local global_ai_diff_lines=$(git config --global --get gitbasher.ai-diff-limit)
3856
3906
  local global_ai_diff_chars=$(git config --global --get gitbasher.ai-diff-max-chars)
3857
3907
  if [ -n "$global_ai_diff_lines" ] || [ -n "$global_ai_diff_chars" ]; then
@@ -3949,6 +3999,7 @@ function config_script {
3949
3999
  history|hist) set_ai_history_cfg="true";;
3950
4000
  diff|payload) set_ai_diff_cfg="true";;
3951
4001
  push-size|pushsize|ps) set_push_warn_cfg="true";;
4002
+ fast-split|fastsplit|fs) set_fast_split_cfg="true";;
3952
4003
  delete|unset|del) delete_cfg="true";;
3953
4004
  user|name|email|u) set_user_cfg="true";;
3954
4005
  auto|completion|comp) auto_cfg="true";;
@@ -3982,6 +4033,8 @@ function config_script {
3982
4033
  header="$header AI DIFF PAYLOAD"
3983
4034
  elif [ -n "${set_push_warn_cfg}" ]; then
3984
4035
  header="$header PUSH SIZE WARNING"
4036
+ elif [ -n "${set_fast_split_cfg}" ]; then
4037
+ header="$header FAST MODE SPLIT"
3985
4038
  elif [ -n "${delete_cfg}" ]; then
3986
4039
  header="$header UNSET GLOBAL CONFIG"
3987
4040
  elif [ -n "${set_user_cfg}" ]; then
@@ -4055,6 +4108,10 @@ function config_script {
4055
4108
  configure_push_warn_size
4056
4109
  exit
4057
4110
  fi
4111
+ if [ "$set_fast_split_cfg" == "true" ]; then
4112
+ configure_fast_split
4113
+ exit
4114
+ fi
4058
4115
  if [ "$delete_cfg" == "true" ]; then
4059
4116
  delete_global
4060
4117
  exit
@@ -4083,6 +4140,7 @@ function config_script {
4083
4140
  print_help_row $PAD "history" "hist" "Set how many recent commits to include in AI prompts"
4084
4141
  print_help_row $PAD "diff" "payload" "Set the diff payload size (lines and char cap) sent to AI"
4085
4142
  print_help_row $PAD "push-size" "ps, pushsize" "Warn before pushing more than N MB (0 disables)"
4143
+ print_help_row $PAD "fast-split" "fs, fastsplit" "Split automatically in fast commit modes (on/off)"
4086
4144
  print_help_row $PAD "auto" "completion, comp" "Install/remove tab completion for bash, zsh, or fish"
4087
4145
  print_help_row $PAD "delete" "unset, del" "Unset a global gitbasher configuration value"
4088
4146
  print_help_row $PAD "help" "h" "Show this help"
@@ -7295,11 +7353,35 @@ function _restore_split_snapshot {
7295
7353
  done < "$snapshot"
7296
7354
  rm -f "$snapshot"
7297
7355
  }
7356
+ function _undo_commit_split {
7357
+ local start_head="$1"
7358
+ local orig_tree="$2"
7359
+ local snapshot="$3"
7360
+ local made="$4"
7361
+ if [ "${made:-0}" -gt 0 ]; then
7362
+ if [ -n "$start_head" ]; then
7363
+ git reset -q --soft "$start_head" >/dev/null 2>&1
7364
+ else
7365
+ git update-ref -d HEAD >/dev/null 2>&1
7366
+ fi
7367
+ fi
7368
+ if [ -n "$orig_tree" ] && git read-tree "$orig_tree" >/dev/null 2>&1; then
7369
+ rm -f "$snapshot"
7370
+ else
7371
+ _restore_split_snapshot "$snapshot"
7372
+ fi
7373
+ }
7298
7374
  function print_split_type_menu {
7299
7375
  local scope="$1"
7300
7376
  local ai_available="$2"
7377
+ local made="${3:-0}"
7301
7378
  echo -e "${YELLOW}What type of changes for ${BLUE}${BOLD}${scope}${ENDCOLOR}${YELLOW}?${ENDCOLOR}"
7302
7379
  echo -e "Final message will be ${YELLOW}<type>${ENDCOLOR}(${BLUE}<scope>${ENDCOLOR}): ${BLUE}<summary>${ENDCOLOR}"
7380
+ if [ "$made" -gt 0 ]; then
7381
+ 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}"
7382
+ else
7383
+ echo -e "${GRAY}Changes were split into groups — press ${ENDCOLOR}${BOLD}u${NORMAL}${GRAY} to undo the split and commit everything at once.${ENDCOLOR}"
7384
+ fi
7303
7385
  echo -e "1. ${BOLD}feat${ENDCOLOR}:\tnew feature, logic change or performance improvement"
7304
7386
  echo -e "2. ${BOLD}fix${ENDCOLOR}:\t\tsmall changes, eg. bug fix"
7305
7387
  echo -e "3. ${BOLD}refactor${ENDCOLOR}:\tcode change that neither fixes a bug nor adds a feature, style changes"
@@ -7313,6 +7395,7 @@ function print_split_type_menu {
7313
7395
  echo -e "g. ${GREEN}${BOLD}ai${ENDCOLOR}:\t\tgenerate commit message using AI"
7314
7396
  fi
7315
7397
  echo -e "s. ${YELLOW}${BOLD}skip${ENDCOLOR}:\tSkip this group and leave its files unstaged"
7398
+ echo -e "u. ${CYAN}${BOLD}undo${ENDCOLOR}:\tUndo split and commit all changes as a single commit"
7316
7399
  echo -e "0. ${RED}${BOLD}abort${ENDCOLOR}:\tAbort split and restore original staging"
7317
7400
  echo
7318
7401
  }
@@ -7386,7 +7469,7 @@ function run_split_ai_for_scope {
7386
7469
  echo
7387
7470
  echo -e "${GREEN}AI suggestion:${ENDCOLOR} ${BOLD}$ai_msg${ENDCOLOR}"
7388
7471
  echo
7389
- read_key choice "Use it? (y/e to edit/r to regenerate/s to skip group/0 to abort) " || choice="0"
7472
+ 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"
7390
7473
  echo
7391
7474
  normalize_key "$choice"
7392
7475
  while [ "$normalized_key" = "r" ]; do
@@ -7406,7 +7489,7 @@ ${ai_msg}"
7406
7489
  echo
7407
7490
  echo -e "${GREEN}AI suggestion:${ENDCOLOR} ${BOLD}$ai_msg${ENDCOLOR}"
7408
7491
  echo
7409
- read_key choice "Use it? (y/e to edit/r to regenerate/s to skip group/0 to abort) " || choice="0"
7492
+ 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"
7410
7493
  echo
7411
7494
  normalize_key "$choice"
7412
7495
  done
@@ -7423,6 +7506,8 @@ ${ai_msg}"
7423
7506
  [ -n "$f" ] && git restore --staged -- ":(top,literal)$f" >/dev/null 2>&1
7424
7507
  done <<< "$_files_str"
7425
7508
  return 2
7509
+ elif [ "$normalized_key" = "u" ]; then
7510
+ return 4
7426
7511
  elif [ "$choice" = "0" ]; then
7427
7512
  return 3
7428
7513
  fi
@@ -7436,6 +7521,9 @@ function perform_commit_split {
7436
7521
  return 1
7437
7522
  fi
7438
7523
  _capture_split_statuses
7524
+ local start_head orig_tree
7525
+ start_head=$(git rev-parse -q --verify HEAD 2>/dev/null)
7526
+ orig_tree=$(git write-tree 2>/dev/null)
7439
7527
  local snapshot_file
7440
7528
  snapshot_file=$(mktemp "${TMPDIR:-/tmp}/gitb-split-snapshot.XXXXXX")
7441
7529
  chmod 600 "$snapshot_file" 2>/dev/null || true
@@ -7458,6 +7546,10 @@ function perform_commit_split {
7458
7546
  fi
7459
7547
  local scope files_str msg ai_msg choice scope_for_msg prefix manual_input
7460
7548
  local -a files_array
7549
+ if [ -z "$auto_accept" ] && [ -n "$llm" ] && [ "$ai_ok" = "true" ]; then
7550
+ echo
7551
+ 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}"
7552
+ fi
7461
7553
  for scope in "${split_group_keys[@]}"; do
7462
7554
  idx=$((idx + 1))
7463
7555
  echo
@@ -7506,6 +7598,13 @@ function perform_commit_split {
7506
7598
  run_split_ai_for_scope "$scope" "$scope_for_msg" "$files_str"
7507
7599
  case $? in
7508
7600
  2) continue ;;
7601
+ 4)
7602
+ _undo_commit_split "$start_head" "$orig_tree" "$snapshot_file" "$commit_count"
7603
+ trap - INT TERM
7604
+ echo
7605
+ echo -e "${YELLOW}Split undone, continuing with a single commit.${ENDCOLOR}"
7606
+ return 3
7607
+ ;;
7509
7608
  3)
7510
7609
  _restore_split_snapshot "$snapshot_file"
7511
7610
  trap - INT TERM
@@ -7526,11 +7625,11 @@ function perform_commit_split {
7526
7625
  local commit_type=""
7527
7626
  local is_empty_msg=""
7528
7627
  echo
7529
- print_split_type_menu "$scope" "$ai_ok"
7628
+ print_split_type_menu "$scope" "$ai_ok" "$commit_count"
7530
7629
  local tchoice
7531
7630
  while true; do
7532
7631
  read_key tchoice || tchoice="0"
7533
- if ! sanitize_choice_input "$tchoice" "^[0-9sg]$"; then
7632
+ if ! sanitize_choice_input "$tchoice" "^[0-9sgu]$"; then
7534
7633
  continue
7535
7634
  fi
7536
7635
  tchoice="$sanitized_choice"
@@ -7552,6 +7651,13 @@ function perform_commit_split {
7552
7651
  case $? in
7553
7652
  0) break ;;
7554
7653
  2) continue 2 ;;
7654
+ 4)
7655
+ _undo_commit_split "$start_head" "$orig_tree" "$snapshot_file" "$commit_count"
7656
+ trap - INT TERM
7657
+ echo
7658
+ echo -e "${YELLOW}Split undone, continuing with a single commit.${ENDCOLOR}"
7659
+ return 3
7660
+ ;;
7555
7661
  3)
7556
7662
  _restore_split_snapshot "$snapshot_file"
7557
7663
  trap - INT TERM
@@ -7561,10 +7667,17 @@ function perform_commit_split {
7561
7667
  ;;
7562
7668
  *)
7563
7669
  echo
7564
- print_split_type_menu "$scope" "$ai_ok"
7670
+ print_split_type_menu "$scope" "$ai_ok" "$commit_count"
7565
7671
  ;;
7566
7672
  esac
7567
7673
  ;;
7674
+ u)
7675
+ _undo_commit_split "$start_head" "$orig_tree" "$snapshot_file" "$commit_count"
7676
+ trap - INT TERM
7677
+ echo
7678
+ echo -e "${YELLOW}Split undone, continuing with a single commit.${ENDCOLOR}"
7679
+ return 3
7680
+ ;;
7568
7681
  s)
7569
7682
  echo -e "${YELLOW}Skipping scope '${scope}' (files left unstaged)${ENDCOLOR}"
7570
7683
  while IFS= read -r f; do
@@ -7661,6 +7774,24 @@ function try_offer_commit_split {
7661
7774
  if [ -n "$llm" ] && should_attempt_ai_grouping "${#split_group_keys[@]}" "$ai_grouping"; then
7662
7775
  should_use_ai="true"
7663
7776
  fi
7777
+ if [ ${#split_group_keys[@]} -lt 2 ] && [ "$should_use_ai" != "true" ]; then
7778
+ if [ -n "$force_split" ]; then
7779
+ echo -e "${YELLOW}Cannot split: all changes look like a single feature.${ENDCOLOR}"
7780
+ fi
7781
+ return 1
7782
+ fi
7783
+ local choice
7784
+ if [ -z "$force_split" ] && [ -z "$auto_yes" ] && [ "$auto_split" != "always" ]; then
7785
+ echo
7786
+ if [ ${#split_group_keys[@]} -ge 2 ]; then
7787
+ echo -e "${YELLOW}Staged changes span ${#split_group_keys[@]} scopes:${ENDCOLOR} ${BLUE}${split_group_keys[*]}${ENDCOLOR}"
7788
+ fi
7789
+ read_key choice "Try to split them into atomic commits? (y/N) " || choice=""
7790
+ echo
7791
+ if [ -z "$choice" ] || ! is_yes "$choice"; then
7792
+ return 1
7793
+ fi
7794
+ fi
7664
7795
  if [ "$should_use_ai" = "true" ] && check_ai_available 2>/dev/null; then
7665
7796
  echo
7666
7797
  echo -e "${YELLOW}Grouping changes by feature with AI...${ENDCOLOR}"
@@ -7682,7 +7813,6 @@ function try_offer_commit_split {
7682
7813
  echo
7683
7814
  print_split_groups_preview
7684
7815
  echo
7685
- local choice
7686
7816
  if [ -n "$force_split" ] || [ -n "$auto_yes" ] || [ "$auto_split" = "always" ]; then
7687
7817
  choice="y"
7688
7818
  else
@@ -7692,8 +7822,13 @@ function try_offer_commit_split {
7692
7822
  if [ -z "$choice" ] || ! is_yes "$choice"; then
7693
7823
  return 1
7694
7824
  fi
7695
- perform_commit_split || return 2
7696
- return 0
7825
+ local split_rc=0
7826
+ perform_commit_split || split_rc=$?
7827
+ case "$split_rc" in
7828
+ 0) return 0 ;;
7829
+ 3) return 3 ;;
7830
+ *) return 2 ;;
7831
+ esac
7697
7832
  }
7698
7833
  function handle_ai_commit_generation {
7699
7834
  local step="$1"
@@ -8122,7 +8257,7 @@ function commit_script {
8122
8257
  print_help_row $FPAD "staged" "st" "Use already-staged files (skip the add step)"
8123
8258
  print_help_row $FPAD "push" "p, pu" "Push after the commit succeeds"
8124
8259
  print_help_row $FPAD "scope" "s" "Force a scope: 'type(scope): message' (useful with fast mode)"
8125
- print_help_row $FPAD "no-split" "nos, nsp, nsl" "Disable automatic split detection for this commit"
8260
+ print_help_row $FPAD "no-split" "nos, nsp, nsl" "Don't offer to split this commit (fast modes split by default)"
8126
8261
  print_help_row $FPAD "ai" "i, llm" "Generate the commit message with AI"
8127
8262
  print_help_row $FPAD "msg" "m" "Open \$EDITOR for a multiline message body"
8128
8263
  print_help_row $FPAD "ticket" "t, j, jira" "Append ticket info to the header"
@@ -8141,6 +8276,8 @@ function commit_script {
8141
8276
  echo -e " ${BLUE}•${ENDCOLOR} Word order doesn't matter: ${GREEN}ai fast push${ENDCOLOR} == ${GREEN}push fast ai${ENDCOLOR} == ${GREEN}aifp${ENDCOLOR}"
8142
8277
  echo -e " ${BLUE}•${ENDCOLOR} Modifiers stack on actions: ${GREEN}ai+fixup${ENDCOLOR}, ${GREEN}fast+amend${ENDCOLOR}, ${GREEN}split+push${ENDCOLOR}, ${GREEN}ai+staged${ENDCOLOR}, ..."
8143
8278
  echo -e " ${BLUE}•${ENDCOLOR} ${BOLD}fast${NORMAL} and ${BOLD}staged${NORMAL} are mutually exclusive (one stages all, the other uses what's staged)"
8279
+ echo -e " ${BLUE}•${ENDCOLOR} Regular commits ask before splitting; fast modes split by default (toggle: ${GREEN}gitb cfg fast-split${ENDCOLOR})"
8280
+ echo -e " ${BLUE}•${ENDCOLOR} Press ${BOLD}u${NORMAL} at a split prompt to undo the split and commit everything as one"
8144
8281
  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}"
8145
8282
  git config --unset gitbasher.cached-git-add 2>/dev/null || true
8146
8283
  exit 0
@@ -8295,14 +8432,19 @@ function commit_script {
8295
8432
  else
8296
8433
  staged_files_list="$(sed 's/^/\t/' <<< "$(git -c core.quotePath=false diff --no-renames --name-only --cached)")"
8297
8434
  fi
8298
- if [ -z "${amend}" ] && [ -z "${fixup}" ] && [ -z "${msg}" ] && [ -z "${ticket}" ] && [ -z "${no_split}" ]; then
8299
- _split_auto_yes=""
8300
- if [ -n "$fast" ] || [ -n "$auto_accept" ]; then
8435
+ _split_skip="${no_split}"
8436
+ _split_auto_yes=""
8437
+ if [ -z "$split" ] && { [ -n "$fast" ] || [ -n "$auto_accept" ]; }; then
8438
+ if [ "$(get_fast_split)" = "false" ]; then
8439
+ [ -n "$auto_accept" ] && _split_skip="true"
8440
+ else
8301
8441
  _split_auto_yes="true"
8302
8442
  fi
8443
+ fi
8444
+ if [ -z "${amend}" ] && [ -z "${fixup}" ] && [ -z "${msg}" ] && [ -z "${ticket}" ] && [ -z "${_split_skip}" ]; then
8303
8445
  try_offer_commit_split "$split" "$_split_auto_yes"
8304
8446
  _split_rc=$?
8305
- if [ -n "$split" ]; then
8447
+ if [ -n "$split" ] && [ "$_split_rc" -ne 3 ]; then
8306
8448
  if [ "$_split_rc" -ge 2 ]; then
8307
8449
  exit 1
8308
8450
  fi
@@ -9174,7 +9316,7 @@ function branch_script {
9174
9316
  "$main_branch"|"$current_branch"|master|main|develop) continue ;;
9175
9317
  esac
9176
9318
  merged_to_delete+=("$_mb")
9177
- done < <(git branch --merged --format='%(refname:short)')
9319
+ done < <(git branch --merged HEAD --format='%(refname:short)')
9178
9320
  for branch_to_delete in "${merged_to_delete[@]}"; do
9179
9321
  delete_output=$(git branch -d "$branch_to_delete" 2>&1)
9180
9322
  delete_code=$?
@@ -15479,7 +15621,7 @@ _gitb_sub_sync="push p merge m mergep mp pm dry d dr help h"
15479
15621
  _gitb_sub_wip="up u down d help h"
15480
15622
  _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"
15481
15623
  _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"
15482
- _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"
15624
+ _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"
15483
15625
  _gitb_sub_undo="commit c amend a merge m rebase r stash s help h"
15484
15626
  _gitb_sub_reset="soft s undo u interactive i ref r help h"
15485
15627
  _gitb_sub_stash="select sel all list l pop p show s drop d apply a help h"
@@ -15828,7 +15970,7 @@ _gitb() {
15828
15970
  _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]'
15829
15971
  ;;
15830
15972
  config|cf|cfg|conf)
15831
- _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]'
15973
+ _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]'
15832
15974
  ;;
15833
15975
  undo|un)
15834
15976
  _values 'undo target' 'commit[undo last commit]' 'amend[undo amend]' 'merge[undo merge]' 'rebase[undo rebase]' 'stash[undo stash]' 'help[show help]'
@@ -16030,7 +16172,7 @@ complete -c gitb -n "$__gitb_branch" -a "list remote main new newd delete prev r
16030
16172
  set -l __gitb_tag "__gitb_using_cmd tag t tg; and __gitb_at_position 2"
16031
16173
  complete -c gitb -n "$__gitb_tag" -a "annotated commit all push push-all delete delete-all list remote help"
16032
16174
  set -l __gitb_config "__gitb_using_cmd config cf cfg conf; and __gitb_at_position 2"
16033
- 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"
16175
+ 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"
16034
16176
  set -l __gitb_undo "__gitb_using_cmd undo un; and __gitb_at_position 2"
16035
16177
  complete -c gitb -n "$__gitb_undo" -a "commit amend merge rebase stash help"
16036
16178
  set -l __gitb_reset "__gitb_using_cmd reset res; and __gitb_at_position 2"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitbasher",
3
- "version": "5.2.1",
3
+ "version": "5.3.1",
4
4
  "description": "Simple bash utility that makes git easy to use",
5
5
  "keywords": [
6
6
  "git",