Skip to content

Instantly share code, notes, and snippets.

@dazz
Created September 29, 2025 07:26
Show Gist options
  • Save dazz/ce1d95b2c3d9ae6640f62c6d0bde2ee7 to your computer and use it in GitHub Desktop.
Save dazz/ce1d95b2c3d9ae6640f62c6d0bde2ee7 to your computer and use it in GitHub Desktop.
Git release tag

Git Release Tag

Usage

# Interactive (default - asks for pull and confirmation)
./scripts/release.sh
make release

# Fully automated (no prompts at all)
./scripts/release.sh --auto
./scripts/release.sh release --auto
make release-auto

# Skip only confirmation
./scripts/release.sh --no-confirm

# Skip only pull
./scripts/release.sh --no-pull

# Kombinationen
./scripts/release.sh release --no-pull --no-confirm

# Mit custom CalVer
CALVER_PATTERN=%Y.%m ./scripts/release.sh --auto

# Über Makefile
make release-auto CALVER_PATTERN=%Y.%m
# Configuration
RELEASE_BRANCH ?= main
CALVER_PATTERN ?= %Y.%m.%d
.PHONY: release release-auto release-next-tag release-check
# Interactive release (prompts for pull and confirmation)
release:
@RELEASE_BRANCH=$(RELEASE_BRANCH) CALVER_PATTERN=$(CALVER_PATTERN) ./scripts/release.sh release
# Automated release (no prompts, pull + create + push)
release-auto:
@RELEASE_BRANCH=$(RELEASE_BRANCH) CALVER_PATTERN=$(CALVER_PATTERN) ./scripts/release.sh release --auto
# Show next tag
release-next-tag:
@RELEASE_BRANCH=$(RELEASE_BRANCH) CALVER_PATTERN=$(CALVER_PATTERN) ./scripts/release.sh next-tag
# Check for changes
release-check:
@RELEASE_BRANCH=$(RELEASE_BRANCH) CALVER_PATTERN=$(CALVER_PATTERN) ./scripts/release.sh check-changes
#!/bin/bash
set -e
# Configuration
RELEASE_BRANCH="${RELEASE_BRANCH:-main}"
CALVER_PATTERN="${CALVER_PATTERN:-%Y.%m.%d}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Functions
show_help() {
cat << EOF
Usage: $(basename "$0") [COMMAND] [OPTIONS]
Commands:
release Create a new release (default)
next-tag Show what the next tag would be
check-branch Check if current branch is correct
check-changes Check if there are new merges since last tag
help Show this help message
Release Options:
--no-pull Skip git pull
--no-confirm Skip confirmation prompt
--auto Equivalent to --no-confirm (pull, create, push without prompts)
Environment Variables:
RELEASE_BRANCH Branch from which releases are created (default: main)
CALVER_PATTERN Date format for CalVer tags (default: %Y.%m.%d)
Examples:
%Y.%m.%d -> 2025.09.29 (default)
%Y.%m -> 2025.09
%Y.%W -> 2025.39 (week number)
v%Y.%m.%d -> v2025.09.29
Examples:
$(basename "$0") # Interactive release process
$(basename "$0") release --auto # Automated release (no prompts)
$(basename "$0") release --no-confirm # Pull interactively, skip confirmation
$(basename "$0") release --no-pull # Skip pull, confirm before push
$(basename "$0") next-tag # See what tag would be created
CALVER_PATTERN=%Y.%m $(basename "$0") --auto # Automated with custom pattern
EOF
}
check_branch() {
local current_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$current_branch" != "$RELEASE_BRANCH" ]; then
echo -e "${RED}Error: Releases can only be created from the '$RELEASE_BRANCH' branch.${NC}"
echo "Current branch: $current_branch"
return 1
fi
echo -e "${GREEN}✓ On $RELEASE_BRANCH branch${NC}"
return 0
}
get_last_tag() {
git describe --tags --abbrev=0 2>/dev/null || echo ""
}
calculate_next_tag() {
local base_tag=$(date +"$CALVER_PATTERN")
local existing_tags=$(git tag -l "$base_tag*" | sort -V)
if [ -z "$existing_tags" ]; then
echo "$base_tag"
else
local last_today=$(echo "$existing_tags" | tail -n 1)
if [ "$last_today" = "$base_tag" ]; then
echo "$base_tag.1"
else
local suffix=$(echo "$last_today" | sed "s/$(echo "$base_tag" | sed 's/\./\\./g')\.//")
local next_suffix=$((suffix + 1))
echo "$base_tag.$next_suffix"
fi
fi
}
show_next_tag() {
local last_tag=$(get_last_tag)
local next_tag=$(calculate_next_tag)
echo "Release branch: $RELEASE_BRANCH"
echo "CalVer pattern: $CALVER_PATTERN"
echo "Last tag: ${last_tag:-none}"
echo "Next tag: $next_tag"
}
get_changelog() {
local last_tag="$1"
if [ -z "$last_tag" ]; then
echo -e "${YELLOW}Warning: No previous tag found, showing all merge commits${NC}" >&2
git log --merges HEAD \
--pretty=format:"- %s" \
| sed -E 's/^Merge (pull request #[0-9]+ from [^ ]+ |branch .+ )?//' \
| head -n 20
else
git log --merges "$last_tag"..HEAD \
--pretty=format:"- %s" \
| sed -E 's/^Merge (pull request #[0-9]+ from [^ ]+ |branch .+ )?//'
fi
}
check_changes() {
local last_tag=$(get_last_tag)
local msg=$(get_changelog "$last_tag")
echo "Release branch: $RELEASE_BRANCH"
echo "CalVer pattern: $CALVER_PATTERN"
echo "Last tag: ${last_tag:-none}"
echo ""
if [ -z "$msg" ]; then
echo -e "${YELLOW}No new merges since $last_tag${NC}"
return 1
else
echo -e "${GREEN}Found new merges:${NC}"
echo "$msg"
return 0
fi
}
update_branch() {
local skip_pull="$1"
if [ "$skip_pull" = "true" ]; then
echo "Skipping git pull (--no-pull)"
return 0
fi
read -p "Update $RELEASE_BRANCH branch (git pull)? [Y/n] " update_answer
case $update_answer in
[nN]*)
echo "Branch will not be updated."
;;
*)
echo "Updating $RELEASE_BRANCH branch..."
git pull
;;
esac
}
create_release() {
local skip_pull=false
local skip_confirm=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--no-pull)
skip_pull=true
shift
;;
--no-confirm|--auto)
skip_confirm=true
shift
;;
*)
echo -e "${RED}Error: Unknown option '$1'${NC}"
show_help
exit 1
;;
esac
done
# Check branch
check_branch || exit 1
# Update branch
if [ "$skip_pull" = "false" ]; then
if [ "$skip_confirm" = "true" ]; then
echo "Updating $RELEASE_BRANCH branch..."
git pull
else
update_branch "$skip_pull"
fi
else
echo "Skipping git pull"
fi
# Get tags
local last_tag=$(get_last_tag)
local new_tag=$(calculate_next_tag)
echo ""
echo "CalVer pattern: $CALVER_PATTERN"
echo "Last tag: ${last_tag:-none}"
echo "New tag: $new_tag"
# Generate changelog
local msg=$(get_changelog "$last_tag")
if [ -z "$msg" ]; then
echo -e "${RED}No new merges since $last_tag${NC}"
exit 1
fi
# Preview
echo ""
echo "Tag message preview:"
echo "----------------------"
echo "Release $new_tag"
echo ""
echo "$msg"
echo "----------------------"
echo ""
# Confirmation
if [ "$skip_confirm" = "true" ]; then
echo "Creating and pushing tag automatically (--no-confirm)"
git tag -a "$new_tag" -m "Release $new_tag"$'\n\n'"$msg"
git push origin "$new_tag"
echo -e "${GREEN}✓ Release $new_tag successfully created and pushed${NC}"
else
read -p "Really create and push tag? [y/N] " answer
case $answer in
[yY]*)
git tag -a "$new_tag" -m "Release $new_tag"$'\n\n'"$msg"
git push origin "$new_tag"
echo -e "${GREEN}✓ Release $new_tag successfully created and pushed${NC}"
;;
*)
echo "Aborted."
exit 1
;;
esac
fi
}
# Main script logic
COMMAND="${1:-release}"
case $COMMAND in
release)
shift # Remove 'release' from arguments
create_release "$@"
;;
next-tag)
show_next_tag
;;
check-branch)
check_branch
;;
check-changes)
check_changes
;;
help|--help|-h)
show_help
;;
--*)
# If first argument is an option, assume 'release' command
create_release "$@"
;;
*)
echo -e "${RED}Error: Unknown command '$COMMAND'${NC}"
echo ""
show_help
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment