Repository navigation
Expand file tree
/
Copy pathgcom-cli.sh
More file actions
256 lines (233 loc) · 8.91 KB
/
Copy pathgcom-cli.sh
File metadata and controls
256 lines (233 loc) · 8.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#!/usr/bin/env bash
#
# gcom-cli — Interactive Conventional Commits helper
# ---------------------------------------------------
#
# A small, dependency-free Bash wrapper around `git commit` that guides the
# user through producing a well-formed Conventional Commits message:
#
# <type>(<scope>): <description>
#
# Features:
# * Verifies the current working directory is inside a Git repository.
# * Detects whether anything is staged for commit and offers to stage all
# tracked changes (`git add -A`) when nothing has been added yet.
# * Presents a numbered menu of standard Conventional Commit types
# (feat, fix, docs, style, refactor, chore, perf, test, build, ci, revert).
# * Accepts an optional scope (e.g. `api`, `ui`, `auth`) which is wrapped in
# parentheses according to the Conventional Commits specification.
# * Requires a non-empty short description and shows a final preview of the
# commit message before invoking `git commit`.
# * Uses ANSI color codes for clearer terminal output, with graceful
# degradation when the output is not a TTY (colors are stripped).
#
# Exit codes:
# 0 commit was created successfully
# 1 user aborted, no staged changes, or empty description
# 2 not inside a Git working tree
#
# Usage:
# gcom-cli # run the interactive wizard
# gcom-cli -h|--help # print usage information
# gcom-cli -v|--version
#
# See README.md for installation instructions on common Linux distributions.
#
set -euo pipefail
# ---------------------------------------------------------------------------
# Metadata
# ---------------------------------------------------------------------------
readonly GCOM_CLI_VERSION="1.0.0"
readonly GCOM_CLI_NAME="gcom-cli"
# ---------------------------------------------------------------------------
# Color setup
# ---------------------------------------------------------------------------
# Only emit ANSI escape sequences when stdout is attached to a terminal so the
# output stays clean when piped to a file or another program.
if [ -t 1 ]; then
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
GREEN='\033[0;32m'
BOLD='\033[1m'
NC='\033[0m'
else
CYAN=''
YELLOW=''
RED=''
GREEN=''
BOLD=''
NC=''
fi
# ---------------------------------------------------------------------------
# Helper functions
# ---------------------------------------------------------------------------
# Print an informational message in cyan.
info() {
printf "${CYAN}%s${NC}\n" "$*"
}
# Print a warning message in yellow.
warn() {
printf "${YELLOW}%s${NC}\n" "$*"
}
# Print an error message in red to stderr.
err() {
printf "${RED}Error:${NC} %s\n" "$*" >&2
}
# Print a success message in green.
ok() {
printf "${GREEN}%s${NC}\n" "$*"
}
# Print short usage text and exit with the supplied status code (default 0).
usage() {
cat <<EOF
${BOLD}${GCOM_CLI_NAME}${NC} — interactive Conventional Commits helper
Usage:
${GCOM_CLI_NAME} Run the interactive commit wizard
${GCOM_CLI_NAME} -h, --help Show this help message and exit
${GCOM_CLI_NAME} -v, --version
Show version information and exit
The wizard will:
1. Verify you are inside a Git repository.
2. Offer to stage all changes when nothing is staged yet.
3. Let you pick a Conventional Commit type from a numbered menu.
4. Optionally accept a scope (e.g. api, ui, auth).
5. Ask for a short description.
6. Show a preview, then run \`git commit\`.
EOF
exit "${1:-0}"
}
# Print version information and exit.
version() {
printf "%s %s\n" "${GCOM_CLI_NAME}" "${GCOM_CLI_VERSION}"
exit 0
}
# ---------------------------------------------------------------------------
# CLI argument handling
# ---------------------------------------------------------------------------
if [ "$#" -gt 0 ]; then
case "$1" in
-h|--help) usage 0 ;;
-v|--version) version ;;
*)
err "Unknown argument: $1"
usage 1
;;
esac
fi
# ---------------------------------------------------------------------------
# Banner
# ---------------------------------------------------------------------------
printf "${CYAN}${BOLD}Git Commit Helper${NC} ${CYAN}(Conventional Commits)${NC}\n"
# ---------------------------------------------------------------------------
# 1. Ensure we're inside a Git working tree.
# ---------------------------------------------------------------------------
# `git rev-parse --is-inside-work-tree` prints "true" and exits 0 only when
# invoked from inside a Git repo. Anything else means we should bail out
# early rather than confuse the user with a cryptic git error.
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
err "Not inside a Git repository. Run this command from within a Git project."
exit 2
fi
# ---------------------------------------------------------------------------
# 2. Make sure there is something to commit.
# ---------------------------------------------------------------------------
# `git diff --cached --quiet` exits with 0 when the staged tree matches HEAD
# (i.e. nothing is staged). In that case we offer to stage all tracked and
# untracked changes via `git add -A` so the user does not have to leave the
# wizard. If they decline, abort cleanly.
if git diff --cached --quiet; then
warn "No staged changes detected."
printf "${YELLOW}Stage all changes with 'git add -A' now? [y/N]:${NC} "
read -r add_all
case "${add_all:-}" in
y|Y|yes|YES)
git add -A
# After staging, verify there really was something to add.
if git diff --cached --quiet; then
err "Still nothing to commit. Aborting."
exit 1
fi
ok "All changes staged."
;;
*)
err "Commit cancelled. Stage your changes first (e.g. 'git add <files>')."
exit 1
;;
esac
fi
# ---------------------------------------------------------------------------
# 3. Choose a commit type.
# ---------------------------------------------------------------------------
# The list below is the standard set defined by the Conventional Commits
# specification, plus a few widely-used additions (build, ci, revert).
# `select` is a Bash builtin that renders a numbered menu and loops until
# the user selects a valid entry.
printf "\n"
info "Select a commit type:"
options=(
"feat" # A new feature for the user
"fix" # A bug fix for the user
"docs" # Documentation only changes
"style" # Formatting, whitespace, missing semicolons, etc.
"refactor" # Code change that neither fixes a bug nor adds a feature
"chore" # Maintenance tasks (build tooling, deps, housekeeping)
"perf" # A code change that improves performance
"test" # Adding or correcting tests
"build" # Changes that affect the build system or dependencies
"ci" # Changes to CI configuration files and scripts
"revert" # Reverts a previous commit
)
PS3="Enter number: "
type=""
select opt in "${options[@]}"; do
if [ -n "${opt:-}" ]; then
type="$opt"
break
else
warn "Please choose a valid number from the list."
fi
done
# ---------------------------------------------------------------------------
# 4. Optional scope.
# ---------------------------------------------------------------------------
# A scope further qualifies the commit, e.g. `feat(api): ...`. It is wrapped
# in parentheses if provided. Whitespace is trimmed so accidental spaces do
# not leak into the commit subject.
printf "\n"
info "Scope (optional, press Enter to skip):"
read -r scope
scope="${scope#"${scope%%[![:space:]]*}"}" # trim leading whitespace
scope="${scope%"${scope##*[![:space:]]}"}" # trim trailing whitespace
if [ -n "$scope" ]; then
scope="($scope)"
fi
# ---------------------------------------------------------------------------
# 5. Short description.
# ---------------------------------------------------------------------------
# The description is the imperative-mood subject line, e.g. "add login form".
# Empty descriptions are rejected because Conventional Commits requires one.
printf "\n"
info "Short description:"
read -r desc
desc="${desc#"${desc%%[![:space:]]*}"}"
desc="${desc%"${desc##*[![:space:]]}"}"
if [ -z "$desc" ]; then
err "Description cannot be empty."
exit 1
fi
# ---------------------------------------------------------------------------
# 6. Build the final message and commit.
# ---------------------------------------------------------------------------
final_msg="${type}${scope}: ${desc}"
printf "\n"
warn "Commit message preview:"
printf " %s\n\n" "$final_msg"
# `git commit` will open the user's $EDITOR if it fails (e.g. due to a hook
# rejecting the commit), so we just propagate its exit status.
if git commit -m "$final_msg"; then
ok "Commit created successfully."
else
err "git commit failed. See output above."
exit 1
fi