diff --git a/.roo/commands/push.md b/.roo/commands/push.md new file mode 100644 index 0000000..6c4e022 --- /dev/null +++ b/.roo/commands/push.md @@ -0,0 +1,7 @@ +--- +description: "Run increment_and_push.sh with a strong commit message" +--- + +Run `./increment_and_push.sh` and supply a good git commit message. For example: + +`./increment_and_push.sh "Fixed the bug with nip05 implementation"` diff --git a/README.md b/README.md index 6549b85..1043835 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,26 @@ pactl list short sources Then set `audio_device=` in `./config.ini` to the source you want. +## Versioning workflow + +This project uses semantic git tags (`vMAJOR.MINOR.PATCH`) plus header macros in [`src/version.h`](src/version.h). + +Use [`increment_and_push.sh`](increment_and_push.sh) to increment version, update [`VOICE_LINUX_VERSION`](src/version.h:4), commit, tag, and push: + +```bash +# patch bump (default) +./increment_and_push.sh "Fix VAD trigger behavior" + +# minor bump +./increment_and_push.sh -m "Add new GUI debug indicators" + +# major bump +./increment_and_push.sh -M "Breaking config change" + +# release mode (also runs bash ./build.sh and creates tarball) +./increment_and_push.sh -r -m "Release minor update" +``` + ## Notes - This implementation targets **X11** (not native Wayland). diff --git a/increment_and_push.sh b/increment_and_push.sh new file mode 100755 index 0000000..10a8612 --- /dev/null +++ b/increment_and_push.sh @@ -0,0 +1,243 @@ +#!/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" +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 +} + +require_clean_worktree() { + if ! git -C "$ROOT_DIR" diff --quiet || ! git -C "$ROOT_DIR" diff --cached --quiet; then + print_error "Working tree is not clean. Commit or stash changes before running this script." + exit 1 + fi +} + +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.1.0" + 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.1.0" + 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 +require_clean_worktree + +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" diff --git a/src/gui_main.c b/src/gui_main.c index 3fe0f99..aa37290 100644 --- a/src/gui_main.c +++ b/src/gui_main.c @@ -2,6 +2,7 @@ #include "config.h" #include "transcribe.h" #include "typer.h" +#include "version.h" #include @@ -424,8 +425,8 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) { gtk_container_add(GTK_CONTAINER(s->window), outer); char info[1024]; - snprintf(info, sizeof(info), "Device: %s\nModel: %s\nLanguage: %s\nGPU: %s", - s->cfg.audio_device, s->cfg.model_path, s->cfg.language, s->use_gpu ? "on" : "off"); + snprintf(info, sizeof(info), "Version: %s\nDevice: %s\nModel: %s\nLanguage: %s\nGPU: %s", + VOICE_LINUX_VERSION, s->cfg.audio_device, s->cfg.model_path, s->cfg.language, s->use_gpu ? "on" : "off"); GtkWidget *info_label = gtk_label_new(info); gtk_label_set_xalign(GTK_LABEL(info_label), 0.0f); gtk_box_pack_start(GTK_BOX(outer), info_label, FALSE, FALSE, 0); diff --git a/src/main.c b/src/main.c index e479eec..29a240a 100644 --- a/src/main.c +++ b/src/main.c @@ -3,6 +3,7 @@ #include "hotkey.h" #include "transcribe.h" #include "typer.h" +#include "version.h" #include #include @@ -112,7 +113,7 @@ int main(int argc, char **argv) { return 1; } - fprintf(stderr, "voice_linux ready\n"); + fprintf(stderr, "voice_linux %s ready\n", VOICE_LINUX_VERSION); fprintf(stderr, "hotkey toggle mode: press %s to START recording, press %s again to STOP and transcribe\n", cfg.hotkey, cfg.hotkey); fprintf(stderr, "device: %s | model: %s | language: %s | gpu: %s\n", cfg.audio_device, cfg.model_path, cfg.language, use_gpu ? "on" : "off"); diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..ebfd40e --- /dev/null +++ b/src/version.h @@ -0,0 +1,9 @@ +#ifndef VOICE_VERSION_H +#define VOICE_VERSION_H + +#define VOICE_LINUX_VERSION "v0.1.0" +#define VOICE_LINUX_VERSION_MAJOR 0 +#define VOICE_LINUX_VERSION_MINOR 1 +#define VOICE_LINUX_VERSION_PATCH 0 + +#endif