gitbash 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,6 +2,8 @@
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
@@ -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,24 +17,68 @@ 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
@@ -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
@@ -33,12 +60,103 @@ prompt_read_edit() {
33
60
  fi
34
61
  }
35
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: light (default), dark, or auto
113
+ _get_delta_args() {
114
+ local theme="${GITBASH_THEME:-light}"
115
+ case "$theme" in
116
+ dark)
117
+ echo "--dark"
118
+ ;;
119
+ auto)
120
+ # auto - let delta detect
121
+ echo ""
122
+ ;;
123
+ *)
124
+ # light is default
125
+ echo "--light"
126
+ ;;
127
+ esac
128
+ }
129
+
130
+ # Get bat arguments based on theme setting
131
+ _get_bat_args() {
132
+ local theme="${GITBASH_THEME:-light}"
133
+ case "$theme" in
134
+ dark)
135
+ echo "--theme=Dracula"
136
+ ;;
137
+ auto)
138
+ # auto - let bat detect
139
+ echo ""
140
+ ;;
141
+ *)
142
+ # light is default
143
+ echo "--theme=GitHub"
144
+ ;;
145
+ esac
146
+ }
147
+
36
148
  # Get the diff command to use (delta if installed, otherwise git diff)
37
149
  # Usage: get_diff_cmd
38
150
  # Returns the command name to use for diffs
39
151
  get_diff_cmd() {
40
152
  if command -v delta >/dev/null 2>&1; then
41
- echo "delta --light"
153
+ local delta_args
154
+ delta_args=$(_get_delta_args)
155
+ if [[ -n "$delta_args" ]]; then
156
+ echo "delta $delta_args"
157
+ else
158
+ echo "delta"
159
+ fi
42
160
  else
43
161
  echo "git diff"
44
162
  fi
@@ -50,7 +168,10 @@ get_diff_cmd() {
50
168
  # Example: show_diff HEAD~1
51
169
  show_diff() {
52
170
  if command -v delta >/dev/null 2>&1; then
53
- git diff "$@" | delta --light
171
+ local delta_args
172
+ delta_args=$(_get_delta_args)
173
+ # shellcheck disable=SC2086
174
+ git diff "$@" | delta $delta_args
54
175
  else
55
176
  git diff "$@"
56
177
  fi
@@ -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
@@ -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
@@ -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
  }