gitbash 1.9.1 → 2.0.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/bin/gitbash CHANGED
@@ -1,753 +1,370 @@
1
1
  #!/usr/bin/env bash
2
2
  # gitbash - Interactive git utilities for bash with fzf-powered menus
3
3
 
4
- set -euo pipefail
4
+ # Resolve symlinks to get the actual script location
5
+ SOURCE="${BASH_SOURCE[0]}"
6
+ while [[ -L "$SOURCE" ]]; do
7
+ DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
8
+ SOURCE="$(readlink "$SOURCE")"
9
+ [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
10
+ done
11
+ SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
12
+ GITBASH_BIN="$SCRIPT_DIR/bin/gitbash"
13
+ COMMANDS_DIR="$SCRIPT_DIR/commands"
14
+ VERSION="$(grep -o '"version": *"[^"]*"' "$SCRIPT_DIR/package.json" | head -1 | cut -d'"' -f4)"
15
+ export SCRIPT_DIR GITBASH_BIN VERSION
5
16
 
6
- CONFIG_FILE="$HOME/.gitbashrc"
7
- LOCAL_CONFIG_FILE=".gitbashrc"
8
- USER_CONFIG_FILE=".gitbashrc-user"
17
+ # shellcheck source=../commands/_utils.sh
18
+ source "$COMMANDS_DIR/_utils.sh"
9
19
 
10
- load_config() {
11
- # Source global config first
12
- if [[ -f "$CONFIG_FILE" ]]; then
13
- # shellcheck disable=SC1090
14
- source "$CONFIG_FILE"
15
- fi
16
-
17
- # Source local config from repo root if present (overrides global)
18
- local repo_root
19
- if repo_root=$(git rev-parse --show-toplevel 2>/dev/null); then
20
- local local_config="$repo_root/$LOCAL_CONFIG_FILE"
21
- if [[ -f "$local_config" ]]; then
22
- # shellcheck disable=SC1090
23
- source "$local_config"
24
- fi
25
-
26
- # Source user config from repo root if present (overrides both)
27
- local user_config="$repo_root/$USER_CONFIG_FILE"
28
- if [[ -f "$user_config" ]]; then
29
- # shellcheck disable=SC1090
30
- source "$user_config"
31
- fi
32
- fi
33
- }
20
+ # Available commands
21
+ COMMANDS=(
22
+ "branch"
23
+ "cleanstash"
24
+ "cleanup"
25
+ "commit"
26
+ "commits"
27
+ "create"
28
+ "pr"
29
+ "stale"
30
+ "stash"
31
+ "stashes"
32
+ "status"
33
+ "switch"
34
+ "unstash"
35
+ "update"
36
+ )
34
37
 
35
- configure_gitbash() {
36
- load_config
38
+ is_command() {
39
+ local cmd
40
+ for cmd in "${COMMANDS[@]}"; do
41
+ [[ "$cmd" == "$1" ]] && return 0
42
+ done
43
+ return 1
44
+ }
37
45
 
38
- # Backward compatibility: migrate GITBASH_FEATURE_BRANCH_PREFIX to GITBASH_CREATE_BRANCH_PREFIX
39
- local current_prefix="${GITBASH_CREATE_BRANCH_PREFIX:-}"
40
- if [[ -z "$current_prefix" && -n "${GITBASH_FEATURE_BRANCH_PREFIX:-}" ]]; then
41
- # Remove "feature/" prefix from old variable
42
- current_prefix="${GITBASH_FEATURE_BRANCH_PREFIX#feature/}"
43
- fi
44
- # Remove trailing slash if present
45
- current_prefix="${current_prefix%/}"
46
- # Default to empty if still not set
47
- current_prefix="${current_prefix:-}"
48
-
49
- local current_merge="${GITBASH_MERGE_COMMAND:-fork}"
50
- local current_theme="${GITBASH_THEME:-auto}"
51
- local current_stale_months="${GITBASH_STALE_MONTHS:-3}"
52
- local current_cleanup_days="${GITBASH_CLEANUP_DAYS:-7}"
53
- local current_no_issue_parsing="${GITBASH_CREATE_NO_ISSUE_PARSING:-no}"
54
- local current_issue_fallback="${GITBASH_CREATE_ISSUE_PARSING_FALLBACK:-NOISSUE}"
55
-
56
- echo "Configure gitbash (writes to $CONFIG_FILE)"
57
- echo
46
+ # =============================================================================
47
+ # Configuration wizard
48
+ # =============================================================================
49
+
50
+ # key|type|question
51
+ SETTINGS=(
52
+ "GITBASH_CREATE_BRANCH_PREFIX|prefix|Branch prefix inserted between type and branch name (e.g. 'team' → feature/team/PROJ-123-fix-bug)"
53
+ "GITBASH_CREATE_NO_ISSUE_PARSING|parsing|Parse Jira issue numbers into branch names? (yes/no)"
54
+ "GITBASH_CREATE_ISSUE_PARSING_FALLBACK|text|Fallback issue prefix when no issue number is given"
55
+ "GITBASH_CREATE_AUTO_PUSH|yesno|Push new branches to the remote right after 'create'? (yes/no)"
56
+ "GITBASH_MERGE_COMMAND|command|Merge tool opened by 'update' on conflicts (not allowed in the committed .gitbashrc)"
57
+ "GITBASH_THEME|theme|Theme for delta/bat previews (auto/dark/light)"
58
+ "GITBASH_STALE_MONTHS|number|Stale remote branch threshold in months (used by 'stale')"
59
+ "GITBASH_CLEANUP_DAYS|number|Stale local branch threshold in days (used by 'cleanup')"
60
+ "GITBASH_PROTECTED_BRANCHES|text|Branches never offered for deletion (space-separated globs)"
61
+ "GITBASH_BASE_BRANCH|text|Base branch (empty = detect from the remote's HEAD, then main/master)"
62
+ "GITBASH_REMOTE|text|Remote name"
63
+ )
58
64
 
59
- local input prefix merge_cmd theme stale_months cleanup_days no_issue_parsing issue_fallback
60
-
61
- # Branch prefix
62
- echo "Branch prefix"
63
- echo "Team names are typically used to organize branches by team or subproject."
64
- echo "Inserted between type and branch name, examples:"
65
- echo " '' → feature/PROJ-123-fix-bug"
66
- echo " 'team' → feature/team/PROJ-123-fix-bug"
67
- echo " 'awesome-team' → hotfix/awesome-team/PROJ-999-critical-fix"
68
- if [[ -n "$current_prefix" ]]; then
69
- echo "Tip: Enter a single dash '-' to explicitly clear the prefix"
70
- fi
71
- read -re -p "Branch prefix [${current_prefix}]: " prefix
72
- # If user just pressed Enter, keep current value
73
- # If user entered a single dash, clear it
74
- if [[ -z "$prefix" ]]; then
75
- prefix="$current_prefix"
76
- elif [[ "$prefix" == "-" ]]; then
77
- prefix=""
78
- fi
79
- # Remove trailing slash if present (will be added automatically)
80
- prefix="${prefix%/}"
65
+ # Default value of a setting
66
+ setting_default() {
67
+ case "$1" in
68
+ GITBASH_CREATE_NO_ISSUE_PARSING) echo "no" ;;
69
+ GITBASH_CREATE_ISSUE_PARSING_FALLBACK) echo "NOISSUE" ;;
70
+ GITBASH_CREATE_AUTO_PUSH) echo "yes" ;;
71
+ GITBASH_MERGE_COMMAND) echo "fork" ;;
72
+ GITBASH_THEME) echo "auto" ;;
73
+ GITBASH_STALE_MONTHS) echo "3" ;;
74
+ GITBASH_CLEANUP_DAYS) echo "7" ;;
75
+ GITBASH_PROTECTED_BRANCHES) echo "main master develop release/*" ;;
76
+ GITBASH_REMOTE) echo "origin" ;;
77
+ *) echo "" ;;
78
+ esac
79
+ }
81
80
 
82
- # Merge tool
83
- echo
84
- read -re -p "Merge tool command (used by update on conflicts) [${current_merge}]: " input
85
- merge_cmd=${input:-$current_merge}
81
+ # Value of a setting stored in a specific file. Returns 1 if not set there.
82
+ file_value() {
83
+ local file="$1" key="$2" k v
84
+ while IFS='=' read -r k v; do
85
+ if [[ "$k" == "$key" ]]; then
86
+ printf '%s' "$v"
87
+ return 0
88
+ fi
89
+ done < <(_gb_config_entries "$file")
90
+ return 1
91
+ }
86
92
 
87
- # Issue parsing
88
- echo
89
- # Invert current value for display (no_issue_parsing="no" means enabled=yes)
90
- local current_enabled="yes"
91
- if [[ "$current_no_issue_parsing" == "yes" ]]; then
92
- current_enabled="no"
93
+ # Convert between the stored value and what the wizard shows.
94
+ # GITBASH_CREATE_NO_ISSUE_PARSING is stored inverted.
95
+ to_display() {
96
+ local type="$1" value="$2"
97
+ if [[ "$type" == "parsing" ]]; then
98
+ [[ "$value" == "yes" ]] && echo "no" || echo "yes"
99
+ else
100
+ printf '%s' "$value"
93
101
  fi
94
-
95
- read -re -p "Enable Jira issue parsing in branch names? (yes/no) [${current_enabled}]: " input
96
- local enable_parsing=${input:-$current_enabled}
97
-
98
- # Convert answer to no_issue_parsing variable (inverted logic)
99
- case "$enable_parsing" in
100
- yes)
101
- no_issue_parsing="no"
102
+ }
103
+
104
+ # Validate an answer and print the value to store. Returns 1 if invalid.
105
+ to_stored() {
106
+ local type="$1" answer="$2"
107
+ if [[ "$answer" == *'"'* || "$answer" == *'$'* || "$answer" == *'`'* || "$answer" == *\\* ]]; then
108
+ echo "Values cannot contain \", \$, \` or \\." >&2
109
+ return 1
110
+ fi
111
+ case "$type" in
112
+ parsing)
113
+ case "$answer" in
114
+ yes) echo "no" ;;
115
+ no) echo "yes" ;;
116
+ *) echo "Please answer yes or no." >&2; return 1 ;;
117
+ esac
118
+ ;;
119
+ yesno)
120
+ case "$answer" in
121
+ yes|no) echo "$answer" ;;
122
+ *) echo "Please answer yes or no." >&2; return 1 ;;
123
+ esac
124
+ ;;
125
+ theme)
126
+ case "$answer" in
127
+ auto|dark|light) echo "$answer" ;;
128
+ *) echo "Please answer auto, dark or light." >&2; return 1 ;;
129
+ esac
130
+ ;;
131
+ number)
132
+ if [[ "$answer" =~ ^[1-9][0-9]*$ ]]; then
133
+ echo "$answer"
134
+ else
135
+ echo "Please enter a whole number of at least 1." >&2
136
+ return 1
137
+ fi
102
138
  ;;
103
- no)
104
- no_issue_parsing="yes"
139
+ prefix)
140
+ echo "${answer%/}"
105
141
  ;;
106
142
  *)
107
- echo "Invalid value. Using 'yes' (parsing enabled)."
108
- no_issue_parsing="no"
143
+ echo "$answer"
109
144
  ;;
110
145
  esac
146
+ }
111
147
 
112
- # Issue parsing fallback (only ask if parsing is enabled)
113
- if [[ "$enable_parsing" == "yes" ]]; then
114
- read -re -p "Fallback prefix when no issue number is provided [${current_issue_fallback}]: " input
115
- issue_fallback=${input:-$current_issue_fallback}
116
- else
117
- issue_fallback="${current_issue_fallback}"
118
- fi
119
-
120
- # Theme preference
121
- echo
122
- echo "Theme preference for delta/bat (affects diff highlighting):"
123
- echo " auto - Use terminal's theme detection"
124
- echo " dark - Force dark theme"
125
- echo " light - Force light theme (GitHub style)"
126
- read -re -p "Theme [${current_theme}]: " input
127
- theme=${input:-$current_theme}
128
- case "$theme" in
129
- auto|dark|light) ;;
130
- *)
131
- echo "Invalid theme. Using 'auto'."
132
- theme="auto"
148
+ # Interactive configuration for one scope: global (~/.gitbashrc),
149
+ # local (<repo>/.gitbashrc, committed) or user (<repo>/.gitbashrc-user, not committed)
150
+ configure_wizard() {
151
+ local scope="$1" file repo_root=""
152
+ case "$scope" in
153
+ global)
154
+ file=$(gb_global_config_file)
155
+ ;;
156
+ local|user)
157
+ if ! repo_root=$(git rev-parse --show-toplevel 2>/dev/null); then
158
+ print_error "Not inside a git repository."
159
+ return 1
160
+ fi
161
+ if [[ "$scope" == "local" ]]; then
162
+ file="$repo_root/.gitbashrc"
163
+ else
164
+ file="$repo_root/.gitbashrc-user"
165
+ fi
133
166
  ;;
134
167
  esac
135
168
 
136
- # Stale branch threshold
137
- echo
138
- read -re -p "Stale branch threshold in months (used by stale) [${current_stale_months}]: " input
139
- stale_months=${input:-$current_stale_months}
140
- if ! [[ "$stale_months" =~ ^[0-9]+$ ]] || [[ "$stale_months" -lt 1 ]]; then
141
- echo "Invalid value. Using default of 3 months."
142
- stale_months=3
143
- fi
144
-
145
- # Cleanup threshold
146
- read -re -p "Cleanup threshold in days (used by cleanup) [${current_cleanup_days}]: " input
147
- cleanup_days=${input:-$current_cleanup_days}
148
- if ! [[ "$cleanup_days" =~ ^[0-9]+$ ]] || [[ "$cleanup_days" -lt 1 ]]; then
149
- echo "Invalid value. Using default of 7 days."
150
- cleanup_days=7
151
- fi
152
-
153
- cat > "$CONFIG_FILE" <<EOF
154
- # Automatically generated gitbash configuration
155
-
156
- # CAUTION: Do not edit this file manually to avoid setting being overwritten.
157
- # run 'gitbash --config' to update settings
169
+ # Effective values from all config files, used as defaults
170
+ gb_load_config 2>/dev/null
158
171
 
159
- # Branch prefix inserted between type and issue (e.g., 'team-name' or '')
160
- # No trailing slash needed - it's added automatically
161
- # Format: <type>/<prefix>/<issue>-<title> or <type>/<prefix>/<title>
162
- GITBASH_CREATE_BRANCH_PREFIX="$prefix"
163
-
164
- # Merge tool command invoked by 'update' when conflicts occur
165
- GITBASH_MERGE_COMMAND="$merge_cmd"
166
-
167
- # Disable Jira issue number parsing in branch names (yes/no)
168
- GITBASH_CREATE_NO_ISSUE_PARSING="$no_issue_parsing"
169
-
170
- # Fallback prefix when no issue number is provided (only used if parsing is enabled)
171
- GITBASH_CREATE_ISSUE_PARSING_FALLBACK="$issue_fallback"
172
-
173
- # Theme for delta/bat: auto, dark, or light
174
- GITBASH_THEME="$theme"
175
-
176
- # Stale branch threshold in months (branches older than this in 'stale')
177
- GITBASH_STALE_MONTHS="$stale_months"
178
-
179
- # Cleanup threshold in days (branches merged more than this many days ago)
180
- GITBASH_CLEANUP_DAYS="$cleanup_days"
181
-
182
- EOF
183
-
184
- echo
185
- echo "Saved configuration to $CONFIG_FILE"
186
-
187
- # Check for dependencies
188
- check_dependencies
189
-
190
- # Check for shell integration
191
- check_shell_integration
192
- }
193
-
194
- configure_local_gitbash() {
195
- # Check if we're in a git repository
196
- local repo_root
197
- if ! repo_root=$(git rev-parse --show-toplevel 2>/dev/null); then
198
- echo "Error: Not inside a git repository." >&2
199
- exit 1
200
- fi
201
-
202
- local local_config="$repo_root/$LOCAL_CONFIG_FILE"
203
-
204
- # Load global config first to get current defaults
205
- load_config
206
-
207
- # Backward compatibility: migrate GITBASH_FEATURE_BRANCH_PREFIX to GITBASH_CREATE_BRANCH_PREFIX
208
- local current_prefix="${GITBASH_CREATE_BRANCH_PREFIX:-}"
209
- if [[ -z "$current_prefix" && -n "${GITBASH_FEATURE_BRANCH_PREFIX:-}" ]]; then
210
- # Remove "feature/" prefix from old variable
211
- current_prefix="${GITBASH_FEATURE_BRANCH_PREFIX#feature/}"
212
- fi
213
- # Remove trailing slash if present
214
- current_prefix="${current_prefix%/}"
215
-
216
- echo "Configure local gitbash overrides for this repository"
217
- echo "Config will be saved to: $local_config"
218
- echo
219
- echo "Press Enter to keep global setting, or enter a new value to override."
220
- echo "Enter '-' to explicitly unset a local override."
221
- echo
222
-
223
- local overrides=()
224
- local input
225
-
226
- # Branch prefix (current_prefix already set above with backward compatibility)
227
- echo "1. Branch prefix"
228
- echo " Current (global): ${current_prefix:-'(empty)'}"
229
- read -re -p " Local override: " input
230
- if [[ -n "$input" ]]; then
231
- if [[ "$input" == "-" ]]; then
232
- overrides+=("GITBASH_CREATE_BRANCH_PREFIX=\"\"")
233
- echo " → Will use empty prefix locally"
234
- else
235
- input="${input%/}" # Remove trailing slash
236
- overrides+=("GITBASH_CREATE_BRANCH_PREFIX=\"$input\"")
237
- echo " → Will use '$input' locally"
238
- fi
239
- else
240
- echo " → Keeping global setting"
241
- fi
242
- echo
243
-
244
- # Merge command
245
- local current_merge="${GITBASH_MERGE_COMMAND:-fork}"
246
- echo "2. Merge tool command (used by update on conflicts)"
247
- echo " Current (global): $current_merge"
248
- read -re -p " Local override: " input
249
- if [[ -n "$input" && "$input" != "-" ]]; then
250
- overrides+=("GITBASH_MERGE_COMMAND=\"$input\"")
251
- echo " → Will use '$input' locally"
252
- else
253
- echo " → Keeping global setting"
254
- fi
255
- echo
256
-
257
- # Issue parsing
258
- local current_no_issue_parsing="${GITBASH_CREATE_NO_ISSUE_PARSING:-no}"
259
- local current_enabled="yes"
260
- [[ "$current_no_issue_parsing" == "yes" ]] && current_enabled="no"
261
- echo "3. Enable Jira issue parsing in branch names? (yes/no)"
262
- echo " Current (global): $current_enabled"
263
- read -re -p " Local override: " input
264
- if [[ -n "$input" && "$input" != "-" ]]; then
265
- case "$input" in
266
- yes)
267
- overrides+=("GITBASH_CREATE_NO_ISSUE_PARSING=\"no\"")
268
- echo " → Will enable parsing locally"
269
- ;;
270
- no)
271
- overrides+=("GITBASH_CREATE_NO_ISSUE_PARSING=\"yes\"")
272
- echo " → Will disable parsing locally"
273
- ;;
274
- *)
275
- echo " → Invalid value, keeping global setting"
276
- ;;
277
- esac
278
- else
279
- echo " → Keeping global setting"
280
- fi
281
- echo
282
-
283
- # Issue fallback
284
- local current_issue_fallback="${GITBASH_CREATE_ISSUE_PARSING_FALLBACK:-NOISSUE}"
285
- echo "4. Fallback prefix when no issue number is provided"
286
- echo " Current (global): $current_issue_fallback"
287
- read -re -p " Local override: " input
288
- if [[ -n "$input" && "$input" != "-" ]]; then
289
- overrides+=("GITBASH_CREATE_ISSUE_PARSING_FALLBACK=\"$input\"")
290
- echo " → Will use '$input' locally"
172
+ echo "Configure gitbash ($scope) - writes to $file"
173
+ if [[ "$scope" == "global" ]]; then
174
+ echo "Press Enter to keep the current value, or '-' to reset it to the default."
291
175
  else
292
- echo " → Keeping global setting"
176
+ echo "Press Enter to keep this file's current setting, '-' to remove the override,"
177
+ echo "or '\"\"' to override with an empty value."
293
178
  fi
294
179
  echo
295
180
 
296
- # Theme
297
- local current_theme="${GITBASH_THEME:-auto}"
298
- echo "5. Theme for delta/bat (auto/dark/light)"
299
- echo " Current (global): $current_theme"
300
- read -re -p " Local override: " input
301
- if [[ -n "$input" && "$input" != "-" ]]; then
302
- case "$input" in
303
- auto|dark|light)
304
- overrides+=("GITBASH_THEME=\"$input\"")
305
- echo " → Will use '$input' locally"
306
- ;;
307
- *)
308
- echo " → Invalid value, keeping global setting"
309
- ;;
310
- esac
311
- else
312
- echo " → Keeping global setting"
313
- fi
314
- echo
315
-
316
- # Stale months
317
- local current_stale_months="${GITBASH_STALE_MONTHS:-3}"
318
- echo "6. Stale branch threshold in months"
319
- echo " Current (global): $current_stale_months"
320
- read -re -p " Local override: " input
321
- if [[ -n "$input" && "$input" != "-" ]]; then
322
- if [[ "$input" =~ ^[0-9]+$ ]] && [[ "$input" -ge 1 ]]; then
323
- overrides+=("GITBASH_STALE_MONTHS=\"$input\"")
324
- echo " → Will use '$input' locally"
325
- else
326
- echo " → Invalid value, keeping global setting"
181
+ local -a lines
182
+ local entry key type question current default answer stored has_override shown n=0
183
+ for entry in "${SETTINGS[@]}"; do
184
+ n=$((n + 1))
185
+ IFS='|' read -r key type question <<< "$entry"
186
+ [[ "$scope" == "local" && "$key" == "GITBASH_MERGE_COMMAND" ]] && continue
187
+
188
+ default=$(setting_default "$key")
189
+ has_override=false
190
+ if current=$(file_value "$file" "$key"); then
191
+ has_override=true
192
+ elif [[ "$scope" == "global" ]]; then
193
+ current="$default"
327
194
  fi
328
- else
329
- echo " → Keeping global setting"
330
- fi
331
- echo
332
195
 
333
- # Cleanup days
334
- local current_cleanup_days="${GITBASH_CLEANUP_DAYS:-7}"
335
- echo "7. Cleanup threshold in days"
336
- echo " Current (global): $current_cleanup_days"
337
- read -re -p " Local override: " input
338
- if [[ -n "$input" && "$input" != "-" ]]; then
339
- if [[ "$input" =~ ^[0-9]+$ ]] && [[ "$input" -ge 1 ]]; then
340
- overrides+=("GITBASH_CLEANUP_DAYS=\"$input\"")
341
- echo " → Will use '$input' locally"
196
+ echo "$n. $question"
197
+ if [[ "$scope" == "global" ]]; then
198
+ shown=$(to_display "$type" "$current")
342
199
  else
343
- echo " → Invalid value, keeping global setting"
200
+ shown=$(to_display "$type" "${!key}")
201
+ if [[ "$has_override" == true ]]; then
202
+ echo " Current override: '$(to_display "$type" "$current")'"
203
+ else
204
+ echo " Current (inherited): '$shown'"
205
+ fi
344
206
  fi
345
- else
346
- echo " → Keeping global setting"
347
- fi
348
- echo
349
-
350
- # Check if any overrides were set
351
- if [[ ${#overrides[@]} -eq 0 ]]; then
352
- echo "No local overrides configured."
353
- return 0
354
- fi
355
207
 
356
- # Write local config file
357
- {
358
- echo "# Local gitbash configuration overrides for this repository"
359
- echo "# Generated by 'gitbash --config-local'"
360
- echo "# These settings override the global ~/.gitbashrc"
361
- echo
362
- for override in "${overrides[@]}"; do
363
- echo "$override"
208
+ while true; do
209
+ if ! prompt_read " Value [$shown]: " answer; then
210
+ echo "Aborted - nothing was saved."
211
+ return 1
212
+ fi
213
+ if [[ -z "$answer" ]]; then
214
+ # Keep what this file has
215
+ if [[ "$scope" == "global" || "$has_override" == true ]]; then
216
+ lines+=("$key=\"$current\"")
217
+ fi
218
+ break
219
+ fi
220
+ if [[ "$answer" == "-" ]]; then
221
+ if [[ "$scope" == "global" ]]; then
222
+ lines+=("$key=\"$default\"")
223
+ fi
224
+ break
225
+ fi
226
+ [[ "$answer" == '""' ]] && answer=""
227
+ if [[ -z "$answer" && "$type" != "prefix" && "$type" != "text" ]]; then
228
+ echo " This setting cannot be empty." >&2
229
+ continue
230
+ fi
231
+ if stored=$(to_stored "$type" "$answer"); then
232
+ lines+=("$key=\"$stored\"")
233
+ break
234
+ fi
364
235
  done
365
- } > "$local_config"
366
-
367
- echo "Saved local configuration to $local_config"
368
- }
369
-
370
- configure_user_gitbash() {
371
- # Check if we're in a git repository
372
- local repo_root
373
- if ! repo_root=$(git rev-parse --show-toplevel 2>/dev/null); then
374
- echo "Error: Not inside a git repository." >&2
375
- exit 1
376
- fi
377
-
378
- local user_config="$repo_root/$USER_CONFIG_FILE"
379
- local gitignore="$repo_root/.gitignore"
380
-
381
- # Load all configs to get current effective values
382
- load_config
383
-
384
- # Backward compatibility: migrate GITBASH_FEATURE_BRANCH_PREFIX to GITBASH_CREATE_BRANCH_PREFIX
385
- local current_prefix="${GITBASH_CREATE_BRANCH_PREFIX:-}"
386
- if [[ -z "$current_prefix" && -n "${GITBASH_FEATURE_BRANCH_PREFIX:-}" ]]; then
387
- # Remove "feature/" prefix from old variable
388
- current_prefix="${GITBASH_FEATURE_BRANCH_PREFIX#feature/}"
389
- fi
390
- # Remove trailing slash if present
391
- current_prefix="${current_prefix%/}"
392
-
393
- echo "Configure user-specific gitbash overrides for this repository"
394
- echo "Config will be saved to: $user_config"
395
- echo "This file is intended for personal settings and excluded from version control."
396
- echo
397
- echo "Press Enter to keep current setting, or enter a new value to override."
398
- echo "Enter '-' to explicitly unset an override."
399
- echo
400
-
401
- local overrides=()
402
- local input
403
-
404
- # Branch prefix
405
- echo "1. Branch prefix"
406
- echo " Current (effective): ${current_prefix:-'(empty)'}"
407
- read -re -p " User override: " input
408
- if [[ -n "$input" ]]; then
409
- if [[ "$input" == "-" ]]; then
410
- overrides+=("GITBASH_CREATE_BRANCH_PREFIX=\"\"")
411
- echo " → Will use empty prefix"
412
- else
413
- input="${input%/}" # Remove trailing slash
414
- overrides+=("GITBASH_CREATE_BRANCH_PREFIX=\"$input\"")
415
- echo " → Will use '$input'"
416
- fi
417
- else
418
- echo " → Keeping current setting"
419
- fi
420
- echo
421
-
422
- # Merge command
423
- local current_merge="${GITBASH_MERGE_COMMAND:-fork}"
424
- echo "2. Merge tool command (used by update on conflicts)"
425
- echo " Current (effective): $current_merge"
426
- read -re -p " User override: " input
427
- if [[ -n "$input" && "$input" != "-" ]]; then
428
- overrides+=("GITBASH_MERGE_COMMAND=\"$input\"")
429
- echo " → Will use '$input'"
430
- else
431
- echo " → Keeping current setting"
432
- fi
433
- echo
434
-
435
- # Issue parsing
436
- local current_no_issue_parsing="${GITBASH_CREATE_NO_ISSUE_PARSING:-no}"
437
- local current_enabled="yes"
438
- [[ "$current_no_issue_parsing" == "yes" ]] && current_enabled="no"
439
- echo "3. Enable Jira issue parsing in branch names? (yes/no)"
440
- echo " Current (effective): $current_enabled"
441
- read -re -p " User override: " input
442
- if [[ -n "$input" && "$input" != "-" ]]; then
443
- case "$input" in
444
- yes)
445
- overrides+=("GITBASH_CREATE_NO_ISSUE_PARSING=\"no\"")
446
- echo " → Will enable parsing"
447
- ;;
448
- no)
449
- overrides+=("GITBASH_CREATE_NO_ISSUE_PARSING=\"yes\"")
450
- echo " → Will disable parsing"
451
- ;;
452
- *)
453
- echo " → Invalid value, keeping current setting"
454
- ;;
455
- esac
456
- else
457
- echo " → Keeping current setting"
458
- fi
459
- echo
460
-
461
- # Issue fallback
462
- local current_issue_fallback="${GITBASH_CREATE_ISSUE_PARSING_FALLBACK:-NOISSUE}"
463
- echo "4. Fallback prefix when no issue number is provided"
464
- echo " Current (effective): $current_issue_fallback"
465
- read -re -p " User override: " input
466
- if [[ -n "$input" && "$input" != "-" ]]; then
467
- overrides+=("GITBASH_CREATE_ISSUE_PARSING_FALLBACK=\"$input\"")
468
- echo " → Will use '$input'"
469
- else
470
- echo " → Keeping current setting"
471
- fi
472
- echo
473
-
474
- # Theme
475
- local current_theme="${GITBASH_THEME:-auto}"
476
- echo "5. Theme for delta/bat (auto/dark/light)"
477
- echo " Current (effective): $current_theme"
478
- read -re -p " User override: " input
479
- if [[ -n "$input" && "$input" != "-" ]]; then
480
- case "$input" in
481
- auto|dark|light)
482
- overrides+=("GITBASH_THEME=\"$input\"")
483
- echo " → Will use '$input'"
484
- ;;
485
- *)
486
- echo " → Invalid value, keeping current setting"
487
- ;;
488
- esac
489
- else
490
- echo " → Keeping current setting"
491
- fi
492
- echo
493
-
494
- # Stale months
495
- local current_stale_months="${GITBASH_STALE_MONTHS:-3}"
496
- echo "6. Stale branch threshold in months"
497
- echo " Current (effective): $current_stale_months"
498
- read -re -p " User override: " input
499
- if [[ -n "$input" && "$input" != "-" ]]; then
500
- if [[ "$input" =~ ^[0-9]+$ ]] && [[ "$input" -ge 1 ]]; then
501
- overrides+=("GITBASH_STALE_MONTHS=\"$input\"")
502
- echo " → Will use '$input'"
503
- else
504
- echo " → Invalid value, keeping current setting"
505
- fi
506
- else
507
- echo " → Keeping current setting"
508
- fi
509
- echo
236
+ echo
237
+ done
510
238
 
511
- # Cleanup days
512
- local current_cleanup_days="${GITBASH_CLEANUP_DAYS:-7}"
513
- echo "7. Cleanup threshold in days"
514
- echo " Current (effective): $current_cleanup_days"
515
- read -re -p " User override: " input
516
- if [[ -n "$input" && "$input" != "-" ]]; then
517
- if [[ "$input" =~ ^[0-9]+$ ]] && [[ "$input" -ge 1 ]]; then
518
- overrides+=("GITBASH_CLEANUP_DAYS=\"$input\"")
519
- echo " → Will use '$input'"
239
+ if [[ "$scope" != "global" && ${#lines[@]} -eq 0 ]]; then
240
+ if [[ -f "$file" ]]; then
241
+ rm -f "$file"
242
+ print_info "No overrides left - removed $file"
520
243
  else
521
- echo " → Invalid value, keeping current setting"
244
+ print_info "No overrides configured."
522
245
  fi
523
- else
524
- echo " → Keeping current setting"
525
- fi
526
- echo
527
-
528
- # Check if any overrides were set
529
- if [[ ${#overrides[@]} -eq 0 ]]; then
530
- echo "No user overrides configured."
531
246
  return 0
532
247
  fi
533
248
 
534
- # Write user config file
249
+ local flag="--config"
250
+ [[ "$scope" != "global" ]] && flag="--config-$scope"
535
251
  {
536
- echo "# User-specific gitbash configuration overrides for this repository"
537
- echo "# Generated by 'gitbash --config-user'"
538
- echo "# These settings override both global (~/.gitbashrc) and local (.gitbashrc) settings"
539
- echo "# This file should NOT be committed to version control"
252
+ echo "# gitbash configuration ($scope). Generated by 'gitbash $flag'."
253
+ echo "# Only plain GITBASH_*=\"value\" lines are read; nothing in this file is executed."
254
+ [[ "$scope" == "user" ]] && echo "# Personal settings - not committed (listed in .git/info/exclude)."
540
255
  echo
541
- for override in "${overrides[@]}"; do
542
- echo "$override"
256
+ local line
257
+ for line in ${lines[@]+"${lines[@]}"}; do
258
+ echo "$line"
543
259
  done
544
- } > "$user_config"
545
-
546
- echo "Saved user configuration to $user_config"
547
-
548
- # Add to .gitignore if not already present
549
- if [[ -f "$gitignore" ]]; then
550
- if ! grep -qxF "$USER_CONFIG_FILE" "$gitignore"; then
551
- echo "$USER_CONFIG_FILE" >> "$gitignore"
552
- echo "Added '$USER_CONFIG_FILE' to .gitignore"
553
- else
554
- echo "'$USER_CONFIG_FILE' already in .gitignore"
555
- fi
556
- else
557
- echo "$USER_CONFIG_FILE" > "$gitignore"
558
- echo "Created .gitignore with '$USER_CONFIG_FILE'"
559
- fi
560
- }
561
-
562
- # Check and offer to install dependencies
563
- check_dependencies() {
564
- local missing_required=()
565
- local missing_optional=()
260
+ } > "$file"
261
+ print_success "Saved $file"
566
262
 
567
- # Optional dependencies (fzf is optional; commands will try to run without it)
568
- if ! command -v fzf >/dev/null 2>&1; then
569
- missing_optional+=("fzf")
570
- fi
571
-
572
- # Optional dependencies
573
- if ! command -v delta >/dev/null 2>&1; then
574
- missing_optional+=("git-delta")
575
- fi
576
- if ! command -v bat >/dev/null 2>&1; then
577
- missing_optional+=("bat")
263
+ if [[ "$scope" == "user" ]]; then
264
+ exclude_user_config
265
+ elif [[ "$scope" == "global" ]]; then
266
+ check_dependencies
267
+ check_shell_integration
578
268
  fi
269
+ }
579
270
 
580
- # If nothing missing, we're done
581
- if [[ ${#missing_required[@]} -eq 0 && ${#missing_optional[@]} -eq 0 ]]; then
582
- echo
583
- echo "All dependencies installed ✓"
271
+ # Keep .gitbashrc-user out of git without touching the tracked .gitignore
272
+ exclude_user_config() {
273
+ local exclude
274
+ exclude=$(git rev-parse --git-path info/exclude 2>/dev/null) || return 0
275
+ mkdir -p "$(dirname "$exclude")"
276
+ if [[ -f "$exclude" ]] && grep -qxF ".gitbashrc-user" "$exclude"; then
584
277
  return 0
585
278
  fi
586
-
587
- echo
588
- echo "Dependencies:"
589
-
590
- # Show status of each dependency
591
- if command -v fzf >/dev/null 2>&1; then
592
- echo " ✓ fzf (optional)"
593
- else
594
- echo " ✗ fzf (optional) - fuzzy finder for interactive selection"
595
- fi
596
-
597
- if command -v delta >/dev/null 2>&1; then
598
- echo " ✓ git-delta (optional)"
599
- else
600
- echo " ✗ git-delta (optional) - better diff highlighting"
601
- fi
602
-
603
- if command -v bat >/dev/null 2>&1; then
604
- echo " ✓ bat (optional)"
605
- else
606
- echo " ✗ bat (optional) - syntax highlighting for file previews"
279
+ # Make sure we start on a new line
280
+ if [[ -s "$exclude" && -n "$(tail -c 1 "$exclude")" ]]; then
281
+ echo >> "$exclude"
607
282
  fi
283
+ echo ".gitbashrc-user" >> "$exclude"
284
+ print_info "Added .gitbashrc-user to $exclude"
285
+ }
608
286
 
609
- # Build list of all missing dependencies
610
- local all_missing=()
611
- [[ ${#missing_required[@]} -gt 0 ]] && all_missing+=("${missing_required[@]}")
612
- [[ ${#missing_optional[@]} -gt 0 ]] && all_missing+=("${missing_optional[@]}")
287
+ # Report missing optional dependencies and offer to install them with Homebrew
288
+ check_dependencies() {
289
+ local missing=() dep
290
+ command -v fzf >/dev/null 2>&1 || missing+=("fzf")
291
+ command -v delta >/dev/null 2>&1 || missing+=("git-delta")
292
+ command -v bat >/dev/null 2>&1 || command -v batcat >/dev/null 2>&1 || missing+=("bat")
613
293
 
614
- # If nothing to install, we're done
615
- if [[ ${#all_missing[@]} -eq 0 ]]; then
294
+ echo
295
+ if [[ ${#missing[@]} -eq 0 ]]; then
296
+ if require_fzf; then
297
+ print_success "All dependencies installed."
298
+ fi
616
299
  return 0
617
300
  fi
618
301
 
619
- # Check if Homebrew is available
302
+ echo "Missing optional dependencies: ${missing[*]}"
303
+ echo " fzf (>= $GB_FZF_MIN_VERSION) - interactive menus, git-delta - diff highlighting, bat - file previews"
620
304
  if ! command -v brew >/dev/null 2>&1; then
621
- echo
622
- echo "Homebrew not found. Install dependencies manually:"
623
- for dep in "${all_missing[@]}"; do
624
- echo " brew install $dep"
625
- done
305
+ echo "Install them with your package manager, e.g.: sudo apt install ${missing[*]}"
626
306
  return 0
627
307
  fi
308
+ if gb_confirm "Install them with Homebrew?" y; then
309
+ for dep in "${missing[@]}"; do
310
+ if brew install "$dep"; then
311
+ print_success "$dep installed"
312
+ else
313
+ print_error "Failed to install $dep"
314
+ fi
315
+ done
316
+ fi
317
+ }
628
318
 
629
- # Offer to install missing dependencies
630
- echo
631
- local install_deps
632
- read -re -p "Install missing dependencies with Homebrew? (Y/n): " install_deps
633
- case "$install_deps" in
634
- [nN][oO]|[nN])
635
- echo "Skipped dependency installation."
319
+ # Offer to add the shell integration to the rc file of the user's shell
320
+ check_shell_integration() {
321
+ local shell_name shell_rc
322
+ shell_name=$(basename "${SHELL:-}")
323
+ case "$shell_name" in
324
+ zsh) shell_rc="$HOME/.zshrc" ;;
325
+ bash)
326
+ if [[ -f "$HOME/.bashrc" ]]; then
327
+ shell_rc="$HOME/.bashrc"
328
+ else
329
+ shell_rc="$HOME/.bash_profile"
330
+ fi
636
331
  ;;
637
332
  *)
638
- echo "Installing dependencies..."
639
- for dep in "${all_missing[@]}"; do
640
- echo " Installing $dep..."
641
- if brew install "$dep" 2>/dev/null; then
642
- echo " ✓ $dep installed"
643
- else
644
- echo " ✗ Failed to install $dep"
645
- fi
646
- done
333
+ echo
334
+ print_info "Shell '$shell_name' is not supported for integration. Use 'gitbash <command>' or aliases."
335
+ return 0
647
336
  ;;
648
337
  esac
649
- }
650
338
 
651
- # Check if gitbash shell integration is set up
652
- check_shell_integration() {
653
- local shell_rc=""
654
- local shell_files=("$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile")
655
-
656
- # Find the first existing shell rc file
657
- for file in "${shell_files[@]}"; do
658
- if [[ -f "$file" ]]; then
659
- shell_rc="$file"
660
- break
661
- fi
662
- done
663
-
664
- if [[ -z "$shell_rc" ]]; then
665
- echo
666
- echo "No shell config file found (.zshrc, .bashrc, .bash_profile)"
339
+ echo
340
+ if [[ -f "$shell_rc" ]] && grep -q 'gitbash --init' "$shell_rc"; then
341
+ print_info "Shell integration already configured in $shell_rc"
667
342
  return 0
668
343
  fi
669
-
670
- # Check if gitbash integration already exists
671
- local has_init=false
672
- local has_alias=false
673
-
674
- if grep -q 'eval.*gitbash --init' "$shell_rc" 2>/dev/null; then
675
- has_init=true
676
- fi
677
-
678
- if grep -q 'alias.*gitbash' "$shell_rc" 2>/dev/null; then
679
- has_alias=true
680
- fi
681
-
682
- # If neither init nor alias found, offer to add
683
- if [[ "$has_init" == false && "$has_alias" == false ]]; then
684
- echo
685
- echo "Shell integration not found in $shell_rc"
686
- local add_init
687
- read -re -p "Add 'eval \"\$(gitbash --init)\"' to $shell_rc? (Y/n): " add_init
688
- case "$add_init" in
689
- [nN][oO]|[nN])
690
- echo "Skipped shell integration."
691
- ;;
692
- *)
693
- echo >> "$shell_rc"
694
- echo '# gitbash shell integration' >> "$shell_rc"
695
- echo 'eval "$(gitbash --init)"' >> "$shell_rc"
696
- echo
697
- echo "Added to $shell_rc"
698
- echo "Run 'source $shell_rc' or restart your shell to activate."
699
- ;;
700
- esac
701
- else
702
- echo
703
- echo "Shell integration already configured in $shell_rc"
344
+ if gb_confirm "Add 'eval \"\$(gitbash --init)\"' to $shell_rc?" y; then
345
+ {
346
+ echo
347
+ echo '# gitbash shell integration'
348
+ # shellcheck disable=SC2016 # written literally to the rc file
349
+ echo 'eval "$(gitbash --init)"'
350
+ } >> "$shell_rc"
351
+ print_success "Added to $shell_rc. Restart your shell or run 'source $shell_rc'."
704
352
  fi
705
353
  }
706
354
 
707
- # Resolve symlinks to get the actual script location
708
- SOURCE="${BASH_SOURCE[0]}"
709
- while [[ -L "$SOURCE" ]]; do
710
- DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
711
- SOURCE="$(readlink "$SOURCE")"
712
- [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
713
- done
714
- SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
715
-
716
- # Export so sourced command files can use it
717
- export SCRIPT_DIR
718
-
719
- COMMANDS_DIR="$SCRIPT_DIR/commands"
720
- VERSION="$(grep -o '"version": *"[^"]*"' "$SCRIPT_DIR/package.json" | head -1 | cut -d'"' -f4)"
721
-
722
- # Available commands
723
- COMMANDS=(
724
- "branch"
725
- "cleanstash"
726
- "cleanup"
727
- "commit"
728
- "commits"
729
- "create"
730
- "pr"
731
- "stale"
732
- "stash"
733
- "stashes"
734
- "status"
735
- "switch"
736
- "unstash"
737
- "update"
738
- )
355
+ # =============================================================================
356
+ # Help and shell integration
357
+ # =============================================================================
739
358
 
740
359
  show_help() {
741
- cat << 'EOF'
742
- gitbash - Interactive git utilities for bash
360
+ cat << EOF
361
+ gitbash $VERSION - Interactive git utilities for bash
743
362
 
744
363
  Usage:
745
364
  gitbash <command> [args...]
746
- gitbash --help
747
- gitbash --init
748
- gitbash --config
749
- gitbash --config-local
750
- gitbash --config-user
365
+ gitbash --help | --version
366
+ gitbash --init [--prefix=PREFIX]
367
+ gitbash --config | --config-local | --config-user
751
368
 
752
369
  Commands:
753
370
  branch Interactive menu for branch operations
@@ -768,10 +385,11 @@ Commands:
768
385
  Options:
769
386
  -h, --help Show this help message
770
387
  -v, --version Show version number
771
- --init Print shell initialization code (for sourcing)
388
+ --init Print shell functions for calling commands directly
389
+ (--prefix=gb- creates gb-commit, gb-switch, ...)
772
390
  --config Configure gitbash and save to ~/.gitbashrc
773
- --config-local Configure local overrides for current repository
774
- --config-user Configure user-specific overrides (excluded from git)
391
+ --config-local Configure overrides for this repository (.gitbashrc, committed)
392
+ --config-user Configure personal overrides for this repository (.gitbashrc-user)
775
393
 
776
394
  Examples:
777
395
  gitbash commit fix bug -p # Commit and push
@@ -779,83 +397,87 @@ Examples:
779
397
  gitbash create PROJ-123 fix # Create feature/PROJ-123-fix
780
398
 
781
399
  Shell Integration:
782
- Add to your .bashrc or .bash_profile:
783
- eval "$(gitbash --init)"
400
+ Add to your .zshrc or .bashrc:
401
+ eval "\$(gitbash --init)"
784
402
 
785
403
  This enables calling commands directly:
786
404
  commit fix bug -p
787
405
  switch feature
788
406
 
789
- Dependencies:
790
- brew install fzf # Required
791
- brew install git-delta # Optional: better diffs
792
- brew install bat # Optional: syntax highlighting
407
+ Dependencies (optional):
408
+ fzf >= $GB_FZF_MIN_VERSION Interactive menus (brew install fzf)
409
+ git-delta Better diffs (brew install git-delta)
410
+ bat Syntax highlighting (brew install bat)
793
411
  EOF
794
412
  }
795
413
 
414
+ # Print wrapper functions for the interactive shell. Commands still run in
415
+ # their own bash process, so nothing leaks into the user's shell.
796
416
  show_init() {
797
- cat << EOF
798
- # gitbash shell integration
799
- # Add this to your .bashrc or .bash_profile:
800
- # eval "\$(gitbash --init)"
801
-
802
- GITBASH_COMMANDS_DIR="$COMMANDS_DIR"
803
-
804
- EOF
805
- # Output source commands for each script
417
+ local prefix="" arg cmd bin_quoted
418
+ for arg in "$@"; do
419
+ case "$arg" in
420
+ --prefix=*) prefix="${arg#--prefix=}" ;;
421
+ *) print_error "Unknown option for --init: $arg"; return 1 ;;
422
+ esac
423
+ done
424
+ if ! [[ "$prefix" =~ ^[A-Za-z0-9_-]*$ ]]; then
425
+ print_error "Invalid prefix '$prefix' (use letters, digits, '-' or '_')."
426
+ return 1
427
+ fi
428
+ printf -v bin_quoted '%q' "$GITBASH_BIN"
429
+ echo "# gitbash shell integration - add to your .zshrc or .bashrc:"
430
+ echo "# eval \"\$(gitbash --init${prefix:+ --prefix=$prefix})\""
806
431
  for cmd in "${COMMANDS[@]}"; do
807
- if [[ -f "$COMMANDS_DIR/$cmd.sh" ]]; then
808
- echo "source \"\$GITBASH_COMMANDS_DIR/$cmd.sh\""
809
- fi
432
+ echo "${prefix}${cmd}() { $bin_quoted $cmd \"\$@\"; }"
810
433
  done
811
434
  }
812
435
 
436
+ # =============================================================================
813
437
  # Main entry point
438
+ # =============================================================================
439
+
814
440
  main() {
815
- load_config
816
441
  case "${1:-}" in
817
442
  -h|--help|"")
818
443
  show_help
819
- exit 0
820
444
  ;;
821
445
  -v|--version)
822
446
  echo "gitbash $VERSION"
823
- exit 0
824
447
  ;;
825
448
  --init)
826
- show_init
827
- exit 0
449
+ shift
450
+ show_init "$@"
828
451
  ;;
829
452
  --config)
830
- configure_gitbash
831
- exit 0
453
+ configure_wizard global
832
454
  ;;
833
455
  --config-local)
834
- configure_local_gitbash
835
- exit 0
456
+ configure_wizard local
836
457
  ;;
837
458
  --config-user)
838
- configure_user_gitbash
839
- exit 0
459
+ configure_wizard user
460
+ ;;
461
+ -*)
462
+ print_error "Unknown option '$1'"
463
+ echo "Run 'gitbash --help' for available options." >&2
464
+ return 1
840
465
  ;;
841
466
  *)
842
467
  local cmd="$1"
843
468
  shift
844
-
845
- # Check if command exists
846
- if [[ -f "$COMMANDS_DIR/$cmd.sh" ]]; then
847
- # Source the command file and execute the function
848
- source "$COMMANDS_DIR/$cmd.sh"
849
- "$cmd" "$@"
850
- else
851
- echo "Error: Unknown command '$cmd'" >&2
469
+ if ! is_command "$cmd"; then
470
+ print_error "Unknown command '$cmd'"
852
471
  echo "Run 'gitbash --help' for available commands." >&2
853
- exit 1
472
+ return 1
854
473
  fi
474
+ gb_load_config
475
+ # shellcheck source=/dev/null
476
+ source "$COMMANDS_DIR/$cmd.sh"
477
+ "$cmd" "$@"
855
478
  ;;
856
479
  esac
857
480
  }
858
481
 
859
482
  main "$@"
860
-
861
-
483
+ exit $?