198 lines
4.4 KiB
Bash
Executable file
198 lines
4.4 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
# Conventional commit helper
|
|
# Usage: gc [type] [message] [-s scope] [-b] [-B body]
|
|
# gc -e [type] (open in editor with template)
|
|
# gc (interactive mode)
|
|
|
|
set -o errexit
|
|
set -o nounset
|
|
|
|
TYPES="feat fix docs style refactor test chore perf ci build"
|
|
|
|
usage() {
|
|
echo "Usage: gc [type] [message] [--scope scope] [-b] [-B body]"
|
|
echo ' gc -e [type] # open in $EDITOR'
|
|
echo " gc (interactive mode)"
|
|
echo ""
|
|
echo "Options:"
|
|
echo ' -e, --edit Open commit message in $EDITOR'
|
|
echo " --scope Scope of the change"
|
|
echo " -b, --breaking Mark as breaking change (!)"
|
|
echo " -B, --body Commit body (longer description)"
|
|
echo ""
|
|
echo "Git passthrough flags:"
|
|
echo " -s, --signoff Add Signed-off-by trailer"
|
|
echo " -a, --all Stage all modified files"
|
|
echo " -S, --gpg-sign GPG sign the commit"
|
|
echo " -v, --verbose Show diff in editor"
|
|
echo " --amend Amend previous commit"
|
|
echo ""
|
|
echo "Types: $TYPES"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " gc feat 'add user auth'"
|
|
echo " gc fix 'resolve crash' -s api # fix(api): resolve crash"
|
|
echo " gc feat 'new api' -b # feat!: new api"
|
|
echo " gc feat 'new api' -s auth -b # feat(auth)!: new api"
|
|
echo " gc feat 'big change' -B 'Details here'"
|
|
echo ' gc -e feat # edit feat commit in $EDITOR'
|
|
exit 1
|
|
}
|
|
|
|
editor_mode() {
|
|
local type="$1"
|
|
shift
|
|
local git_args=("$@")
|
|
local template
|
|
|
|
template=$(mktemp)
|
|
trap "rm -f $template" EXIT
|
|
|
|
cat >"$template" <<'EOF'
|
|
|
|
# Conventional Commit Format:
|
|
# <type>(<scope>)!: <description>
|
|
#
|
|
# [optional body]
|
|
#
|
|
# [optional footer(s)]
|
|
#
|
|
# Types: feat fix docs style refactor test chore perf ci build
|
|
# Add ! before : for breaking changes (e.g., feat!: or feat(api)!:)
|
|
#
|
|
# Examples:
|
|
# feat: add user authentication
|
|
# fix(api): resolve null pointer exception
|
|
# feat(auth)!: change token format
|
|
# docs: update API documentation
|
|
EOF
|
|
|
|
# Prepend type if provided
|
|
if [ -n "$type" ]; then
|
|
sed -i.bak "1s/^/$type: /" "$template" && rm -f "$template.bak"
|
|
fi
|
|
|
|
git commit -e -t "$template" "${git_args[@]}"
|
|
}
|
|
|
|
build_message() {
|
|
local type="$1"
|
|
local scope="$2"
|
|
local breaking="$3"
|
|
local msg="$4"
|
|
local body="$5"
|
|
|
|
local commit_msg="$type"
|
|
|
|
if [ -n "$scope" ]; then
|
|
commit_msg+="($scope)"
|
|
fi
|
|
|
|
if [ "$breaking" = "true" ]; then
|
|
commit_msg+="!"
|
|
fi
|
|
|
|
commit_msg+=": $msg"
|
|
|
|
if [ -n "$body" ]; then
|
|
commit_msg+=$'\n\n'"$body"
|
|
fi
|
|
|
|
echo "$commit_msg"
|
|
}
|
|
|
|
# Interactive mode if no args
|
|
if [ $# -eq 0 ]; then
|
|
echo "Types: $TYPES"
|
|
printf "Type: "
|
|
read -r type
|
|
printf "Scope (optional): "
|
|
read -r scope
|
|
printf "Breaking change? [y/N]: "
|
|
read -r breaking_input
|
|
printf "Message: "
|
|
read -r msg
|
|
printf "Body (optional, enter for none): "
|
|
read -r body
|
|
|
|
breaking="false"
|
|
if [[ $breaking_input =~ ^[Yy] ]]; then
|
|
breaking="true"
|
|
fi
|
|
|
|
commit_msg=$(build_message "$type" "$scope" "$breaking" "$msg" "$body")
|
|
git commit -m "$commit_msg"
|
|
exit 0
|
|
fi
|
|
|
|
# Parse arguments
|
|
type=""
|
|
msg=""
|
|
scope=""
|
|
breaking="false"
|
|
body=""
|
|
use_editor="false"
|
|
git_args=()
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-e | --edit)
|
|
use_editor="true"
|
|
shift
|
|
;;
|
|
--scope)
|
|
scope="$2"
|
|
shift 2
|
|
;;
|
|
-b | --breaking)
|
|
breaking="true"
|
|
shift
|
|
;;
|
|
-B | --body)
|
|
body="$2"
|
|
shift 2
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
;;
|
|
# Pass through git commit flags
|
|
-s | --signoff | -a | --all | -v | --verbose | -n | --no-verify | --amend | --no-edit)
|
|
git_args+=("$1")
|
|
shift
|
|
;;
|
|
-S | --gpg-sign)
|
|
git_args+=("$1")
|
|
shift
|
|
;;
|
|
*)
|
|
if [ -z "$type" ]; then
|
|
type="$1"
|
|
else
|
|
msg="$1"
|
|
fi
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Editor mode
|
|
if [ "$use_editor" = "true" ]; then
|
|
editor_mode "$type" "${git_args[@]}"
|
|
exit 0
|
|
fi
|
|
|
|
# Validate type
|
|
if ! echo "$TYPES" | grep -qw "$type"; then
|
|
echo "Invalid type: $type"
|
|
echo "Valid types: $TYPES"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$msg" ]; then
|
|
echo "Message required"
|
|
usage
|
|
fi
|
|
|
|
commit_msg=$(build_message "$type" "$scope" "$breaking" "$msg" "$body")
|
|
git commit -m "$commit_msg" "${git_args[@]}"
|