gitbash 0.2.7 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Matthias Jäggli
3
+ Copyright (c) 2025 Matthias Jäggli, https://github.com/jaggli
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  Interactive git utilities for bash with fzf-powered menus.
4
4
 
5
+ ![screenshot-status..png](./doc/screenshot-status.png)
6
+
5
7
  ## Installation
6
8
 
7
9
  ### Via npm
8
10
 
9
11
  ```bash
10
- npm i -g gitbash
12
+ npm i -g gitbash && gitbash --config
11
13
  ```
12
14
 
13
15
  ### Options
@@ -19,33 +21,6 @@ gitbash --init # Print shell init code
19
21
  gitbash --config # Interactive configuration wizard
20
22
  ```
21
23
 
22
- ### Shell Aliases
23
-
24
- Add these aliases to your `.zshrc` or `.bashrc` for individual command access:
25
-
26
- ```bash
27
- alias branch="gitbash branch"
28
- alias cleanstash="gitbash cleanstash"
29
- alias cleanup="gitbash cleanup"
30
- alias commit="gitbash commit"
31
- alias commits="gitbash commits"
32
- alias create="gitbash create"
33
- alias pr="gitbash pr"
34
- alias stale="gitbash stale"
35
- alias stash="gitbash stash"
36
- alias stashes="gitbash stashes"
37
- alias status="gitbash status"
38
- alias switch="gitbash switch"
39
- alias unstash="gitbash unstash"
40
- alias update="gitbash update"
41
- ```
42
-
43
- Or use the built-in shell integration, for all commands:
44
-
45
- ```bash
46
- eval "$(gitbash --init)"
47
- ```
48
-
49
24
  ### Dependencies
50
25
 
51
26
  ```bash
@@ -216,6 +191,29 @@ commits # Show last 20 commits
216
191
  commits 50 # Show last 50 commits
217
192
  ```
218
193
 
194
+ ### Individual aliases
195
+
196
+ Instead of adding every command via `eval "$(gitbash --init)"` in `.zshrc` or `.bashrc`
197
+ direct usage with `gitbash [command]` is also possible. Or if desired, individual aliases
198
+ can be made.
199
+
200
+ ```bash
201
+ alias branch="gitbash branch"
202
+ alias cleanstash="gitbash cleanstash"
203
+ alias cleanup="gitbash cleanup"
204
+ alias commit="gitbash commit"
205
+ alias commits="gitbash commits"
206
+ alias create="gitbash create"
207
+ alias pr="gitbash pr"
208
+ alias stale="gitbash stale"
209
+ alias stash="gitbash stash"
210
+ alias stashes="gitbash stashes"
211
+ alias status="gitbash status"
212
+ alias switch="gitbash switch"
213
+ alias unstash="gitbash unstash"
214
+ alias update="gitbash update"
215
+ ```
216
+
219
217
  ## License
220
218
 
221
219
  See [LICENSE](LICENSE) file for details.
package/bin/gitbash CHANGED
@@ -17,33 +17,169 @@ configure_gitbash() {
17
17
 
18
18
  local current_prefix="${GITBASH_FEATURE_BRANCH_PREFIX:-feature/}"
19
19
  local current_merge="${GITBASH_MERGE_COMMAND:-fork}"
20
+ local current_theme="${GITBASH_THEME:-auto}"
21
+ local current_stale_months="${GITBASH_STALE_MONTHS:-3}"
22
+ local current_cleanup_days="${GITBASH_CLEANUP_DAYS:-7}"
20
23
 
21
24
  echo "Configure gitbash (writes to $CONFIG_FILE)"
22
25
  echo
23
26
 
24
- local input prefix merge_cmd
27
+ local input prefix merge_cmd theme stale_months cleanup_days
25
28
 
29
+ # Branch prefix
26
30
  read -e -p "Feature branch prefix (used by create) [${current_prefix}]: " input
27
31
  prefix=${input:-$current_prefix}
28
32
 
33
+ # Merge tool
29
34
  read -e -p "Merge tool command (used by update on conflicts) [${current_merge}]: " input
30
35
  merge_cmd=${input:-$current_merge}
31
36
 
37
+ # Theme preference
38
+ echo
39
+ echo "Theme preference for delta/bat (affects diff highlighting):"
40
+ echo " auto - Use terminal's theme detection"
41
+ echo " dark - Force dark theme"
42
+ echo " light - Force light theme (GitHub style)"
43
+ read -e -p "Theme [${current_theme}]: " input
44
+ theme=${input:-$current_theme}
45
+ case "$theme" in
46
+ auto|dark|light) ;;
47
+ *)
48
+ echo "Invalid theme. Using 'auto'."
49
+ theme="auto"
50
+ ;;
51
+ esac
52
+
53
+ # Stale branch threshold
54
+ echo
55
+ read -e -p "Stale branch threshold in months (used by stale) [${current_stale_months}]: " input
56
+ stale_months=${input:-$current_stale_months}
57
+ if ! [[ "$stale_months" =~ ^[0-9]+$ ]] || [[ "$stale_months" -lt 1 ]]; then
58
+ echo "Invalid value. Using default of 3 months."
59
+ stale_months=3
60
+ fi
61
+
62
+ # Cleanup threshold
63
+ read -e -p "Cleanup threshold in days (used by cleanup) [${current_cleanup_days}]: " input
64
+ cleanup_days=${input:-$current_cleanup_days}
65
+ if ! [[ "$cleanup_days" =~ ^[0-9]+$ ]] || [[ "$cleanup_days" -lt 1 ]]; then
66
+ echo "Invalid value. Using default of 7 days."
67
+ cleanup_days=7
68
+ fi
69
+
32
70
  cat > "$CONFIG_FILE" <<EOF
33
71
  # gitbash configuration
34
72
  # Prefix for feature branches created by 'create'
35
73
  GITBASH_FEATURE_BRANCH_PREFIX="$prefix"
36
74
  # Merge tool command invoked by 'update' when conflicts occur
37
75
  GITBASH_MERGE_COMMAND="$merge_cmd"
76
+ # Theme for delta/bat: auto, dark, or light
77
+ GITBASH_THEME="$theme"
78
+ # Stale branch threshold in months (branches older than this in 'stale')
79
+ GITBASH_STALE_MONTHS="$stale_months"
80
+ # Cleanup threshold in days (branches merged more than this many days ago)
81
+ GITBASH_CLEANUP_DAYS="$cleanup_days"
38
82
  EOF
39
83
 
40
84
  echo
41
85
  echo "Saved configuration to $CONFIG_FILE"
42
86
 
87
+ # Check for dependencies
88
+ check_dependencies
89
+
43
90
  # Check for shell integration
44
91
  check_shell_integration
45
92
  }
46
93
 
94
+ # Check and offer to install dependencies
95
+ check_dependencies() {
96
+ local missing_required=()
97
+ local missing_optional=()
98
+
99
+ # Required dependencies
100
+ if ! command -v fzf >/dev/null 2>&1; then
101
+ missing_required+=("fzf")
102
+ fi
103
+
104
+ # Optional dependencies
105
+ if ! command -v delta >/dev/null 2>&1; then
106
+ missing_optional+=("git-delta")
107
+ fi
108
+ if ! command -v bat >/dev/null 2>&1; then
109
+ missing_optional+=("bat")
110
+ fi
111
+
112
+ # If nothing missing, we're done
113
+ if [[ ${#missing_required[@]} -eq 0 && ${#missing_optional[@]} -eq 0 ]]; then
114
+ echo
115
+ echo "All dependencies installed ✓"
116
+ return 0
117
+ fi
118
+
119
+ echo
120
+ echo "Dependencies:"
121
+
122
+ # Show status of each dependency
123
+ if command -v fzf >/dev/null 2>&1; then
124
+ echo " ✓ fzf (required)"
125
+ else
126
+ echo " ✗ fzf (required) - fuzzy finder for interactive selection"
127
+ fi
128
+
129
+ if command -v delta >/dev/null 2>&1; then
130
+ echo " ✓ git-delta (optional)"
131
+ else
132
+ echo " ✗ git-delta (optional) - better diff highlighting"
133
+ fi
134
+
135
+ if command -v bat >/dev/null 2>&1; then
136
+ echo " ✓ bat (optional)"
137
+ else
138
+ echo " ✗ bat (optional) - syntax highlighting for file previews"
139
+ fi
140
+
141
+ # Build list of all missing dependencies
142
+ local all_missing=()
143
+ [[ ${#missing_required[@]} -gt 0 ]] && all_missing+=("${missing_required[@]}")
144
+ [[ ${#missing_optional[@]} -gt 0 ]] && all_missing+=("${missing_optional[@]}")
145
+
146
+ # If nothing to install, we're done
147
+ if [[ ${#all_missing[@]} -eq 0 ]]; then
148
+ return 0
149
+ fi
150
+
151
+ # Check if Homebrew is available
152
+ if ! command -v brew >/dev/null 2>&1; then
153
+ echo
154
+ echo "Homebrew not found. Install dependencies manually:"
155
+ for dep in "${all_missing[@]}"; do
156
+ echo " brew install $dep"
157
+ done
158
+ return 0
159
+ fi
160
+
161
+ # Offer to install missing dependencies
162
+ echo
163
+ local install_deps
164
+ read -e -p "Install missing dependencies with Homebrew? (Y/n): " install_deps
165
+ case "$install_deps" in
166
+ [nN][oO]|[nN])
167
+ echo "Skipped dependency installation."
168
+ ;;
169
+ *)
170
+ echo "Installing dependencies..."
171
+ for dep in "${all_missing[@]}"; do
172
+ echo " Installing $dep..."
173
+ if brew install "$dep" 2>/dev/null; then
174
+ echo " ✓ $dep installed"
175
+ else
176
+ echo " ✗ Failed to install $dep"
177
+ fi
178
+ done
179
+ ;;
180
+ esac
181
+ }
182
+
47
183
  # Check if gitbash shell integration is set up
48
184
  check_shell_integration() {
49
185
  local shell_rc=""
@@ -2,6 +2,33 @@
2
2
  # shellcheck disable=SC2155
3
3
  # Common utilities for gitbash commands - compatible with bash and zsh
4
4
 
5
+ # Color codes for consistent output
6
+ COLOR_RED='\033[0;31m'
7
+ COLOR_GREEN='\033[0;32m'
8
+ COLOR_YELLOW='\033[0;33m'
9
+ COLOR_BLUE='\033[0;34m'
10
+ COLOR_RESET='\033[0m'
11
+
12
+ # Print success message (green checkmark)
13
+ print_success() {
14
+ echo -e "${COLOR_GREEN}✓${COLOR_RESET} $*"
15
+ }
16
+
17
+ # Print error message (red X)
18
+ print_error() {
19
+ echo -e "${COLOR_RED}✗${COLOR_RESET} $*" >&2
20
+ }
21
+
22
+ # Print warning message (yellow warning)
23
+ print_warning() {
24
+ echo -e "${COLOR_YELLOW}⚠${COLOR_RESET} $*"
25
+ }
26
+
27
+ # Print info message (blue)
28
+ print_info() {
29
+ echo -e "${COLOR_BLUE}ℹ${COLOR_RESET} $*"
30
+ }
31
+
5
32
  # Portable read with prompt that works in both bash and zsh
6
33
  # Usage: prompt_read "prompt text" variable_name
7
34
  # The result is stored in the variable name provided
@@ -32,3 +59,118 @@ prompt_read_edit() {
32
59
  read -e -rp "$prompt" "$varname"
33
60
  fi
34
61
  }
62
+
63
+ # Check if fzf is installed, offer to install if not
64
+ # Returns 0 if fzf is available, 1 if not
65
+ require_fzf() {
66
+ if command -v fzf >/dev/null 2>&1; then
67
+ return 0
68
+ fi
69
+
70
+ print_error "fzf is not installed."
71
+
72
+ local ans
73
+ prompt_read "Install fzf with Homebrew? (y/N): " ans
74
+ case "$ans" in
75
+ [yY][eE][sS]|[yY])
76
+ echo "Installing fzf with brew..."
77
+ if ! command -v brew >/dev/null 2>&1; then
78
+ print_error "Homebrew is not installed. Aborting."
79
+ return 1
80
+ fi
81
+ if brew install fzf; then
82
+ # Optional: install shell integrations automatically
83
+ if [[ -f "$(brew --prefix)/opt/fzf/install" ]]; then
84
+ echo "Running fzf install script..."
85
+ yes | "$(brew --prefix)/opt/fzf/install"
86
+ fi
87
+ print_success "fzf installed successfully."
88
+ return 0
89
+ else
90
+ print_error "fzf install failed. Aborting."
91
+ return 1
92
+ fi
93
+ ;;
94
+ *)
95
+ echo "Aborted (skipped fzf installation)."
96
+ return 1
97
+ ;;
98
+ esac
99
+ }
100
+
101
+ # Check if inside a git repository
102
+ # Returns 0 if in a git repo, 1 if not
103
+ require_git_repo() {
104
+ if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
105
+ return 0
106
+ fi
107
+ print_error "Not inside a git repository."
108
+ return 1
109
+ }
110
+
111
+ # Get delta arguments based on theme setting
112
+ # Uses GITBASH_THEME: auto (default), dark, or light
113
+ _get_delta_args() {
114
+ local theme="${GITBASH_THEME:-auto}"
115
+ case "$theme" in
116
+ light)
117
+ echo "--light"
118
+ ;;
119
+ dark)
120
+ echo "--dark"
121
+ ;;
122
+ *)
123
+ # auto - let delta detect (no args needed, but we can add --light for lighter terminals)
124
+ echo ""
125
+ ;;
126
+ esac
127
+ }
128
+
129
+ # Get bat arguments based on theme setting
130
+ _get_bat_args() {
131
+ local theme="${GITBASH_THEME:-auto}"
132
+ case "$theme" in
133
+ light)
134
+ echo "--theme=GitHub"
135
+ ;;
136
+ dark)
137
+ echo "--theme=Dracula"
138
+ ;;
139
+ *)
140
+ # auto - let bat detect
141
+ echo ""
142
+ ;;
143
+ esac
144
+ }
145
+
146
+ # Get the diff command to use (delta if installed, otherwise git diff)
147
+ # Usage: get_diff_cmd
148
+ # Returns the command name to use for diffs
149
+ get_diff_cmd() {
150
+ if command -v delta >/dev/null 2>&1; then
151
+ local delta_args
152
+ delta_args=$(_get_delta_args)
153
+ if [[ -n "$delta_args" ]]; then
154
+ echo "delta $delta_args"
155
+ else
156
+ echo "delta"
157
+ fi
158
+ else
159
+ echo "git diff"
160
+ fi
161
+ }
162
+
163
+ # Show a diff using delta if available, otherwise git diff
164
+ # Usage: show_diff [git diff args...]
165
+ # Example: show_diff --cached
166
+ # Example: show_diff HEAD~1
167
+ show_diff() {
168
+ if command -v delta >/dev/null 2>&1; then
169
+ local delta_args
170
+ delta_args=$(_get_delta_args)
171
+ # shellcheck disable=SC2086
172
+ git diff "$@" | delta $delta_args
173
+ else
174
+ git diff "$@"
175
+ fi
176
+ }
@@ -107,31 +107,7 @@ EOF
107
107
  # -----------------------------
108
108
  # 1. Check for fzf
109
109
  # -----------------------------
110
- if ! command -v fzf >/dev/null 2>&1; then
111
- echo "fzf is not installed."
112
-
113
- # Ask whether to install with Homebrew
114
- prompt_read "Install fzf with Homebrew? (y/N): " ans
115
- case "$ans" in
116
- [yY][eE][sS]|[yY])
117
- echo "Installing fzf with brew..."
118
- if ! command -v brew >/dev/null 2>&1; then
119
- echo "Homebrew is not installed. Aborting."
120
- return 1
121
- fi
122
- brew install fzf || { echo "fzf install failed. Aborting."; return 1; }
123
- # Optional: install shell integrations automatically
124
- if [[ -f "$(brew --prefix)/opt/fzf/install" ]]; then
125
- echo "Running fzf install script..."
126
- yes | "$(brew --prefix)/opt/fzf/install"
127
- fi
128
- ;;
129
- *)
130
- echo "Aborted (skipped fzf installation)."
131
- return 1
132
- ;;
133
- esac
134
- fi
110
+ require_fzf || return 1
135
111
 
136
112
  # -----------------------------
137
113
  # 2. Build action menu
@@ -176,12 +152,24 @@ EOF
176
152
  # -----------------------------
177
153
  case "$selection" in
178
154
  "$create_option")
155
+ # Source create.sh if create function not available
156
+ if ! declare -f create >/dev/null 2>&1; then
157
+ source "$SOURCE_DIR/create.sh"
158
+ fi
179
159
  create
180
160
  ;;
181
161
  "$switch_option")
162
+ # Source switch.sh if switch function not available
163
+ if ! declare -f switch >/dev/null 2>&1; then
164
+ source "$SOURCE_DIR/switch.sh"
165
+ fi
182
166
  switch
183
167
  ;;
184
168
  "$update_option")
169
+ # Source update.sh if update function not available
170
+ if ! declare -f update >/dev/null 2>&1; then
171
+ source "$SOURCE_DIR/update.sh"
172
+ fi
185
173
  update
186
174
  ;;
187
175
  "$abort_label")
@@ -73,31 +73,7 @@ EOF
73
73
  # -----------------------------
74
74
  # 1. Check for fzf
75
75
  # -----------------------------
76
- if ! command -v fzf >/dev/null 2>&1; then
77
- echo "fzf is not installed."
78
-
79
- # Ask whether to install with Homebrew
80
- prompt_read "Install fzf with Homebrew? (y/N): " ans
81
- case "$ans" in
82
- [yY][eE][sS]|[yY])
83
- echo "Installing fzf with brew..."
84
- if ! command -v brew >/dev/null 2>&1; then
85
- echo "Homebrew is not installed. Aborting."
86
- return 1
87
- fi
88
- brew install fzf || { echo "fzf install failed. Aborting."; return 1; }
89
- # Optional: install shell integrations automatically
90
- if [[ -f "$(brew --prefix)/opt/fzf/install" ]]; then
91
- echo "Running fzf install script..."
92
- yes | "$(brew --prefix)/opt/fzf/install"
93
- fi
94
- ;;
95
- *)
96
- echo "Aborted (skipped fzf installation)."
97
- return 1
98
- ;;
99
- esac
100
- fi
76
+ require_fzf || return 1
101
77
 
102
78
  # -----------------------------
103
79
  # 2. Build menu entries
@@ -126,7 +102,7 @@ EOF
126
102
  --preview='
127
103
  sel=$(echo {} | grep -o "stash@{[0-9]\+}" || true);
128
104
  if [[ -n "$sel" ]]; then
129
- git stash show -p "$sel";
105
+ git stash show -p "$sel" | delta --light 2>/dev/null || git stash show -p "$sel";
130
106
  else
131
107
  echo "No preview";
132
108
  fi
@@ -12,6 +12,8 @@ cleanup() {
12
12
  # 0. Check for help/version flag and parse options
13
13
  # -----------------------------
14
14
  local json_mode=false
15
+ local dry_run=false
16
+ local days_threshold="${GITBASH_CLEANUP_DAYS:-7}"
15
17
 
16
18
  while [[ $# -gt 0 ]]; do
17
19
  case "$1" in
@@ -26,15 +28,17 @@ Usage: cleanup [OPTIONS]
26
28
  Find and delete local branches that are no longer needed.
27
29
 
28
30
  Options:
29
- -h, --help Show this help message
30
- --json Output branch data as JSON (non-interactive)
31
+ -h, --help Show this help message
32
+ --json Output branch data as JSON (non-interactive)
33
+ --dry-run Show what would be deleted without actually deleting
34
+ --days=N Override stale threshold (default: 7 days, configurable via GITBASH_CLEANUP_DAYS)
31
35
 
32
36
  Categories (pre-selected for deletion):
33
37
  - Merged branches: Local branches whose remote was merged and deleted
34
- - Stale branches: Local branches with no commits in 7+ days
38
+ - Stale branches: Local branches with no commits in N+ days (default 7)
35
39
 
36
40
  Not pre-selected (but listed):
37
- - Recent branches: Local branches with commits in the last 7 days
41
+ - Recent branches: Local branches with commits in the last N days
38
42
 
39
43
  Navigation:
40
44
  ↑/↓ or j/k Navigate through branches
@@ -42,6 +46,9 @@ Navigation:
42
46
  Enter Delete selected branch(es)
43
47
  ESC/Ctrl-C Exit without action
44
48
 
49
+ Configuration:
50
+ GITBASH_CLEANUP_DAYS - Set default days threshold (run 'gitbash --config')
51
+
45
52
  Notes:
46
53
  - Only deletes LOCAL branches (never touches remote)
47
54
  - If current branch is selected, switches to main/master first
@@ -55,6 +62,12 @@ Examples:
55
62
  [RECENT] feature/work-in-progress 2 days ago
56
63
  ✖ Abort
57
64
 
65
+ $ cleanup --dry-run
66
+ # Shows what would be deleted without deleting
67
+
68
+ $ cleanup --days=14
69
+ # Use 14-day threshold for stale branches
70
+
58
71
  $ cleanup --json
59
72
  [{"last_change_timestamp":1733123456,"author_email":"dev@example.com",...}]
60
73
 
@@ -69,6 +82,18 @@ EOF
69
82
  json_mode=true
70
83
  shift
71
84
  ;;
85
+ --dry-run)
86
+ dry_run=true
87
+ shift
88
+ ;;
89
+ --days=*)
90
+ days_threshold="${1#--days=}"
91
+ if ! [[ "$days_threshold" =~ ^[0-9]+$ ]] || [[ "$days_threshold" -lt 1 ]]; then
92
+ print_error "Invalid days value: $days_threshold"
93
+ return 1
94
+ fi
95
+ shift
96
+ ;;
72
97
  *)
73
98
  echo "Unknown option: $1"
74
99
  return 1
@@ -82,38 +107,20 @@ EOF
82
107
  if [[ "$json_mode" == true ]]; then
83
108
  echo "[]"
84
109
  else
85
- echo "Not inside a git repository." >&2
110
+ print_error "Not inside a git repository."
86
111
  fi
87
112
  return 1
88
113
  fi
89
114
 
90
115
  if [[ "$json_mode" == false ]]; then
91
- if ! command -v fzf >/dev/null 2>&1; then
92
- echo "fzf is not installed."
93
- prompt_read "Install fzf with Homebrew? (y/N): " ans
94
- case "$ans" in
95
- [yY][eE][sS]|[yY])
96
- echo "Installing fzf with brew..."
97
- if ! command -v brew >/dev/null 2>&1; then
98
- echo "Homebrew is not installed. Aborting."
99
- return 1
100
- fi
101
- brew install fzf || { echo "fzf install failed. Aborting."; return 1; }
102
- ;;
103
- *)
104
- echo "Aborted (skipped fzf installation)."
105
- return 1
106
- ;;
107
- esac
108
- fi
116
+ require_fzf || return 1
109
117
  fi
110
-
111
118
  # -----------------------------
112
119
  # 2. Fetch and prune to sync with remote
113
120
  # -----------------------------
114
121
  if ! git fetch --prune origin 2>/dev/null; then
115
122
  if [[ "$json_mode" == false ]]; then
116
- echo "⚠ Fetch failed; continuing with local data." >&2
123
+ print_warning "Fetch failed; continuing with local data." >&2
117
124
  fi
118
125
  fi
119
126
 
@@ -141,8 +148,8 @@ EOF
141
148
  # 4. Build branch lists
142
149
  # -----------------------------
143
150
  local abort_label="✖ Abort"
144
- local one_week_ago
145
- one_week_ago=$(date -v-7d +%s 2>/dev/null || date -d "7 days ago" +%s 2>/dev/null)
151
+ local threshold_ago
152
+ threshold_ago=$(date -v-"${days_threshold}d" +%s 2>/dev/null || date -d "${days_threshold} days ago" +%s 2>/dev/null)
146
153
 
147
154
  # Get list of remote branches (without origin/ prefix)
148
155
  local remote_branches
@@ -185,8 +192,8 @@ EOF
185
192
  if [[ "$has_remote" == false && -n "$configured_upstream" ]]; then
186
193
  # Had upstream but remote is gone = merged and deleted remotely
187
194
  merged_branches+=("$entry")
188
- elif [[ -n "$last_commit_ts" && "$last_commit_ts" -lt "$one_week_ago" ]]; then
189
- # Stale: no commits in 7+ days (pre-select for deletion)
195
+ elif [[ -n "$last_commit_ts" && "$last_commit_ts" -lt "$threshold_ago" ]]; then
196
+ # Stale: no commits in N+ days (pre-select for deletion)
190
197
  stale_branches+=("$entry")
191
198
  else
192
199
  # Recent: has recent activity (don't pre-select)
@@ -458,6 +465,16 @@ Found $total_count branches ($preselect_count pre-selected for deletion)" \
458
465
  fi
459
466
  echo ""
460
467
 
468
+ # Handle dry-run mode
469
+ if [[ "$dry_run" == true ]]; then
470
+ print_info "Dry run mode - no branches will be deleted"
471
+ echo "Would delete ${#branches_to_delete[@]} branch(es):"
472
+ for branch in "${branches_to_delete[@]}"; do
473
+ echo " - $branch"
474
+ done
475
+ return 0
476
+ fi
477
+
461
478
  # Default to Y if only merged branches, otherwise N
462
479
  local confirm
463
480
  if [[ "$only_merged" == true ]]; then
@@ -482,20 +499,20 @@ Found $total_count branches ($preselect_count pre-selected for deletion)" \
482
499
 
483
500
  # Switch to base branch if needed
484
501
  if [[ "$need_switch" == true ]]; then
485
- echo "Switching to '$base_branch'..."
502
+ print_info "Switching to '$base_branch'..."
486
503
  git checkout "$base_branch" || {
487
- echo "⚠ Failed to switch to '$base_branch'. Aborting deletion."
504
+ print_warning "Failed to switch to '$base_branch'. Aborting deletion."
488
505
  return 1
489
506
  }
490
507
  fi
491
508
 
492
- echo "Deleting branches..."
509
+ print_info "Deleting branches..."
493
510
  for branch in "${branches_to_delete[@]}"; do
494
511
  echo "Deleting $branch ..."
495
512
  if git branch -D "$branch" 2>/dev/null; then
496
- echo "✓ Deleted $branch"
513
+ print_success "Deleted $branch"
497
514
  else
498
- echo "✗ Failed to delete $branch"
515
+ print_error "Failed to delete $branch"
499
516
  fi
500
517
  done
501
518
  }