#!/usr/bin/env bash set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' print_status() { echo -e "${BLUE}[INFO]${NC} $1" >&2; } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2; } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" >&2; } print_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } COMMIT_MESSAGE="" RELEASE_MODE=false VERSION_INCREMENT_TYPE="patch" # patch | minor | major ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VERSION_FILE="$ROOT_DIR/src/version.h" show_usage() { cat <<'EOF' voice_linux increment and push script USAGE: ./increment_and_push.sh [OPTIONS] "commit message" OPTIONS: -p, --patch Increment patch version (default) -m, --minor Increment minor version -M, --major Increment major version -r, --release Build and create a source tarball after tagging -h, --help Show this help EXAMPLES: ./increment_and_push.sh "Fix VAD trigger logic" ./increment_and_push.sh -m "Add live debug indicators" ./increment_and_push.sh -M "Breaking config format update" ./increment_and_push.sh -r -m "Release minor update" NOTES: - If the working tree is dirty, the script auto-commits pending changes first as: prep: EOF } check_git_repo() { if ! git -C "$ROOT_DIR" rev-parse --git-dir >/dev/null 2>&1; then print_error "Not in a git repository: $ROOT_DIR" exit 1 fi } has_pending_changes() { if ! git -C "$ROOT_DIR" diff --quiet; then return 0 fi if ! git -C "$ROOT_DIR" diff --cached --quiet; then return 0 fi if [[ -n "$(git -C "$ROOT_DIR" ls-files --others --exclude-standard)" ]]; then return 0 fi return 1 } auto_commit_pending_changes() { local message="$1" if ! has_pending_changes; then return 0 fi print_warning "Working tree is dirty. Auto-committing pending changes before version bump." git -C "$ROOT_DIR" add -A if git -C "$ROOT_DIR" diff --cached --quiet; then print_warning "No staged changes after auto-add; continuing." return 0 fi git -C "$ROOT_DIR" commit -m "prep: ${message}" >/dev/null print_success "Committed pending changes: prep: ${message}" } latest_version_tag() { git -C "$ROOT_DIR" tag -l 'v*.*.*' | sort -V | tail -n 1 } read_header_version() { if [[ ! -f "$VERSION_FILE" ]]; then echo "v0.0.1" return fi local v v=$(grep -E '^#define[[:space:]]+VOICE_LINUX_VERSION[[:space:]]+"v[0-9]+\.[0-9]+\.[0-9]+"' "$VERSION_FILE" | sed -E 's/.*"(v[0-9]+\.[0-9]+\.[0-9]+)".*/\1/' || true) if [[ -z "$v" ]]; then echo "v0.0.1" else echo "$v" fi } parse_semver() { local v="$1" if [[ "$v" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]} ${BASH_REMATCH[3]}" else print_error "Invalid version: $v" exit 1 fi } increment_version() { local current="$1" local kind="$2" read -r major minor patch < <(parse_semver "$current") case "$kind" in major) major=$((major + 1)) minor=0 patch=0 ;; minor) minor=$((minor + 1)) patch=0 ;; patch) patch=$((patch + 1)) ;; *) print_error "Unknown increment type: $kind" exit 1 ;; esac echo "v${major}.${minor}.${patch}" } update_version_header() { local new_version="$1" read -r major minor patch < <(parse_semver "$new_version") if [[ ! -f "$VERSION_FILE" ]]; then print_error "Missing $VERSION_FILE" exit 1 fi sed -i -E "s|^#define[[:space:]]+VOICE_LINUX_VERSION[[:space:]]+\"v[0-9]+\.[0-9]+\.[0-9]+\"|#define VOICE_LINUX_VERSION \"${new_version}\"|" "$VERSION_FILE" sed -i -E "s|^#define[[:space:]]+VOICE_LINUX_VERSION_MAJOR[[:space:]]+[0-9]+|#define VOICE_LINUX_VERSION_MAJOR ${major}|" "$VERSION_FILE" sed -i -E "s|^#define[[:space:]]+VOICE_LINUX_VERSION_MINOR[[:space:]]+[0-9]+|#define VOICE_LINUX_VERSION_MINOR ${minor}|" "$VERSION_FILE" sed -i -E "s|^#define[[:space:]]+VOICE_LINUX_VERSION_PATCH[[:space:]]+[0-9]+|#define VOICE_LINUX_VERSION_PATCH ${patch}|" "$VERSION_FILE" print_success "Updated $VERSION_FILE to $new_version" } create_or_replace_tag() { local tag="$1" if git -C "$ROOT_DIR" rev-parse "$tag" >/dev/null 2>&1; then print_warning "Tag $tag exists, replacing locally" git -C "$ROOT_DIR" tag -d "$tag" >/dev/null fi git -C "$ROOT_DIR" tag "$tag" print_success "Created tag: $tag" } git_commit_push_and_tag() { local version="$1" local message="$2" git -C "$ROOT_DIR" add "$VERSION_FILE" git -C "$ROOT_DIR" commit -m "$version - $message" >/dev/null print_success "Committed: $version - $message" create_or_replace_tag "$version" git -C "$ROOT_DIR" push print_success "Pushed branch" if git -C "$ROOT_DIR" push origin "$version"; then print_success "Pushed tag: $version" else print_warning "Tag push failed, trying force push" git -C "$ROOT_DIR" push --force origin "$version" print_success "Force-pushed tag: $version" fi } build_and_package_release() { local version="$1" print_status "Running release build with bash ./build.sh" (cd "$ROOT_DIR" && bash ./build.sh) local tarball="voice_linux-${version#v}.tar.gz" print_status "Creating source tarball: $tarball" (cd "$ROOT_DIR" && tar -czf "$tarball" \ --exclude='.git' \ --exclude='.git/*' \ --exclude='vendor/whisper.cpp/build/*' \ --exclude='*.o' \ --exclude='voice_linux' \ --exclude='voice_linux_gui' \ --exclude='*.tar.gz' \ .) print_success "Created tarball: $tarball" } while [[ $# -gt 0 ]]; do case "$1" in -p|--patch) VERSION_INCREMENT_TYPE="patch" shift ;; -m|--minor) VERSION_INCREMENT_TYPE="minor" shift ;; -M|--major) VERSION_INCREMENT_TYPE="major" shift ;; -r|--release) RELEASE_MODE=true shift ;; -h|--help) show_usage exit 0 ;; *) if [[ -z "$COMMIT_MESSAGE" ]]; then COMMIT_MESSAGE="$1" else COMMIT_MESSAGE="$COMMIT_MESSAGE $1" fi shift ;; esac done if [[ -z "$COMMIT_MESSAGE" ]]; then print_error "Commit message is required" echo "" show_usage exit 1 fi print_status "voice_linux increment and push" check_git_repo auto_commit_pending_changes "$COMMIT_MESSAGE" LATEST_TAG="$(latest_version_tag || true)" if [[ -z "$LATEST_TAG" ]]; then LATEST_TAG="$(read_header_version)" print_warning "No git version tags found, using header version: $LATEST_TAG" fi NEW_VERSION="$(increment_version "$LATEST_TAG" "$VERSION_INCREMENT_TYPE")" print_status "Current: $LATEST_TAG" print_status "Next: $NEW_VERSION" update_version_header "$NEW_VERSION" git_commit_push_and_tag "$NEW_VERSION" "$COMMIT_MESSAGE" if [[ "$RELEASE_MODE" == true ]]; then build_and_package_release "$NEW_VERSION" fi print_success "Done: $NEW_VERSION"