diff --git a/.agents/instructions/repo.instructions.md b/.agents/instructions/repo.instructions.md index 07b26f00..cb56e2b5 100644 --- a/.agents/instructions/repo.instructions.md +++ b/.agents/instructions/repo.instructions.md @@ -16,7 +16,6 @@ This repository contains 7 devcontainer features installed via `install.sh` or ` | **githooks** | Dev environment setup: commitlint, prettier, lint-staged | | **gitversion** | Semantic versioning via GitVersion | | **act** | Local GitHub Actions via nektos/act | -| **pecl** | PHP Extensions installer | | **larasets** | Laravel-specific development utilities | | **common-utils** | Shared scripts used by other features | | **gateway** | SSL certificate management for local development | diff --git a/.github/workflows/release-main.yml b/.github/workflows/release-main.yml index 9837dd81..d6983f39 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release-main.yml @@ -4,6 +4,11 @@ name: 'Release Main' on: workflow_dispatch: + inputs: + dry_run: + description: 'Run through beta/prod without pushing to main (verification only)' + type: boolean + default: false permissions: contents: write @@ -13,38 +18,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: develop - - name: Set up Node.js - uses: tomgrv/actions/setup-node@v2 - - - name: Install GitUtils - # zz_use/install-feature link git-beta/git-prod/etc. onto a bin dir - # that only lasts for *this* step's own shell unless it's also - # appended to $GITHUB_PATH - a later step (Run git beta then git - # prod) otherwise can't find them even though this step's install - # succeeded. Pinning INSTALL_BIN_DIR here keeps both in sync. - env: - INSTALL_BIN_DIR: ${{ runner.temp }}/gitutils-bin - run: | - mkdir -p "$INSTALL_BIN_DIR" - echo "$INSTALL_BIN_DIR" >> "$GITHUB_PATH" - npm exec --yes --legacy-peer-deps --package github:tomgrv/devcontainer-features -- devcontainer-features -- add gitutils - - - name: Verify GitUtils aliases - run: | - git config --global --get alias.beta >/dev/null || { echo 'Missing git beta alias after GitUtils install; check the "Install GitUtils" step logs.'; false; } - git config --global --get alias.prod >/dev/null || { echo 'Missing git prod alias after GitUtils install; check the "Install GitUtils" step logs.'; false; } - - - name: Configure git identity - run: | - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - - - name: Run git beta then git prod - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: git beta && git prod + - name: Release promote + uses: tomgrv/actions/release-promote@v2.22.0 + with: + scripts-ref: v0.3.0 + dry-run: ${{ inputs.dry_run == true }} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ed5fa547..33e4d4bb 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -29,24 +29,12 @@ "command": "git pf", "problemMatcher": [] }, - { - "label": "๐ŸŽˆ Beta", - "type": "shell", - "command": "git release beta", - "problemMatcher": [] - }, { "label": "๐Ÿš‘ Hotfix", "type": "shell", "command": "git release hotfix", "problemMatcher": [] }, - { - "label": "๐Ÿš€ Prod", - "type": "shell", - "command": "git release prod", - "problemMatcher": [] - }, { "label": "โฌ Upd. DevEnv", "type": "shell", diff --git a/CLAUDE.md b/CLAUDE.md index 9ab0208e..9b0c6ea0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,7 @@ Every tool-specific path is a symlink into `.agents/` (the single source of trut ## Feature Pattern -Each feature follows this structure (use `src/pecl/` as the minimal reference โ€” just the standard entrypoints plus `stubs/`; `src/githooks/` or `src/larasets/` for a fuller example with all four optional subdirectories): +Each feature follows this structure (use `src/scripting/` as the minimal reference โ€” just the standard entrypoints plus `stubs/`; `src/githooks/` or `src/larasets/` for a fuller example with all four optional subdirectories): ``` src// @@ -73,7 +73,7 @@ If no branch is specified in a request, start from `develop`. **PR titles must follow Conventional Commits format with scope matching the affected workspace/feature.** Format: `(devcontainer-features-): `. -Valid scopes (deduced from npm workspace names): `devcontainer-features-act`, `devcontainer-features-ai-coding`, `devcontainer-features-common-utils`, `devcontainer-features-gateway`, `devcontainer-features-githooks`, `devcontainer-features-gitutils`, `devcontainer-features-gitversion`, `devcontainer-features-larasets`, `devcontainer-features-minikube`, `devcontainer-features-pecl`, `devcontainer-features-scripting`. +Valid scopes (deduced from npm workspace names): `devcontainer-features-act`, `devcontainer-features-ai-coding`, `devcontainer-features-common-utils`, `devcontainer-features-gateway`, `devcontainer-features-githooks`, `devcontainer-features-gitutils`, `devcontainer-features-gitversion`, `devcontainer-features-larasets`, `devcontainer-features-minikube`, `devcontainer-features-scripting`. Example: `fix(devcontainer-features-githooks): ๐Ÿ”ง Add conditional skip when GITLEAKS_LICENSE not set`. diff --git a/README.md b/README.md index 25cc46bc..95686b11 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ curl -fsSL https://raw.githubusercontent.com/tomgrv/devcontainer-features/develo | [GitHooks](#githooks) | Commit hooks: commitlint, prettier, lint-staged | | [GitVersion](#gitversion) | Semantic versioning via GitVersion | | [Act](#act) | Run GitHub Actions locally via nektos/act | -| [PECL](#pecl) | PHP extension installer via PECL | | [Larasets](#larasets) | Laravel-specific development utilities | | [Common Utils](#common-utils) | Shared utilities used by other features | | [Gateway](#gateway) | SSL certificate management for corporate networks | @@ -74,7 +73,7 @@ A collection of Git aliases, git-flow shortcuts, and interactive utilities for a ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/gitutils:5": {} + "ghcr.io/tomgrv/devcontainer-features/gitutils:8": {} } ``` @@ -104,7 +103,7 @@ Configures Git hooks in one step using commitlint, commitizen, lint-staged, pret ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/githooks:5": {} + "ghcr.io/tomgrv/devcontainer-features/githooks:8": {} } ``` @@ -134,7 +133,7 @@ Installs [GitVersion](https://gitversion.net/) to calculate semantic version num ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/gitversion:6": {} + "ghcr.io/tomgrv/devcontainer-features/gitversion:8": {} } ``` @@ -164,7 +163,7 @@ Installs [nektos/act](https://github.com/nektos/act) to run GitHub Actions local ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/act:6": {} + "ghcr.io/tomgrv/devcontainer-features/act:8": {} } ``` @@ -184,38 +183,6 @@ npm install --save-dev @tomgrv/devcontainer-features-act --- -### PECL - -Installs PHP extensions from the [PHP Extension Community Library (PECL)](https://pecl.php.net/). - -๐Ÿ“– [Full documentation](./src/pecl/README.md) - -#### Quick Start โ€” devcontainer.json - -```json -"features": { - "ghcr.io/tomgrv/devcontainer-features/pecl:5": { - "extension": "zip" - } -} -``` - -#### Quick Install โ€” console - -```sh -npx tomgrv/devcontainer-features -- add pecl -# or, without node/npm: -curl -fsSL https://raw.githubusercontent.com/tomgrv/devcontainer-features/develop/setup.sh | sh -s -- add pecl -``` - -#### Quick Install โ€” npm - -```sh -npm install --save-dev @tomgrv/devcontainer-features-pecl -``` - ---- - ### Larasets Laravel-specific settings, shell utilities, Composer scripts, and VS Code extensions for Laravel development. @@ -226,7 +193,7 @@ Laravel-specific settings, shell utilities, Composer scripts, and VS Code extens ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/larasets:6": {} + "ghcr.io/tomgrv/devcontainer-features/larasets:8": {} } ``` @@ -256,7 +223,7 @@ Shared utilities (`jq`, `dos2unix`, JSON helpers, logging) used by other feature ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/common-utils:5": { + "ghcr.io/tomgrv/devcontainer-features/common-utils:8": { "utils": "jq dos2unix" } } @@ -288,7 +255,7 @@ Handles SSL inspection certificates for corporate network environments (e.g. Zsc ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/gateway:7": {} + "ghcr.io/tomgrv/devcontainer-features/gateway:8": {} } ``` @@ -318,7 +285,7 @@ Installs [Minikube](https://minikube.sigs.k8s.io/) to run a single-node Kubernet ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/minikube:6": {} + "ghcr.io/tomgrv/devcontainer-features/minikube:8": {} } ``` @@ -348,7 +315,7 @@ Agent-agnostic AI coding skills (`.github/skills/`) plus the [Claude Code GitHub ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/ai-coding:7": {} + "ghcr.io/tomgrv/devcontainer-features/ai-coding:8": {} } ``` diff --git a/docs/reviews/2026-08-repo-review.md b/docs/reviews/2026-08-repo-review.md index b8326bd2..2b8cc890 100644 --- a/docs/reviews/2026-08-repo-review.md +++ b/docs/reviews/2026-08-repo-review.md @@ -7,6 +7,12 @@ Scope: whole repository โ€” CI/workflows, `src/*` feature source, and the `.agen This is a findings-only report. No code changes are included in this pass; each item below is written so a follow-up PR can act on it independently. +> **2026-09 update:** `pecl` (the subject of ยง2.5 and several other findings below) has been +> removed from this repository rather than brought into conformance โ€” the findings that +> reference it are kept as-is for the historical record, but no longer describe anything +> present in `src/`. `CLAUDE.md`'s "minimal reference" feature pointer was moved to +> `src/scripting/`. + ## Executive Summary Six items stand out by impact-to-effort: diff --git a/src/act/README.md b/src/act/README.md index cf7857da..74162683 100644 --- a/src/act/README.md +++ b/src/act/README.md @@ -10,7 +10,7 @@ More information about Act can be found on the [Act GitHub page](https://github. ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/act:6": {} + "ghcr.io/tomgrv/devcontainer-features/act:8": {} } ``` diff --git a/src/act/devcontainer-feature.json b/src/act/devcontainer-feature.json index 84a586f0..08c5098b 100644 --- a/src/act/devcontainer-feature.json +++ b/src/act/devcontainer-feature.json @@ -9,7 +9,7 @@ "ghcr.io/devcontainers/features/docker-in-docker:3": { "moby": false }, - "ghcr.io/tomgrv/devcontainer-features/common-utils:7": {} + "ghcr.io/tomgrv/devcontainer-features/common-utils:8": {} }, "name": "Nektos Act", "options": { diff --git a/src/ai-coding/README.md b/src/ai-coding/README.md index 8cfe826f..546a98d5 100644 --- a/src/ai-coding/README.md +++ b/src/ai-coding/README.md @@ -10,7 +10,7 @@ Other features can opt into skills too: `dependsOn` this feature, ship a `stubs/ ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/ai-coding:7": {} + "ghcr.io/tomgrv/devcontainer-features/ai-coding:8": {} } ``` diff --git a/src/ai-coding/devcontainer-feature.json b/src/ai-coding/devcontainer-feature.json index 4fd45222..8de99363 100644 --- a/src/ai-coding/devcontainer-feature.json +++ b/src/ai-coding/devcontainer-feature.json @@ -1,19 +1,15 @@ { "customizations": { "vscode": { - "extensions": [ - "Anthropic.claude-code" - ], + "extensions": ["Anthropic.claude-code"], "settings": {} } }, "description": "Agent-agnostic AI coding skills (.github/skills) plus the Claude Code GitHub Action for @claude mentions.", "id": "ai-coding", - "installsAfter": [ - "ghcr.io/tomgrv/devcontainer-features/common-utils" - ], + "installsAfter": ["ghcr.io/tomgrv/devcontainer-features/common-utils"], "dependsOn": { - "ghcr.io/tomgrv/devcontainer-features/common-utils:7": {} + "ghcr.io/tomgrv/devcontainer-features/common-utils:8": {} }, "name": "AI Coding", "version": "8.1.0", diff --git a/src/common-utils/README.md b/src/common-utils/README.md index 4c123639..4641daa3 100644 --- a/src/common-utils/README.md +++ b/src/common-utils/README.md @@ -8,7 +8,7 @@ This feature provides common utilities for the devcontainer features. ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/common-utils:5": { + "ghcr.io/tomgrv/devcontainer-features/common-utils:8": { "utils": "jq dos2unix" } } diff --git a/src/gateway/README.md b/src/gateway/README.md index 18ca6307..7f3f0177 100644 --- a/src/gateway/README.md +++ b/src/gateway/README.md @@ -19,7 +19,7 @@ SSL inspection tools act as a man-in-the-middle TLS proxy and replace server cer ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/gateway:7": {} + "ghcr.io/tomgrv/devcontainer-features/gateway:8": {} } ``` diff --git a/src/gateway/devcontainer-feature.json b/src/gateway/devcontainer-feature.json index fefd03f3..567c2736 100644 --- a/src/gateway/devcontainer-feature.json +++ b/src/gateway/devcontainer-feature.json @@ -9,11 +9,9 @@ }, "description": "Handling SSL Inspection Gateway while building & using devcontainers", "id": "gateway", - "installsAfter": [ - "ghcr.io/tomgrv/devcontainer-features/common-utils" - ], + "installsAfter": ["ghcr.io/tomgrv/devcontainer-features/common-utils"], "dependsOn": { - "ghcr.io/tomgrv/devcontainer-features/common-utils:7": { + "ghcr.io/tomgrv/devcontainer-features/common-utils:8": { "utils": "bash ca-certificates curl" } }, diff --git a/src/githooks/README.md b/src/githooks/README.md index 5eaca8a2..fb5c930c 100644 --- a/src/githooks/README.md +++ b/src/githooks/README.md @@ -8,7 +8,7 @@ This feature provides a set of hooks for working with Git repositories. ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/githooks:5": {} + "ghcr.io/tomgrv/devcontainer-features/githooks:8": {} } ``` diff --git a/src/githooks/devcontainer-feature.json b/src/githooks/devcontainer-feature.json index de41bf3c..0bcdde84 100644 --- a/src/githooks/devcontainer-feature.json +++ b/src/githooks/devcontainer-feature.json @@ -33,7 +33,7 @@ "ghcr.io/devcontainers/features/node:1": { "version": "lts" }, - "ghcr.io/tomgrv/devcontainer-features/gitutils:7": {} + "ghcr.io/tomgrv/devcontainer-features/gitutils:8": {} }, "name": "Git Hooks", "version": "8.0.0", diff --git a/src/githooks/stubs/.github/instructions/commit.instructions.md b/src/githooks/stubs/.github/instructions/commit.instructions.md index e8711dfc..431e9b73 100644 --- a/src/githooks/stubs/.github/instructions/commit.instructions.md +++ b/src/githooks/stubs/.github/instructions/commit.instructions.md @@ -59,5 +59,5 @@ perf(common-utils): reduce script startup time refactor(gitversion): extract bump logic into shared function docs(gateway): document SSL setup chore(githooks): bump @commitlint/cli to latest -feat!(pecl): remove legacy extension installer โ€” breaking change +feat!(gateway): remove legacy certificate installer โ€” breaking change ``` diff --git a/src/gitutils/README.md b/src/gitutils/README.md index b30c07be..c29b3a02 100644 --- a/src/gitutils/README.md +++ b/src/gitutils/README.md @@ -8,7 +8,7 @@ This feature provides a set of utilities for working with Git repositories. ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/gitutils:7": {} + "ghcr.io/tomgrv/devcontainer-features/gitutils:8": {} } ``` @@ -32,6 +32,12 @@ Installs a collection of Git utilities. Installs the git-flow extension and sets up Git configuration to use it. Adds shortcuts to the git command for easier use of git-flow commands. +The utilities themselves live in [`tomgrv/scripts`](https://github.com/tomgrv/scripts#git-utilities) +(one source of truth, shared with every other `tomgrv`/`perspikapps` repo) +and are pulled in via `zz_use` at install time โ€” this feature owns the +alias/config wiring and the git-flow install/configure lifecycle, not the +script implementations. + ## GitFlow Additionally, the feature installs the [git-flow](https://github.com/nvie/gitflow) extension and sets up the Git configuration to use it. diff --git a/src/gitutils/bin/git-align.sh b/src/gitutils/bin/git-align.sh deleted file mode 100755 index e4a4ce19..00000000 --- a/src/gitutils/bin/git-align.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/sh - -# get the branch name whatever the form is -branch=$(git rev-parse --abbrev-ref HEAD) - -# get the remote branch name -origin=$(git config --get branch.$branch.remote) - -# Stash all changes, including untracked files, with a message -# Record the stash ref before/after so we only pop when something was stashed -# (a clean tree stashes nothing, and an unconditional pop would consume an -# unrelated, older stash entry). -before=$(git rev-parse -q --verify refs/stash 2>/dev/null) -git stash push -u -m "Stashing changes before aligning branch" -after=$(git rev-parse -q --verify refs/stash 2>/dev/null) - -# Fetch the latest changes from the remote and align the branch -git fetch $origin -git branch -m $branch $branch-to-delete -if git checkout -b $branch $origin/$branch; then - git branch -D $branch-to-delete -else - zz_log e "Failed to checkout branch $branch from $origin/$branch" - git branch -m $branch-to-delete $branch -fi -# Apply the stashed changes only if a new stash entry was created -[ "$before" != "$after" ] && git stash pop - -# Print the current branch name -zz_log i "Current branch: $(git rev-parse --abbrev-ref HEAD)" diff --git a/src/gitutils/bin/git-autorebase.sh b/src/gitutils/bin/git-autorebase.sh deleted file mode 100755 index 6faa9211..00000000 --- a/src/gitutils/bin/git-autorebase.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/sh - -# Handle parameters -eval $( - zz_args "Automatically handles non-interactive rebasing with conflict resolution" $0 "$@" <<-help - f - force allow overwriting pushed history - p - push push to remote after rebase - a - autosquash Apply autosquash (default: off) - n - no-lock don't automatically manage lock files - s strategy strategy strategy for conflicts (default: theirs) - o onto onto rebase onto (default: origin/main) - b branch branch branch to rebase (default: current branch) - - sha sha commit/branch to rebase onto (default: origin/main) -help -) - -# Set defaults -strategy="${strategy:-theirs}" -branch="${branch:-$(git branch --show-current)}" -sha=$(git getcommit $force $sha) -lockfile_patterns="${no_lock:+}${no_lock:-package-lock.json yarn.lock composer.lock pnpm-lock.yaml go.sum}" - -# Navigate to repository root and validate state -cd "$(git rev-parse --show-toplevel)" >/dev/null -git sync >/dev/null 2>&1 || git fetch --prune origin >/dev/null 2>&1 - -if git isRebase; then - zz_log e 'Rebase is in progress, please finish or abort it first.' - exit 1 -fi - -# Manage lockfiles during conflicts -manage_lockfiles() { - - for pattern in $lockfile_patterns; do - find . -name "$pattern" -type f | while read lockfile; do - if git status --porcelain | grep -q "^UU.*$lockfile"; then - zz_log i "Resolving lock file conflict: $lockfile" - git checkout --theirs "$lockfile" && git add "$lockfile" - fi - done - done -} - -# Handle all conflicts automatically -handle_conflicts() { - git conflict | while read file; do - zz_log i "Resolving conflict in: $file" - case "$strategy" in - "theirs") git checkout --theirs "$file" ;; - "ours") git checkout --ours "$file" ;; - esac - git add "$file" - done - manage_lockfiles -} - - -current_branch=$(git branch --show-current) -zz_log i "Rebasing '$current_branch' onto '$sha' with '$strategy' strategy" - -# Perform rebase with automatic conflict resolution -if ! git rebase --strategy-option="$strategy" ${auto:+--autosquash} --autostash --reschedule-failed-exec --exec 'git hook run --ignore-missing pre-commit -- HEAD HEAD~1 && git commit --amend --no-edit --no-verify' --no-verify ${onto:+--onto "$onto"} "$sha" "$branch"; then - zz_log w "Resolving conflicts automatically..." - - attempts=0 - while git status | grep -q "rebase in progress" && [ $attempts -lt 50 ]; do - attempts=$((attempts + 1)) - - if git conflict | grep -q .; then - zz_log i "Handling conflicts (attempt $attempts)" - handle_conflicts - fi - - git rebase --continue || break - sleep 0.1 - done - - # Check if rebase completed successfully - if git status | grep -q "rebase in progress"; then - zz_log e "Rebase failed after $attempts attempts" - git abort - exit 1 - fi -else - zz_log s "Rebase completed without conflicts" -fi - -# Push changes if requested -if [ -n "$push" ] && git rev-parse --verify --quiet origin/HEAD >/dev/null; then - zz_log i "Pushing changes..." - if [ -n "$force" ]; then - git pf origin HEAD - else - git push origin HEAD - fi -fi - -zz_log s "Rebase completed: '$current_branch' โ†’ '$sha'" diff --git a/src/gitutils/bin/git-co.sh b/src/gitutils/bin/git-co.sh deleted file mode 100755 index cb5c89e7..00000000 --- a/src/gitutils/bin/git-co.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments - -eval $( - zz_args "git enhanced commit" $0 "$@" <<-help - n - noscope Enforce no scope in commit message - s scope scope Scope to use in commit message - b branch branch Branch to commit to - + msg msg Commit message - help -) - -if [ -z "$msg" ]; then - zz_log e "Commit message is required" - exit 1 -fi - -# Check if a scope is provided as (...) in the commit message -if [ -z "$noscope" ] && [ -z "$scope" ]; then - - zz_log i "No scope provided, using current branch name as scope" - - # Get feature branch configuration - prefix=$(git config gitflow.prefix.feature) - - # Get scope from the arguments or current branch - if [ -n "$prefix" ]; then - scope=$(git rev-parse --abbrev-ref HEAD | grep "^$prefix" | sed "s|$prefix||") - fi -fi - -if echo $msg | grep -q '^[^(]*([^)]*)'; then - zz_log w "Scope already set in commit message" -elif [ -n "$scope" ]; then - zz_log i "Injecting scope {B $scope} into commit message" - msg="$(echo "$msg" | sed 's/:.*$//')($scope): $(echo "$msg" | sed 's/^[^:]*://')" -elif [ -n "$noscope" ]; then - zz_log i "Removing scope from commit message" - msg="$(echo "$msg" | sed 's/^[^(]*([^)]*)//')" -fi - -git commit -m "$msg" - -if [ -n "$branch" ]; then - zz_log i "Rebasing branch $branch on top of current branch" - git stash && git rebase $branch && git stash pop -fi diff --git a/src/gitutils/bin/git-degit.sh b/src/gitutils/bin/git-degit.sh deleted file mode 100755 index 0401e978..00000000 --- a/src/gitutils/bin/git-degit.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -# Check if repository is provided -if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" - exit 1 -fi - -# Function to print help and manage arguments -eval $( - zz_args "Clone and degit a repository" $0 "$@" <<-help - - repo repo repository to clone - - directory directory directory to degit into - help -) - -# Get the repository host -host=$(echo "${repo}" | sed -E 's/https?:\/\/([^/]+)\/.*/\1/') - -# Keep only the repository name. Eventually remove .git suffix -repo=$(echo "${repo}" | sed -E -e 's/.*github.com\/([^/]+)\/([^/]+).*/\1\/\2/' -e 's/\.git$//') - -# Check if the directory is provided -if [ -z "${directory}" ]; then - directory=. -fi - -# Trace -zz_log i "Repository: ${repo}" -zz_log i "Directory: ${directory}" - -# Create directory if it doesn't exist -mkdir -p "${directory}" - -# Download and extract repository per host -case $host in -"gitlab.com") - curl --location "https://gitlab.com/${repo}/-/archive/master/${repo}-master.tar.gz" | - tar --extract --ungzip --strip-components=1 --directory "${directory}" - ;; -"bitbucket.org") - curl --location "https://bitbucket.org/${repo}/get/master.tar.gz" | - tar --extract --ungzip --strip-components=1 --directory "${directory}" - ;; -"github.com") - curl --location "https://api.github.com/repos/${repo}/tarball" | - tar --extract --ungzip --strip-components=1 --directory "${directory}" - ;; -*) - zz_log e "Unsupported host: {U ${host}}" - exit 1 - ;; -esac diff --git a/src/gitutils/bin/git-fix-author.sh b/src/gitutils/bin/git-fix-author.sh deleted file mode 100755 index ab29f2a2..00000000 --- a/src/gitutils/bin/git-fix-author.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Set user.name and user.email to specified commit's author" $0 "$@" <<-help - - sha sha SHA of the commit to copy author from -help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Remove global user configuration -git config --global --remove-section user 2>/dev/null || true - -# Get the commit SHA and set user config based on its author -git getcommit -f $sha | xargs -I {} sh -c 'git config user.name "$(git log -1 --pretty=format:"%an" {})"; git config user.email "$(git log -1 --pretty=format:"%ae" {})"' diff --git a/src/gitutils/bin/git-fix-base.sh b/src/gitutils/bin/git-fix-base.sh deleted file mode 100755 index d4cee8ad..00000000 --- a/src/gitutils/bin/git-fix-base.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Fix git base - rebase commits from one branch to another" $0 "$@" <<-help - p - push push changes to remote - n - dry-run show what would be done without making changes - - target target target branch to rebase commits onto - - source source source branch to take commits from (default: current branch) -help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Validate required arguments -if [ -z "$target" ]; then - zz_log e 'Target branch is required. Use -h for help.' - exit 1 -elif ! git rev-parse --verify "$target" >/dev/null 2>&1; then - zz_log e "Target branch '$target' does not exist" - exit 1 -fi - -# Get current branch if source is not specified and validate source branch exists -if [ -z "$source" ]; then - source=$(git rev-parse --abbrev-ref HEAD) - zz_log i "Using current branch as source: $source" -elif ! git rev-parse --verify "$source" >/dev/null 2>&1; then - zz_log e "Source branch '$source' does not exist" - exit 1 -fi - -# Validate that source and target are different -if [ "$source" = "$target" ]; then - zz_log e "Source and target branches cannot be the same: $source" - exit 1 -fi - -# Check if there are uncommitted changes -if git isDirty; then - zz_log e 'Working directory is not clean. Please commit or stash your changes.' - exit 1 -fi - -# Find the merge base between source and target -base=$(git merge-base "$source" "$target") -if [ -z "$base" ]; then - zz_log e "Could not find common ancestor between '$source' and '$target'" - exit 1 -else - zz_log i "Found merge base: $base" -fi - -# Get commits that are not pushed on the source branch -commits=$(git log --reverse --format=%H "$source" --not origin/"$source" --not "$target" --no-merges) - -if [ -z "$commits" ]; then - zz_log i "No commits to move from '$source' to '$target'" - exit 0 -fi - -# Count commits -count=$(echo "$commits" | wc -l) -zz_log i "Found $count commit(s) to move from '$source' to '$target'" - -# Show commits in dry-run mode -if [ -n "$dryrun" ]; then - zz_log i "Commits that would be moved:" - echo "$commits" | while read commit; do - if [ -n "$commit" ]; then - echo " $(git log --oneline -1 "$commit")" - fi - done - zz_log i "To execute, run without -n flag" - exit 0 -fi - -# Confirm the operation -zz_log i "About to move $count commit(s) from '$source' to '$target':" -echo "$commits" | while read commit; do - if [ -n "$commit" ]; then - zz_log - " $(git log --oneline -1 "$commit")" >&2 - fi -done -zz_log i "This will:" -zz_log - " 1. Checkout '$target' branch" -zz_log - " 2. Create a temporary branch for the rebase" -zz_log - " 3. Cherry-pick commits from '$source'" -zz_log - " 4. Reset '$source' to the merge base" -zz_log - " 5. Fast-forward '$target' to include the rebased commits" -echo "" -if ! zz_ask "Yn" "Continue?"; then - zz_log e "Operation cancelled" - exit 1 -fi - -# Store current branch -current=$(git rev-parse --abbrev-ref HEAD) - -# Create a temporary branch name -temp="temp-fix-base-$(date +%s)" - -# Checkout target branch -zz_log i "Checking out '$target' branch" -if ! git checkout "$target"; then - zz_log e "Failed to checkout '$target' branch" - exit 1 -fi - -# Create temporary branch from target -zz_log i "Creating temporary branch '$temp'" -if ! git checkout -b "$temp"; then - zz_log e "Failed to create temporary branch" - git checkout "$current" 2>/dev/null - exit 1 -fi - -# Cherry-pick commits from source -zz_log i "Cherry-picking commits..." -# NB: iterate in the current shell (not a `... | while` subshell) so a failure -# propagates and we bail out before the destructive reset of the source branch. -failed=0 -for commit in $commits; do - if ! git cherry-pick "$commit" --strategy=recursive -X theirs --allow-empty; then - zz_log e "Cherry-pick failed on commit $commit" - failed=1 - break - fi -done - -# Check if cherry-pick succeeded -if [ $failed -eq 1 ]; then - zz_log e "Please resolve conflicts and run 'git cherry-pick --continue'" - zz_log e "Or run 'git cherry-pick --abort' to cancel" - exit 1 -fi - -# Fast-forward target branch -zz_log i "Fast-forwarding '$target' branch" -if ! git checkout "$target"; then - zz_log e "Failed to checkout '$target' branch" - exit 1 -fi - -if ! git merge --ff-only "$temp"; then - zz_log e "Failed to fast-forward '$target' branch" - exit 1 -elif [ -n "$push" ] && ! git push origin "$target"; then - zz_log e "Failed to push '$target' branch to remote" - exit 1 -else - zz_log i "Cleaning up temporary branch" - git branch -D "$temp" -fi - -# Reset source branch to source base -zz_log i "Resetting '$source' to source base" -if ! git checkout "$source"; then - zz_log e "Failed to checkout '$source' branch" - exit 1 -elif ! git reset --hard "$base"; then - zz_log e "Failed to reset '$source' branch" - exit 1 -fi - -# Go back to original branch -zz_log i "Switching back to original branch '$current'" -if ! git checkout "$current"; then - zz_log e "Failed to checkout original branch '$current'" - exit 1 -fi - -# Log success message -zz_log i "Successfully moved commits from '$source' to '$target'" diff --git a/src/gitutils/bin/git-fix-blanks.sh b/src/gitutils/bin/git-fix-blanks.sh deleted file mode 100755 index b2fe1cc2..00000000 --- a/src/gitutils/bin/git-fix-blanks.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/sh - -# Handle parameters -eval $( - zz_args "Discard changes made only of whitespace, blanks, quote/slash swaps" $0 "$@" <<-help - d - dryrun show files that would be discarded without changing them - help -) - -# Navigate to repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null || exit 1 - -# Temp workspace for normalized comparisons -temp_dir=$(mktemp -d) -changed_list="$temp_dir/changed-files.list" -trap 'rm -rf "$temp_dir"' EXIT - -# Compare all tracked modified files from HEAD to working tree/index -git diff --name-only --diff-filter=M HEAD -- >"$changed_list" - -if [ ! -s "$changed_list" ]; then - zz_log i "No modified tracked files found." - exit 0 -fi - -normalize_file() { - # Strip full-line comments first so line-oriented filters still work, then - # collapse everything the tool is meant to ignore: all whitespace, quote - # style ("/'), slash direction (\ vs /), and blank lines. The trailing awk - # guarantees every emitted line ends in a newline, so a change that only - # adds or removes the final newline normalizes away too. - case "$1" in - *.sh) sed -e '/^[[:space:]]*#/d' "$1" ;; # Remove comment lines in shell scripts - *.yml|*.yaml) sed -e '/^[[:space:]]*#/d' "$1" ;; # Remove comment lines in YAML files - *.md|*.markdown) sed -e '/^[[:space:]]*/d' "$1" ;; # Remove HTML comments in Markdown files - *.php) sed -e '/^[[:space:]]*\/\//d' -e '/^[[:space:]]*\/\*/d' -e '/^[[:space:]]*\*/d' "$1" ;; # Remove comment lines in PHP files - *.html|*.htm) sed -e '/^[[:space:]]*/d' "$1" ;; # Remove HTML comments in HTML files - *.css) sed -e '/^[[:space:]]*\/\*/d' -e '/^[[:space:]]*\*/d' "$1" ;; # Remove comment lines in CSS files - *.js) sed -e '/^[[:space:]]*\/\//d' -e '/^[[:space:]]*\/\*/d' -e '/^[[:space:]]*\*/d' "$1" ;; # Remove comment lines in JavaScript files - *.json) normalize-json -c -a -i -t 4 -f local -l true "$1" 2>/dev/null || cat "$1" ;; # Normalize JSON to stdout; fall back to raw content if normalization fails (never emit empty, which would be a false match) - *) cat "$1" ;; - esac | sed -e 's/[[:space:]]//g' -e "s/[\"']/\"/g" -e 's#[\\/]#/#g' -e '/^$/d' | awk '{ print }' # strip whitespace, unify quotes/slashes, drop blank lines, force trailing newline -} - -discarded=0 -kept=0 -skipped=0 - -while IFS= read -r file; do - # Ensure the file still exists in the working tree. - if [ ! -f "$file" ]; then - skipped=$((skipped + 1)) - continue - fi - - extension="${file##*.}" - old_file="$temp_dir/old-$discarded-$kept-$skipped.$extension" - new_file="$temp_dir/new-$discarded-$kept-$skipped.$extension" - norm_old="$temp_dir/norm-old-$discarded-$kept-$skipped.$extension" - norm_new="$temp_dir/norm-new-$discarded-$kept-$skipped.$extension" - - if ! git show "HEAD:$file" >"$old_file" 2>/dev/null; then - skipped=$((skipped + 1)) - continue - fi - - cp "$file" "$new_file" - - # Ignore non-text files. - if ! grep -Iq . "$old_file" || ! grep -Iq . "$new_file"; then - skipped=$((skipped + 1)) - continue - fi - - normalize_file "$old_file" >"$norm_old" - normalize_file "$new_file" >"$norm_new" - - if cmp -s "$norm_old" "$norm_new"; then - discarded=$((discarded + 1)) - zz_log i "Discarding ignorable-only changes in $file" - if [ -z "$dryrun" ]; then - git checkout HEAD -- "$file" - fi - else - kept=$((kept + 1)) - fi -done <"$changed_list" - -if [ -n "$dryrun" ]; then - zz_log s "Dry run complete. Discardable files: $discarded, kept: $kept, skipped: $skipped" -else - zz_log s "Done. Discarded: $discarded, kept: $kept, skipped: $skipped" -fi diff --git a/src/gitutils/bin/git-fix-children.sh b/src/gitutils/bin/git-fix-children.sh deleted file mode 100755 index e466d08b..00000000 --- a/src/gitutils/bin/git-fix-children.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -set -e - -# Function to print help and manage arguments -eval $( - zz_args "Delete all descendant tags and branches of a commit" $0 "$@" <<-help - p - push push to remote - f - force allow overwriting pushed history - - sha sha sha commit to start from - help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Retrieve the commit SHA to fixup -sha=$(git getcommit $force $sha) - -zz_log i "Deleting all tags that are descendants of $sha" - -tags=$(git tag --contains "$sha") -if [ -n "$tags" ]; then - echo "$tags" | xargs -r git tag -d -else - zz_log w "No descendant tags found" -fi - -zz_log i "Deleting all local branches that are descendants of $sha (except current, main, master)" - -current=$(git rev-parse --abbrev-ref HEAD) -branches=$(git branch --contains "$sha" | sed "s/^[* ]*//" | grep -vE "^($current|main|master)$") -if [ -n "$branches" ]; then - echo "$branches" | xargs -r git branch -D -else - zz_log w "No descendant branches found" -fi - -if [ -n "$push" ]; then - - zz_log i "Pushing deletions to remote" - for tag in $tags; do - git push origin ":refs/tags/$tag" || zz_log w "Failed to delete remote tag $tag" - done - for branch in $branches; do - git push origin --delete "$branch" || zz_log w "Failed to delete remote branch $branch" - done -else - zz_log w "Remote deletions not pushed. Use -p to push deletions to remote." -fi diff --git a/src/gitutils/bin/git-fix-date.sh b/src/gitutils/bin/git-fix-date.sh deleted file mode 100755 index 66149a3a..00000000 --- a/src/gitutils/bin/git-fix-date.sh +++ /dev/null @@ -1,213 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Fix commit dates and times in git history" $0 "$@" <<-help - f - force allow overwriting pushed history - p - push push changes after rewriting history - d - dryrun dry-run mode: output change plan without applying changes - r days days days of week to reschedule (default: 1,2,3,4,5 for Mon-Fri; 0=Sunday, 6=Saturday) - s start start start time for rescheduling (default: 08:00, HH:MM format) - e end end end time for rescheduling (default: 17:00, HH:MM format) - b before before time to move first half commits to (default: 06:00, HH:MM format) - a after after time to move second half commits to (default: 20:00, HH:MM format) - - sha sha sha commit to fix from - help -) - -# Set default values -days="${days:-1,2,3,4,5}" -start="${start:-08:00}" -end="${end:-17:00}" -before="${before:-06:00}" -after="${after:-20:00}" - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Fetch updates from the remote repository -git fetch --progress --prune --recurse-submodules=no origin >/dev/null - -# Make sure we don't have uncommitted changes -if ! git diff-index --quiet HEAD --; then - zz_log e "You have uncommitted changes. Please commit or stash them before running this script." - exit 1 -fi - -# Retrieve the commit SHA to fix from -sha=$(git getcommit $force $sha) - -# Validate time formats - -# Prevent running while a rebase is in progress -if git isRebase >/dev/null 2>&1; then - zz_log e "A rebase is in progress. Please finish or abort it before running this script." - exit 1 -fi -if ! echo "$start" | grep -qE '^[0-9]{2}:[0-9]{2}$'; then - zz_log e "Invalid start time format. Use HH:MM (e.g., 08:00)" - exit 1 -fi -if ! echo "$end" | grep -qE '^[0-9]{2}:[0-9]{2}$'; then - zz_log e "Invalid end time format. Use HH:MM (e.g., 17:00)" - exit 1 -fi -if ! echo "$before" | grep -qE '^[0-9]{2}:[0-9]{2}$'; then - zz_log e "Invalid before time format. Use HH:MM (e.g., 06:00)" - exit 1 -fi -if ! echo "$after" | grep -qE '^[0-9]{2}:[0-9]{2}$'; then - zz_log e "Invalid after time format. Use HH:MM (e.g., 20:00)" - exit 1 -fi - -# Convert days to array for bash processing -days_array=$(echo "$days" | tr ',' ' ') - -zz_log s "Rescheduling commits on days: $days (0=Sunday, 6=Saturday)" -zz_log s "Time range: $start - $end" -zz_log s "First half ($start - midpoint) will move to before $before" -zz_log s "Second half (midpoint - $end) will move to after $after" - -#### Rewrite history to fix dates -if [ -n "$dryrun" ]; then - zz_log i "DRY RUN MODE: No changes will be applied" -else - zz_log i "Rewriting commit dates from commit $sha" -fi - -# Rescheduling mode - need to process commits in two passes -# First pass: collect commits that need rescheduling -# Second pass: redistribute them while maintaining sequential order - -zz_log i "Analyzing commits for rescheduling..." - -# Get list of commits to process -if [ -n "$sha" ]; then - commit_range="${sha}..HEAD" -else - commit_range="--all" -fi - -# Create a temporary mapping file for new times -temp_map=$(mktemp) -trap "rm -f $temp_map" EXIT - -# Collect commits that need rescheduling -git log --format="%H|%ai|%ci|%s" --reverse $commit_range | while IFS='|' read commit_sha author_date committer_date subject; do - # Extract date and time components - a_date=$(echo "$author_date" | cut -d' ' -f1) - a_time=$(echo "$author_date" | cut -d' ' -f2) - a_tz=$(echo "$author_date" | cut -d' ' -f3-) - - c_date=$(echo "$committer_date" | cut -d' ' -f1) - c_time=$(echo "$committer_date" | cut -d' ' -f2) - c_tz=$(echo "$committer_date" | cut -d' ' -f3-) - - # Get day of week for author date - dow=$(date -d "$a_date" +%w 2>/dev/null || echo "") - - # Check if this day should be rescheduled - should_reschedule=0 - for day in $(echo "$days" | tr ',' ' '); do - if [ "$dow" = "$day" ]; then - should_reschedule=1 - break - fi - done - - if [ "$should_reschedule" = "0" ]; then - # No rescheduling needed - echo "$commit_sha|$author_date|$committer_date" >> "$temp_map" - continue - fi - - # Convert time to seconds - a_seconds=$(echo "$a_time" | awk -F: '{print ($1 * 3600) + ($2 * 60) + $3}') - start_seconds=$(echo "$start" | awk -F: '{print ($1 * 3600) + ($2 * 60)}') - end_seconds=$(echo "$end" | awk -F: '{print ($1 * 3600) + ($2 * 60)}') - - # Check if time is in the range to reschedule - if [ "$a_seconds" -ge "$start_seconds" ] && [ "$a_seconds" -le "$end_seconds" ]; then - # Calculate midpoint - mid_seconds=$(( (start_seconds + end_seconds) / 2 )) - - # Determine new time based on which half of the range - if [ "$a_seconds" -lt "$mid_seconds" ]; then - # First half - move to before time - new_time="$before:00" - else - # Second half - move to after time - new_time="$after:00" - fi - - new_author_date="$a_date $new_time $a_tz" - new_committer_date="$c_date $new_time $c_tz" - - # Always output change plan - zz_log - "$(echo "$commit_sha" | cut -c1-7) | $a_date $a_time โ†’ $a_date $new_time | $subject" - - echo "$commit_sha|$new_author_date|$new_committer_date" >> "$temp_map" - else - # Not in range, keep as-is - echo "$commit_sha|$author_date|$committer_date" >> "$temp_map" - fi - done - - -# Exit if dry-run mode -if [ -n "$dryrun" ]; then - zz_log i "Dry run complete. No changes were made." - exit 0 -fi - - -# Ask for confirmation before proceeding (use helper prompt) -zz_log w "This will rewrite git history. Make sure you understand the consequences." -if ! zz_ask "Yn" "Do you want to proceed?"; then - zz_log i "Operation cancelled by user." - exit 1 -fi - -# Export the mapping file path for the filter -export DATE_MAP_FILE="$temp_map" - -date_filter=' - # Look up the new date for this commit - commit_sha="$GIT_COMMIT" - map_file="$DATE_MAP_FILE" - - if [ -f "$map_file" ]; then - line=$(grep "^$commit_sha|" "$map_file" || echo "") - if [ -n "$line" ]; then - new_author=$(echo "$line" | cut -d"|" -f2) - new_committer=$(echo "$line" | cut -d"|" -f3) - - if [ -n "$new_author" ]; then - export GIT_AUTHOR_DATE="$new_author" - fi - if [ -n "$new_committer" ]; then - export GIT_COMMITTER_DATE="$new_committer" - fi - fi - fi -' - -# Execute filter-branch with the date filter -git filter-branch --env-filter "$date_filter" --tag-name-filter cat -- --branches --tags ${sha:---all}${sha:+..HEAD} - -# Clean up the original refs -rm -rf .git/refs/original/ -git reflog expire --expire=now --all -git gc --prune=now - -# Push changes if the push flag is set -if [ "$push" = true ]; then - if [ "$force" = true ]; then - git push --force --tags origin 'refs/heads/*' - else - git push --force-with-lease --tags origin 'refs/heads/*' - fi -fi - -zz_log s "Git date fixup completed successfully." diff --git a/src/gitutils/bin/git-fix-del.sh b/src/gitutils/bin/git-fix-del.sh deleted file mode 100755 index 2a58dc4c..00000000 --- a/src/gitutils/bin/git-fix-del.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -# Handle parameters -eval $( - zz_args "Delete a specified commit and rebase subsequent history" $0 "$@" <<-help - f - force allow overwriting pushed history - p - push push to remote after deletion - a - auto fully automatic mode - no prompts - s strategy strategy strategy to use for conflict resolution (default: theirs) - - sha sha sha commit to delete -help -) - -# Set defaults and navigate to repository root -strategy="${strategy:-theirs}" -cd "$(git rev-parse --show-toplevel)" >/dev/null -git fetch --progress --prune --recurse-submodules=no origin >/dev/null - -# Check for ongoing rebase -if git isRebase; then - zz_log e 'Rebase is in progress, please finish or abort it first.' - exit 1 -fi - -# Get and validate commit -sha=$(git getcommit $force $sha) -if ! git rev-parse --verify "$sha^" >/dev/null 2>&1; then - zz_log e "Cannot delete commit $sha (invalid or initial commit)." - exit 1 -fi - -zz_log i "Deleting commit: $sha" - -# Attempt deletion with fallback to autorebase -if ! git autorebase ${auto:+-a} -s "$strategy" -o "$sha^" "$sha" ; then - zz_log e "Failed to delete commit $sha. Please resolve conflicts manually." - exit 1 -else - zz_log s "Commit deleted with conflict resolution" -fi - -# Push if requested -if [ -n "$push" ] && git rev-parse --verify --quiet origin/HEAD >/dev/null; then - zz_log i "Pushing to remote..." - git push ${force:+--force-with-lease} origin HEAD -elif [ -n "$push" ]; then - zz_log i "Branch not tracked remotely, skipping push" -fi - -zz_log s "Commit $sha successfully deleted and history rebased." \ No newline at end of file diff --git a/src/gitutils/bin/git-fix-emoji.sh b/src/gitutils/bin/git-fix-emoji.sh deleted file mode 100755 index afd274bf..00000000 --- a/src/gitutils/bin/git-fix-emoji.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Fix git emoji" $0 "$@" <<-help - f - force allow overwritting pushed history - p - push push changes after rewriting history - - sha sha sha commit to fix from after - help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Fetch updates from the remote repository -git fetch --progress --prune --recurse-submodules=no origin >/dev/null - -# Make sure we don't have uncommitted changes -if ! git diff-index --quiet HEAD --; then - zz_log e "You have uncommitted changes. Please commit or stash them before running this script." - exit 1 -fi - -# Retrieve the commit SHA to fixup -sha=$(git getcommit -p $force $sha) - -#### Rewrite history to fix author -# Rewrite commit messages to add the appropriate emoji from the specified commit -git filter-branch --msg-filter 'npx --yes devmoji' --tag-name-filter cat -- --branches --tags ${sha:---all}${sha:+..HEAD} - -# Clean up the original refs -rm -rf .git/refs/original/ -git reflog expire --expire=now --all -git gc --prune=now - -# Push changes if the push flag is set -if [ "$push" = true ]; then - if [ "$force" = true ]; then - git push --force --tags origin 'refs/heads/*' - else - git push --force-with-lease --tags origin 'refs/heads/*' - fi -fi - -zz_log s "Git emoji fixup completed successfully." diff --git a/src/gitutils/bin/git-fix-last.sh b/src/gitutils/bin/git-fix-last.sh deleted file mode 100755 index ab590fa7..00000000 --- a/src/gitutils/bin/git-fix-last.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Edit the last commit message and content" $0 "$@" <<-help - m msg msg New commit message -help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Edit the last commit message and content -git commit --amend --edit ${msg:+-m "$msg"} diff --git a/src/gitutils/bin/git-fix-lock.sh b/src/gitutils/bin/git-fix-lock.sh deleted file mode 100755 index d178cb9d..00000000 --- a/src/gitutils/bin/git-fix-lock.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Fix git lock files - resolve conflicts and regenerate lock files" $0 "$@" <<-help -help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# List files with conflicts and filter only lock files -conflicted_files=$(git diff --name-only --diff-filter=U | grep -E 'composer.lock|package-lock.json|yarn.lock') - -# Process each lock file with conflicts -for file in $conflicted_files; do - - # Keep our version of the lock file, then regenerate it below - zz_log i "Fixing merge conflict in $file" - git checkout --ours "$file" - - # Regenerate the lock file based on the type of file - case "$file" in - composer.lock) - # Regenerate composer.lock file with minimal changes - zz_log i "Regenerating composer.lock..." - composer update --lock --minimal-changes --ignore-platform-reqs --with-all-dependencies --no-scripts --no-interaction --no-progress --no-install - ;; - package-lock.json) - # Check if the project uses npm workspaces and regenerate package-lock.json accordingly - zz_log i "Regenerating package-lock.json..." - ws=$(npm pkg get workspaces) - if test "$ws" = "undefined" || test "$ws" = "{}"; then - npm install --package-lock - else - npm install --package-lock --ws --if-present --include-workspace-root - fi - ;; - yarn.lock) - # Regenerate yarn.lock file - zz_log i "Regenerating yarn.lock..." - yarn install --check-files - ;; - esac - # Stage the updated lock file for commit - zz_log i "Staging $file for commit..." - git add "$file" -done diff --git a/src/gitutils/bin/git-fix-message.sh b/src/gitutils/bin/git-fix-message.sh deleted file mode 100755 index 303690ef..00000000 --- a/src/gitutils/bin/git-fix-message.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Rewrite an arbitrary commit message" $0 "$@" <<-help - f - force allow overwritting pushed history - p - push push to remote - m msg msg new commit message - - sha sha sha commit to rewrite message - help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Fetch updates from the remote repository -git fetch --progress --prune --recurse-submodules=no origin >/dev/null - -# Make sure we don't have uncommitted changes -if ! git diff-index --quiet HEAD --; then - zz_log e "You have uncommitted changes. Please commit or stash them before running this script." - exit 1 -fi - -# Prevent running while a rebase is in progress -if git isRebase >/dev/null 2>&1; then - zz_log e "A rebase is in progress. Please finish or abort it before running this script." - exit 1 -fi - -# Retrieve the commit SHA to edit -sha=$(git getcommit $force $sha) - -# Validate commit exists -if ! git rev-parse --verify --quiet "$sha^{commit}" >/dev/null; then - zz_log e "Invalid commit: $sha" - exit 1 -fi - -# Ensure commit belongs to current branch history -if ! git merge-base --is-ancestor "$sha" HEAD; then - zz_log e "Commit $(echo "$sha" | cut -c1-7) is not in the current branch history." - exit 1 -fi - -# Ask for the new message if not provided as argument -if [ -z "$msg" ]; then - msg=$(zz_prompt "New commit message:") -fi - -# Ensure message is not empty -if [ -z "$msg" ]; then - zz_log e "Commit message cannot be empty." - exit 1 -fi - -old_msg=$(git log -1 --pretty=%s "$sha") -zz_log w "Commit to rewrite: $(echo "$sha" | cut -c1-7)" -zz_log - "Current message: $old_msg" -zz_log - "New message: $msg" -zz_log w "This will rewrite git history. Make sure you understand the consequences." - -if ! zz_ask "Yn" "Do you want to proceed?"; then - zz_log i "Operation cancelled by user." - exit 1 -fi - -# Build a minimal range that includes target commit and descendants. -if [ -n "$(git rev-list --parents -n 1 "$sha" | cut -d' ' -f2)" ]; then - range="$sha^..HEAD" -else - range="--all" -fi - -TARGET_SHA="$sha" NEW_MESSAGE="$msg" git filter-branch --msg-filter ' - if [ "$GIT_COMMIT" = "$TARGET_SHA" ]; then - printf "%s\n" "$NEW_MESSAGE" - else - cat - fi -' --tag-name-filter cat -- $range - -# Clean up the original refs -rm -rf .git/refs/original/ -git reflog expire --expire=now --all -git gc --prune=now - -# Push rewritten history if requested -if [ -n "$push" ]; then - if git rev-parse --verify --quiet origin/HEAD >/dev/null; then - zz_log i "Pushing to remote..." - if [ -n "$force" ]; then - git push --force origin HEAD - else - git push --force-with-lease origin HEAD - fi - else - zz_log i "Branch not pushed, skipping push..." - fi -fi - -zz_log s "Commit message rewritten successfully." \ No newline at end of file diff --git a/src/gitutils/bin/git-fix-mode.sh b/src/gitutils/bin/git-fix-mode.sh deleted file mode 100755 index a79be84f..00000000 --- a/src/gitutils/bin/git-fix-mode.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Fix file mode changes from diff -# Handle the case where no deleted files exist - -# Get the list of deleted files -deleted_files=$(git ls-files --deleted) - -# Generate the diff with mode information -diff_output=$(git diff -p -R --no-color | grep -E "^(diff|(old|new) mode)" --color=never) - -# If we have diff output -if [ -n "$diff_output" ]; then - if [ -n "$deleted_files" ]; then - # Filter out deleted files - echo "$diff_output" | grep -vF "$deleted_files" | git apply --allow-empty --no-index - else - # No deleted files, apply all mode changes - echo "$diff_output" | git apply --allow-empty --no-index - fi -fi - -exit 0 diff --git a/src/gitutils/bin/git-fix-privacy.sh b/src/gitutils/bin/git-fix-privacy.sh deleted file mode 100755 index 23d26a14..00000000 --- a/src/gitutils/bin/git-fix-privacy.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Fix privacy in history" $0 "$@" <<-help - f - force force overwrite backup - p - push push to remote - o old old old email to replace - n new new new name to replace with - a author author author name to replace with - - sha sha sha commit to start from - help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Fetch updates from the remote repository -git fetch --progress --prune --recurse-submodules=no origin >/dev/null - -# Check if the old option is set -if [ -z "$old" ]; then - zz_log w "Old email is not specified, it will be taken from the last commit" - old=$(git log -1 --pretty=format:'%ae') - - # Asks for confirmation to proceed with the old email - zz_ask Yn "Do you want to proceed with this <$old> as old email?" || exit 1 -fi - -# Check if the new option is set -if [ -z "$new" ]; then - zz_log w "New email is not specified, it will be taken from the specified commit" -fi - -# Check if the author option is set -if [ -z "$author" ]; then - zz_log w "Author is not specified, it will be taken from the specified commit" -fi - -# Retrieve the commit SHA to fixup -sha=$(git getcommit $force $sha) - -# Log the commit SHA to be fixed up -zz_log i "Fix privacy from commit: $sha" - -if [ -n "$new" ]; then - zz_log i "Setting new email for git config" - git config user.email "$new" -else - zz_log s "Take email from specified commit" - new=$(git log -1 --pretty=format:'%ae' "$sha") -fi - -if [ -n "$author" ]; then - echo "Setting new author for git config" - git config user.name "$author" -else - zz_log s "Take author from specified commit" - author=$(git log -1 --pretty=format:'%an' "$sha") -fi - -git filter-branch $force --env-filter " -if [ \"\$GIT_COMMITTER_EMAIL\" = \"$old\" ] -then - GIT_COMMITTER_NAME=\"$author\" - GIT_COMMITTER_EMAIL=\"$new\" -fi -if [ \"\$GIT_AUTHOR_EMAIL\" = \"$old\" ] -then - GIT_AUTHOR_NAME=\"$author\" - GIT_AUTHOR_EMAIL=\"$new\" -fi -" --tag-name-filter cat -- --branches --tags ${sha:---all}${sha:+..HEAD} - -if [ -n "$push" ]; then - zz_log i "Pushing changes to remote" - git push --force --progress --recurse-submodules=no origin --all - git push --force --progress --recurse-submodules=no origin --tags -else - zz_log w "Changes are not pushed to remote, use -p option to push" -fi - -# Clean up the original refs -rm -rf .git/refs/original/ -git reflog expire --expire=now --all -git gc --prune=now diff --git a/src/gitutils/bin/git-fix-prune.sh b/src/gitutils/bin/git-fix-prune.sh deleted file mode 100755 index 0aa438c8..00000000 --- a/src/gitutils/bin/git-fix-prune.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Prune remote-tracking references that no longer exist on the remote" $0 "$@" <<-help - - remote remote remote to prune (default: all remotes) - help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -remotes="${remote:-$(git remote)}" - -for r in $remotes; do - zz_log i "Pruning stale remote-tracking references for $r" - git remote prune "$r" -done - -zz_log s "Remote-tracking references pruned." diff --git a/src/gitutils/bin/git-fix-rights.sh b/src/gitutils/bin/git-fix-rights.sh deleted file mode 100755 index 72057bd0..00000000 --- a/src/gitutils/bin/git-fix-rights.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Fix git access rights - set appropriate permissions for files and directories" $0 "$@" <<-help - - help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Apply the given permission to the tracked files matching the given pathspecs. -# Operates on files tracked by git (not ignored/untracked ones), matching the -# stated intent of normalising the repository's own file permissions. -set_permissions() { - perm="$1" - shift - zz_log i "Setting permissions $perm for: ${*:-}" - git ls-files -z "$@" | xargs -0 -r chmod "$perm" -} - -# Regular files default to 644, scripts to 755, sensitive files to 600 -set_permissions 644 -set_permissions 755 '*.sh' -set_permissions 600 '*.conf' '*.env' - -# Directories default to 755 (git does not track directories, so derive them -# from the working tree, excluding the .git internals) -find "." -type d -not -path '*/.git' -not -path '*/.git/*' -exec chmod 755 {} + - -# Tighten well-known sensitive directories (e.g. logs, cache) to 700 -find "." -type d \( -name logs -o -name cache \) -not -path '*/.git/*' -exec chmod 700 {} + - -zz_log s "Access rights have been set according to best practices." diff --git a/src/gitutils/bin/git-fix-secrets.sh b/src/gitutils/bin/git-fix-secrets.sh deleted file mode 100755 index 87bb0c65..00000000 --- a/src/gitutils/bin/git-fix-secrets.sh +++ /dev/null @@ -1,176 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Redact a secret from files, commit messages and/or tag annotations across all git history" $0 "$@" <<-help - f - force allow overwriting pushed history - p - push push to remote - d - dryrun list matching commits/files without rewriting history - g glob glob glob pattern of files to search (e.g. "**/*.env") - s secret secret secret value to redact - r repl replace replacement string (default: ****) - m - fixmsg also redact the secret from commit messages - t - fixtags also redact the secret from tag annotation messages - - sha sha sha commit to fix from (use 0 for the very first commit) - help -) - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Fetch updates from the remote repository -git fetch --progress --prune --recurse-submodules=no origin >/dev/null - -# Check if the glob option is set -if [ -z "$glob" ]; then - glob=$(zz_prompt "Glob pattern of files to search (e.g. **/*.env):") -fi - -if [ -z "$glob" ]; then - zz_log e "A glob pattern is required." - exit 1 -fi - -# Check if the secret option is set -if [ -z "$secret" ]; then - secret=$(zz_prompt "Secret value to redact:") -fi - -if [ -z "$secret" ]; then - zz_log e "A secret value is required." - exit 1 -fi - -# Default replacement string -replace="${replace:-****}" - -# Make sure we don't have uncommitted changes -if ! git diff-index --quiet HEAD --; then - zz_log e "You have uncommitted changes. Please commit or stash them before running this script." - exit 1 -fi - -# Prevent running while a rebase is in progress -if git isRebase >/dev/null 2>&1; then - zz_log e "A rebase is in progress. Please finish or abort it before running this script." - exit 1 -fi - -# Retrieve the commit SHA to fix from -sha=$(git getcommit $force $sha) - -zz_log i "Searching for secret in files matching '$glob'" - -file_matches=$(git grep -I -l -F "$secret" $(git rev-list --branches --tags ${sha:---all}${sha:+..HEAD}) -- "$glob" 2>/dev/null) - -msg_matches="" -if [ -n "$fixmsg" ]; then - msg_matches=$(git log --branches --tags ${sha:---all}${sha:+..HEAD} -F --grep="$secret" --oneline) -fi - -tag_matches="" -if [ -n "$fixtags" ]; then - for t in $(git tag -l); do - msg=$(git for-each-ref --format='%(contents)' "refs/tags/$t" 2>/dev/null) - if printf '%s' "$msg" | grep -qF "$secret"; then - tag_matches="$tag_matches$t -" - fi - done -fi - -if [ -z "$file_matches" ] && [ -z "$msg_matches" ] && [ -z "$tag_matches" ]; then - zz_log s "No occurrences of the secret found." - exit 0 -fi - -[ -n "$file_matches" ] && zz_log - "Files:" && zz_log - "$file_matches" -[ -n "$msg_matches" ] && zz_log - "Commit messages:" && zz_log - "$msg_matches" -[ -n "$tag_matches" ] && zz_log - "Tag annotations:" && zz_log - "$tag_matches" - -if [ -n "$dryrun" ]; then - zz_log i "Dry run complete. No changes were made." - exit 0 -fi - -zz_log w "This will rewrite git history. Make sure you understand the consequences." -if ! zz_ask "Yn" "Do you want to proceed?"; then - zz_log i "Operation cancelled by user." - exit 1 -fi - -# Escape the secret so it is matched literally by sed, not as a regex -sed_escape_pattern() { - printf '%s' "$1" \ - | sed -e 's/\\/\\\\/g' \ - -e 's/\//\\\//g' \ - -e 's/\./\\./g' \ - -e 's/\*/\\*/g' \ - -e 's/\[/\\[/g' \ - -e 's/\^/\\^/g' \ - -e 's/\$/\\$/g' -} - -# Escape the replacement so it is inserted literally by sed -sed_escape_replacement() { - printf '%s' "$1" \ - | sed -e 's/\\/\\\\/g' \ - -e 's/\//\\\//g' \ - -e 's/&/\\&/g' -} - -esc_secret=$(sed_escape_pattern "$secret") -esc_replace=$(sed_escape_replacement "$replace") -export SED_EXPR="s/${esc_secret}/${esc_replace}/g" -export GLOB_PATTERN="$glob" - -tree_filter=' - git ls-files -- "$GLOB_PATTERN" | while IFS= read -r file; do - [ -f "$file" ] || continue - grep -Iq . "$file" 2>/dev/null || continue - sed -i "$SED_EXPR" "$file" - done -' - -if [ -n "$fixmsg" ]; then - msg_filter='sed "$SED_EXPR"' -else - msg_filter='cat' -fi - -git filter-branch $force --tree-filter "$tree_filter" --msg-filter "$msg_filter" --tag-name-filter cat -- --branches --tags ${sha:---all}${sha:+..HEAD} - -# Clean up the original refs -rm -rf .git/refs/original/ -git reflog expire --expire=now --all -git gc --prune=now - -# Redact secret from annotated tag messages (filter-branch does not rewrite tag content) -if [ -n "$fixtags" ]; then - zz_log i "Redacting secret from tag annotations" - for t in $(git tag -l); do - obj_type=$(git cat-file -t "refs/tags/$t" 2>/dev/null) - if [ "$obj_type" = "tag" ]; then - msg=$(git for-each-ref --format='%(contents)' "refs/tags/$t") - if printf '%s' "$msg" | grep -qF "$secret"; then - new_msg=$(printf '%s' "$msg" | sed "$SED_EXPR") - target=$(git rev-list -n 1 "$t") - tagger_name=$(git for-each-ref --format='%(taggername)' "refs/tags/$t") - tagger_email=$(git for-each-ref --format='%(taggeremail)' "refs/tags/$t" | sed -e 's/^$//') - tagger_date=$(git for-each-ref --format='%(taggerdate:iso-strict)' "refs/tags/$t") - GIT_COMMITTER_NAME="$tagger_name" GIT_COMMITTER_EMAIL="$tagger_email" GIT_COMMITTER_DATE="$tagger_date" \ - git tag -f -a "$t" "$target" -m "$new_msg" - fi - fi - done -fi - -if [ -n "$push" ]; then - zz_log i "Pushing changes to remote" - git push --force --progress --recurse-submodules=no origin --all - git push --force --progress --recurse-submodules=no origin --tags -else - zz_log w "Changes are not pushed to remote, use -p option to push" -fi - -zz_log s "Secret replaced with '$replace' in history." diff --git a/src/gitutils/bin/git-fix-up.sh b/src/gitutils/bin/git-fix-up.sh deleted file mode 100755 index 39270a3c..00000000 --- a/src/gitutils/bin/git-fix-up.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Fix git history" $0 "$@" <<-help - f - force allow overwritting pushed history - e - edit edit commit message - p - push push to remote - - sha sha sha commit to fixup - help -) - -# Do not fixup if staged files contain composer.lock or package-lock.json -if [ -n "$(git diff --cached --name-only | grep -E 'composer.lock|package-lock.json')" ]; then - zz_log e 'Packages lock file are staged, fixup is not allowed.' - exit 1 -fi - -# Prepare environment variables GIT_EDITOR based on edit option -if [ -z "$edit" ]; then - export GIT_EDITOR=":" -fi - -# Navigate to the repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Fetch updates from the remote repository -git fetch --progress --prune --recurse-submodules=no origin >/dev/null - -# Check if a fixup commit already exists -if git isFixup; then - zz_log e 'Fixup commit found, please continue rebasing...' - exit 1 -fi - -# Ensure there are staged files before proceeding -if [ -z "$(git diff --cached --name-only)" ]; then - zz_log e 'No files are staged, fixup is not allowed.' - exit 1 -fi - -# Retrieve the commit SHA to fixup -sha=$(git getcommit $force $sha) - -# Log the commit SHA to be fixed up -zz_log i "Fixup commit given: $sha" - -# Create a fixup commit and handle failure -if ! git commit --fixup $sha; then - zz_log e 'Fixup commit failed...' - exit 1 -fi - -# Start an interactive rebase with autosquash -git rebase -i --autosquash $sha~ --autostash --no-verify --reschedule-failed-exec --exec 'git hook run --ignore-missing pre-commit -- HEAD HEAD~1 && git commit --amend --no-edit --no-verify' --no-verify - -while [ $? -ne 0 ]; do - - # Check if there are any composer conflicts during the rebase - if [ -f composer.lock ]; then - if grep -q "^<<<<<<< \|^======= \|^>>>>>>> " composer.lock; then - # accept incoming changes in composer.lock - git checkout --theirs composer.lock - - # run composer lock to update the lock file - composer lock || zz_log e 'Please resolve composer.lock conflicts manually.' - fi - fi - - # Check if there are any package-lock conflicts during the rebase - if [ -f package-lock.json ]; then - if grep -q "^<<<<<<< \|^======= \|^>>>>>>> " package-lock.json; then - # accept incoming changes in package-lock.json - git checkout --theirs package-lock.json - - # run npm install to update the lock file - npm install || zz_log e 'Please resolve package-lock.json conflicts manually.' - fi - fi - - # Continue the rebase process - git rebase --continue -done - -# if rebase successful and push option is set, push force the changes -if [ "$?" -eq 0 -a -n "$push" ]; then - # Check if the branch is already pushed - if git rev-parse --verify --quiet origin/HEAD >/dev/null; then - zz_log i "Pushing to remote..." - git push --force-with-lease origin HEAD - else - zz_log i "Branch not pushed, skipping push..." - fi -fi diff --git a/src/gitutils/bin/git-fix.sh b/src/gitutils/bin/git-fix.sh deleted file mode 100755 index c875978e..00000000 --- a/src/gitutils/bin/git-fix.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -# This script is a utility to dispatch calls to various utilities with the same prefix. -zz_dispatch $0 "$@" diff --git a/src/gitutils/bin/git-forall.sh b/src/gitutils/bin/git-forall.sh deleted file mode 100755 index 141b5401..00000000 --- a/src/gitutils/bin/git-forall.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -( - git ls-files --exclude-standard - git ls-files --exclude-standard --others -) | while read f; do - $* $f -done diff --git a/src/gitutils/bin/git-getcommit.sh b/src/gitutils/bin/git-getcommit.sh deleted file mode 100755 index 69ac7041..00000000 --- a/src/gitutils/bin/git-getcommit.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "List git history and asks for commit" $0 "$@" <<-help - f - force allow overwritting pushed history - p - previous show commit previous to the one specified - - sha sha sha commit to fix from after - help -) - -#### Go to repository root -cd "$(git rev-parse --show-toplevel)" - -# Fetch updates from the remote repository -git fetch --progress --prune --recurse-submodules=no origin >/dev/null - -if [ -z "$sha" ]; then - if [ -n "$force" ]; then - zz_log w "Force mode enabled, overwriting pushed history" - git forceable >&2 - else - git fixable >&2 - fi - sha=$(zz_prompt "Which commit?") -fi - - -#### A sha of '0' means the very first commit in the current history -if [ "$sha" = "0" ]; then - sha=$(git rev-list --max-parents=0 HEAD | tail -1) -fi - -#### Display commit to fixup, keep only the sha, remove new line -sha=$(git rev-parse --verify "$sha^{commit}" | cut -d' ' -f1 | tr -d '\n') - -if [ -n "$previous" ]; then - git log --pretty=%P -1 "$sha" -else - echo "$sha" -fi diff --git a/src/gitutils/bin/git-integrate.sh b/src/gitutils/bin/git-integrate.sh deleted file mode 100755 index fd1af152..00000000 --- a/src/gitutils/bin/git-integrate.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh - -#### GOTO DIRECTORY -cd "$(git rev-parse --show-toplevel)" - -pwd - -#### CONFIGURE REPO -git config core.autocrlf false - -#### INTEGRATE MODIFICATIONS -echo 'Integrate modifications...' -git status --porcelain | sed 's/\(.*\)/-\1/g' | while read status; do - index=$(echo $status | cut -c2 | sed -e 's/^ *//' -e 's/ *$//') - local=$(echo $status | cut -c3 | sed -e 's/^ *//' -e 's/ *$//') - file=$(echo $status | cut -c4- | sed -e 's/^ *//' -e 's/ *$//') - - if [ "$local" = 'M' -o "$local" = '?' ]; then - if git diff -a -b -w --quiet HEAD -- "$file" 2>/dev/null; then - if git checkout --quiet -- "$file" 2>/dev/null; then - echo -n "." - else - echo "Add new file: $file" - git add -- "$file" - fi - else - echo - echo "File changed: $file" - git add -- "$file" - fi - fi -done - -#### BACK -cd - >/dev/null diff --git a/src/gitutils/bin/git-pick.sh b/src/gitutils/bin/git-pick.sh deleted file mode 100755 index 9b344e8f..00000000 --- a/src/gitutils/bin/git-pick.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "Pick files from a specific commit" $0 "$@" <<-help - c commit commit commit sha to pick from (if not provided, will prompt) - - path path path to restore (default: current directory) - help -) - -#### Go to repository root -cd "$(git rev-parse --show-toplevel)" - -# If no commit is provided, use git getcommit to select one -if [ -z "$commit" ]; then - commit=$(git getcommit) - if [ $? -ne 0 ] || [ -z "$commit" ]; then - zz_log e "No commit selected or invalid commit" - exit 1 - fi -fi - -# If no path is provided, use current prefix (relative to repo root) -if [ -z "$path" ]; then - path=$(git rev-parse --show-prefix) - # If path is empty (we're at repo root), use current directory - if [ -z "$path" ]; then - path="." - fi -fi - -zz_log i "Picking files from commit: $commit" -zz_log i "Target path: $path" - -# Execute the git restore command -git restore --source="$commit" --staged --worktree "$path" - -if [ $? -eq 0 ]; then - zz_log s "Successfully picked files from commit $commit to $path" -else - zz_log e "Failed to pick files from commit $commit" - exit 1 -fi diff --git a/src/gitutils/bin/git-release-alpha.sh b/src/gitutils/bin/git-release-alpha.sh deleted file mode 100755 index 57a00f3f..00000000 --- a/src/gitutils/bin/git-release-alpha.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/sh - -# This script performs a squash merge of the current feature branch into the develop branch. -# It enforces conventional commit message standards, handles breaking changes, and optionally pushes to remote. - -# Parse arguments and print help if needed -eval $( - zz_args "Release squashed current branch to develop branch" $0 "$@" <<-help - m msg msg Message to use for the squash merge commit - o - occ Override the commit message to use the most occurring commit type - p - push Push the changes to the remote repository after merging -help -) - -# Ensure commit message is provided -if [ -z "$msg" ]; then - zz_log e "You must provide a message for the squash merge commit using the -m option." - exit 1 -fi - -# Change to repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Get the current branch name -current_branch=$(git rev-parse --abbrev-ref HEAD) - -# Ensure the script is run from a feature branch (enforces workflow discipline) -if ! echo "$current_branch" | grep -qE "^feature/"; then - zz_log e "You must be on a feature/xxx branch to release unstable version" - zz_log i "Current branch: $current_branch" - exit 1 -fi - -# Extract feature name from branch -feature=$(echo "$current_branch" | sed 's/^feature\///') - -# Update the commit message to include the feature name as a scope (conventional commit) -msg=$(echo "$msg" | sed -E "s/^([a-z]+):/\1($feature):/") - -# Stash any uncommitted changes before proceeding -zz_log i "Stashing current changes..." -stash="Before finishing $current_branch branch" -git stash push --include-untracked -m "$stash" - -# Check for breaking changes in commit messages and update the commit message accordingly -if git log "$current_branch" --pretty=format:"%s" | grep -Eq "(\!:)" || git log --grep="BREAKING-CHANGE" --oneline "$current_branch" --grep="BREAKING CHANGE" | grep -q .; then - zz_log w "Breaking change found in $current_branch commit messages" - zz_log - "updating conventional commit message to reflect this..." - # Add '!' to the commit type if not already present - msg=$(echo $msg | sed -E 's/^([a-z]+(\([^)]+\))?):/\1!:/') -else - zz_log s "No breaking change found in commit messages" -fi - -all_types=$(git log "$current_branch" --pretty=format:"%s" | grep -oE "^([a-z]+)" | sort | uniq -c | sort -nr) - -# If all commit messages start with the same conventional commit type, start the commit message with that type. -# Else keep the most occurring first word. -# Else force "feat" if at least one commit message starts with "feat" -if echo "$all_types" | wc -l | grep -q '^1$'; then - first_word=$(echo "$all_types" | awk '{print $2}') - msg=$(echo "$msg" | sed -E "s/^( *[a-z]+)/$first_word/") - zz_log i "All commit messages start with '$first_word', updating commit message to start with it..." -elif [ -n "$occ" ]; then - first_word=$(echo "$all_types" | head -n 1 | awk '{print $2}') - msg=$(echo "$msg" | sed -E "s/^( *[a-z]+)/$first_word/") - zz_log i "Most occurring first word is '$first_word', updating commit message to start with it..." -elif echo "$all_types" | grep -q '^ *feat'; then - first_word=feat - msg=$(echo "$msg" | sed -E "s/^( *[a-z]+)/$first_word/") - zz_log i "At least one commit message starts with 'feat', updating commit message to start with it..." -else - zz_log i "No specific commit type found, keeping message as is..." -fi - -# Prevent git editor prompt during commit -export GIT_EDITOR=: - -# Perform the squash merge of the feature branch into develop - -zz_log i "Finishing $current_branch with squash merge to develop..." -git fetch origin || zz_log w "Failed to fetch origin" - -if ! git checkout develop; then - zz_log e "Failed to checkout develop branch" - git stash list | grep -q "$stash" && git stash pop - exit 1 -fi - -if ! git merge --squash "$current_branch"; then - zz_log e "Failed to squash merge $current_branch into develop" - git stash list | grep -q "$stash" && git stash pop - exit 1 -fi - -if ! git commit -m "$msg"; then - zz_log e "Failed to commit squash merge for $current_branch" - git stash list | grep -q "$stash" && git stash pop - exit 1 -fi - -# Restore stashed changes and log result -zz_log s "Feature $feature successfully finished and squashed to develop" - -# Restore stashed changes if any -git stash list | grep -q "$stash" && git stash pop - -# Push changes to remote if requested -if [ -n "$push" ]; then - zz_log i "Pushing changes to remote repository..." - git push origin develop && - zz_log s "Changes pushed to remote repository successfully" || - zz_log e "Failed to push changes to remote repository" -fi - -# Switch back to the original feature branch -git checkout "$current_branch" diff --git a/src/gitutils/bin/git-release-beta.sh b/src/gitutils/bin/git-release-beta.sh deleted file mode 100755 index 19a303e3..00000000 --- a/src/gitutils/bin/git-release-beta.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh - -#### Goto repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -#### GET BUMP VERSION -GBV=$(gv -showvariable MajorMinorPatch) -if [ -z "$GBV" ]; then - zz_log e "Cannot compute release version" - exit 1 -fi - -#### EXIT IF A DIFFERENT RELEASE ALREADY EXISTS -# Compare against branches directly (not the .git/RELEASE file) so a stale -# file left over from a prior run can never block/mask the real state. -other=$(git branch --list 'release/*' | sed 's/^[* ]*release\///' | grep -vFx "$GBV") -if [ -n "$other" ]; then - zz_log e "Other release exists, cannot proceed: $(printf '%s' "$other" | tr '\n' ' ')" - exit 1 -fi - -#### PREVENT GIT EDITOR PROMPT -export GIT_EDITOR=: - -#### STEP: create the release branch (idempotent -- resume if already started) -if git show-ref --verify --quiet "refs/heads/release/$GBV"; then - zz_log i "Release branch release/$GBV already exists, resuming" - if [ "$(git branch --show-current)" != "release/$GBV" ] && ! git checkout "release/$GBV"; then - zz_log e "Cannot switch to release/$GBV" - exit 1 - fi -elif ! git flow release start "$GBV"; then - zz_log e "Failed to start release $GBV" - exit 1 -fi - -#### STEP: record release state + push (safe to repeat -- push is a no-op -#### once the remote already has the branch) -printf '%s\n' "$GBV" >.git/RELEASE -if ! git push origin "release/$GBV"; then - zz_log e "Cannot push release/$GBV, re-run this command to retry" - exit 1 -fi diff --git a/src/gitutils/bin/git-release-hotfix.sh b/src/gitutils/bin/git-release-hotfix.sh deleted file mode 100755 index c5f773e5..00000000 --- a/src/gitutils/bin/git-release-hotfix.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/bin/sh - -#### Goto repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Parse arguments and print help if needed -eval $( - zz_args "Create HotFix branch" $0 "$@" <<-help - r - rebase force rebase of current commits onto hotfix branch - s - stash stash current staged changes before creating hotfix branch and reapply them after -help -) - -#### GET last vX.Y.Z tag on the main branch and replace the last number with 'X' (leading 'v' is kept) -main_tag=$(git describe --tags --abbrev=0 --match "v[0-9]*.[0-9]*.[0-9]*" main) - -if [ -z "$main_tag" ]; then - zz_log e "No tag found on main branch" - exit 1 -else - zz_log i "Current version is $main_tag" -fi - -hotfix=$(echo "$main_tag" | sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+)/\1.\2.X/') - -# Capture develop's tip before we potentially checkout the hotfix branch -# below, so the rebase-safety check further down always evaluates develop -# itself rather than whatever branch happens to be checked out at the time. -develop_rev=$(git rev-parse develop) - -#### STEP: resolve the hotfix branch (idempotent -- reuse it if a previous -#### run already created it, instead of failing on `git flow hotfix start`) -resumed="" -if git show-ref --verify --quiet "refs/heads/hotfix/$hotfix"; then - resumed=true - zz_log i "Hotfix branch hotfix/$hotfix already exists, resuming" - if [ "$(git branch --show-current)" != "hotfix/$hotfix" ] && ! git checkout "hotfix/$hotfix"; then - zz_log e "Cannot switch to hotfix/$hotfix" - exit 1 - fi -fi - -# Check if all commits since the last tag are conventional commits of 'fix:' type -# or if rebase is forced via command line option -if [ -n "$rebase" ]; then - zz_log i "Rebase forced via command line option, will rebase commits onto hotfix branch" -elif git log --reverse --pretty=oneline --format=%B develop --not origin/develop --no-merges | grep -vE "^$|^fix(\(.+\))?:" >/dev/null; then - zz_log w "There are commits since $main_tag that are not of type 'fix:', creating hotfix branch only" - unset rebase -else - zz_log i "All commits since $main_tag are of type 'fix:', creating hotfix branch and rebasing current history + stash on top of it" - rebase=true -fi - -# If rebase needed, check that develop branch has not been pushed since last -# tag and that its tip isn't a merge commit -- either makes it unsafe to reset -# develop back to the main tag afterwards. Refresh the remote-tracking ref -# first so a stale local origin/develop can't mask the first case, and check -# develop's captured tip explicitly (not HEAD, which may now be the hotfix -# branch if this run resumed an already-created one). -if [ -n "$rebase" ]; then - if ! git fetch origin develop >/dev/null 2>&1; then - zz_log e "Cannot fetch develop branch from remote" - exit 1 - fi - if [ "$develop_rev" = "$(git rev-parse origin/develop)" ] || [ "$(git rev-list --parents -1 "$develop_rev" | wc -w)" -gt 2 ]; then - zz_log e "Develop branch has already been pushed or its tip is a merge commit, cannot rebase safely, aborting" - exit 1 - fi -fi - -#### STEP: stash local changes, if any, before creating the branch (idempotent -#### -- reclaim a stash left over from an interrupted previous run instead of -#### stashing again on top of it or leaving it stuck) -pending_stash=$(git stash list | grep -m1 "Hotfix stash:" | cut -d: -f1) - -if [ -z "$resumed" ]; then - if [ -n "$(git status --porcelain)" ]; then - zz_log w "Working directory is not clean. Stashing staged changes before creating hotfix branch..." - git stash save -k -m "Hotfix stash: $(date +%Y-%m-%d-%H-%M-%S)" - pending_stash=$(git stash list | grep -m1 "Hotfix stash:" | cut -d: -f1) - else - zz_log i "Working directory is clean, no need to stash changes" - fi -elif [ -n "$pending_stash" ]; then - zz_log i "Found stash left over from a previous run ($pending_stash), will reapply it" -fi - -# Set GIT_EDITOR to no-op to avoid opening editor during rebase or cherry-pick -export GIT_EDITOR=: - -#### STEP: create hotfix branch (bail out if it fails, so we don't pop the -#### stash or rebase onto the wrong branch) -if [ -z "$resumed" ] && ! git flow hotfix start "$hotfix"; then - zz_log e "Failed to start hotfix branch $hotfix" - exit 1 -fi - -#### STEP: reapply any pending stash (idempotent -- no-op when there is none) -if [ -n "$pending_stash" ]; then - zz_log i "Applying stashed changes ($pending_stash)..." - if ! git stash pop --index "$pending_stash"; then - zz_log e "Stash pop had conflicts. Resolve them, then re-run this command to continue." - exit 1 - fi -fi - -#### STEP: pick all "fix" commits from develop branch and rebase them onto -#### hotfix branch, then reset develop branch to the main tag. Naturally -#### idempotent: it only moves commits still ahead of the hotfix branch, so a -#### re-run after everything already moved is a no-op. -if [ -n "$rebase" ]; then - zz_log i "Rebasing develop commits onto hotfix branch..." - git fix base -p "hotfix/$hotfix" develop -fi diff --git a/src/gitutils/bin/git-release-prod.sh b/src/gitutils/bin/git-release-prod.sh deleted file mode 100755 index ea9c9a3f..00000000 --- a/src/gitutils/bin/git-release-prod.sh +++ /dev/null @@ -1,184 +0,0 @@ -#!/bin/sh - -# Parse arguments and print help if needed -eval $( - zz_args "Release production branch" $0 "$@" <<-help -help -) - -# Change to repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -# Refresh tags up front so the "already finished" checks below (which rely on -# tag existence) see what's actually on the remote, not a stale local view. -git fetch origin --tags >/dev/null 2>&1 - -# git flow finish tags as (default "v"). -versiontag_prefix=$(git config gitflow.prefix.versiontag 2>/dev/null) -versiontag_prefix="${versiontag_prefix:-v}" - -#### STEP: resolve which flow branch to finish ----------------------------- -# Prefer the branch we're already on, so a checked-out hotfix/release branch -# is never overridden by an unrelated one that also happens to exist locally. -current=$(git branch --show-current) -case "$current" in -hotfix/*) - flow=hotfix - name=${current#hotfix/} - zz_log i "On hotfix branch: {Yellow $name}" - ;; -release/*) - flow=release - name=${current#release/} - zz_log i "On release branch: {Blue $name}" - ;; -esac - -# Not currently on a flow branch: fall back to discovery, but refuse to guess -# when more than one candidate exists -- silently picking one risks finishing -# (merging + tagging + pushing) the wrong release/hotfix. -if [ -z "$flow" ]; then - hotfixes=$(git branch --list 'hotfix/*' | sed 's/^[* ]*hotfix\///') - hotfix_count=$(printf '%s\n' "$hotfixes" | grep -c .) - - if [ "$hotfix_count" -gt 1 ]; then - zz_log e "Multiple hotfix branches found, checkout the one to finish first: $(printf '%s' "$hotfixes" | tr '\n' ' ')" - exit 1 - elif [ "$hotfix_count" -eq 1 ]; then - flow=hotfix - name=$hotfixes - zz_log i "Hotfix branch found: {Yellow $name}" - elif [ -f .git/RELEASE ]; then - name=$(cat .git/RELEASE) - # Cleanup-only case: `git flow finish` already ran to completion on a - # prior run (it deletes the release branch on success), but the - # trailing bump-tag/cleanup step never happened. There's no branch - # left to check out, so finish the leftover cleanup here and stop. - if ! git show-ref --verify --quiet "refs/heads/release/$name" \ - && git rev-parse -q --verify "refs/tags/${versiontag_prefix}${name}" >/dev/null; then - zz_log i "Release $name already finished (branch gone, tag exists) -- cleaning up only" - bump-tag "$name" - rm -f .git/RELEASE - exit 0 - fi - flow=release - zz_log i "Release branch found: {Blue $name}" - else - releases=$(git branch --list 'release/*' | sed 's/^[* ]*release\///') - release_count=$(printf '%s\n' "$releases" | grep -c .) - - if [ "$release_count" -gt 1 ]; then - zz_log e "Multiple release branches found, checkout the one to finish first: $(printf '%s' "$releases" | tr '\n' ' ')" - exit 1 - elif [ "$release_count" -eq 1 ]; then - flow=release - name=$releases - zz_log i "Release branch found: {Blue $name}" - fi - fi -fi - -# Exit if no flow branch is found -if [ -z "$flow" ] || [ -z "$name" ]; then - zz_log e "No flow branch found" - exit 1 -fi - -# Switch to the resolved branch -if ! git checkout "$flow/$name" >/dev/null 2>&1; then - zz_log e "Cannot switch to $flow/$name branch" - exit 1 -fi -zz_log s "On branch: {Blue $flow/$name}" - -#### STEP: determine target version + whether finish already happened ------ -GBV=$(gv -showvariable MajorMinorPatch) -if [ -z "$GBV" ]; then - zz_log e "Cannot get version from .gitversion" - exit 1 -fi -zz_log i "Bump version: {Blue $GBV}" - -# If the finish tag already exists, finish already ran to completion on a -# prior run -- resume at cleanup only instead of redoing the merge/tag/push. -finished="" -if git rev-parse -q --verify "refs/tags/${versiontag_prefix}${GBV}" >/dev/null; then - zz_log i "Tag ${versiontag_prefix}${GBV} already exists, release already finished -- resuming cleanup only" - finished=true -fi - -# Prevent git editor prompt during finish -export GIT_EDITOR=: - -if [ -z "$finished" ]; then - - # Ensure working directory is clean - if [ -n "$(git status --porcelain)" ]; then - zz_log e "Working directory is not clean. Please commit or stash changes." - exit 1 - fi - - # Ensure the flow branch has an up-to-date remote - if ! git fetch origin >/dev/null 2>&1; then - zz_log e "Cannot fetch from remote" - exit 1 - fi - - # is-ancestor(remote, local) succeeds only when the remote tip is already - # contained in local -- i.e. local is not behind. Passed the other way - # round it would succeed while local is behind (needs a pull). - if ! git merge-base --is-ancestor "$(git rev-parse "refs/remotes/origin/$flow/$name")" "$(git rev-parse "$flow/$name")" ; then - zz_log e "$flow/$name branch is not up-to-date with remote. Please pull the latest changes." - exit 1 - fi - - #### STEP: bump version/changelog + commit (idempotent -- skip if a - #### previous run already made this exact commit) - if [ "$(git log -1 --pretty=%s)" = "chore(release): $GBV" ]; then - zz_log i "Version & CHANGELOG already committed for $GBV, skipping bump" - else - if ! bump-changelog -f "$GBV" -b -m; then - zz_log e "Cannot update version & CHANGELOG" - exit 1 - fi - zz_log s "Version & CHANGELOG updated to: {B $GBV}" - if ! git commit -am "chore(release): $GBV"; then - zz_log e "Cannot commit version & CHANGELOG" - exit 1 - fi - fi - - #### STEP: push (safe to repeat -- no-op once the remote already has it) - if ! git push --set-upstream origin "$flow/$name"; then - zz_log e "Cannot push $flow/$name, re-run this command to retry" - exit 1 - fi - zz_log s "Version & CHANGELOG committed and pushed" - - # Ensure develop branch is up-to-date before finishing release - if ! git fetch origin develop:develop; then - zz_log e "Cannot fetch develop branch from remote" - exit 1 - fi - - if ! git merge-base --is-ancestor "$(git rev-parse origin/develop)" "$(git rev-parse develop)" ; then - zz_log e "Develop branch is not up-to-date with remote. Please pull the latest changes." - exit 1 - fi - - #### STEP: finish (merge to main/develop + tag + push) - # git flow finish prepends gitflow.prefix.versiontag to --tagname itself, - # so pass the bare version here -- prefixing it ourselves would tag "vv$GBV". - if git flow "$flow" finish "$name" --push --tagname "$GBV" --message "$GBV" ; then - zz_log s "Release finished: {B $GBV}" - else - zz_log e "Cannot finish release. Please fix the issues, commit any pending changes, then re-run this command to retry -- or finish manually with:" - zz_log - " git flow $flow finish $name --push --tagname $GBV --message $GBV" - exit 1 - fi -fi - -#### STEP: tag follow-up + cleanup (both idempotent, safe to repeat) ------- -bump-tag "$GBV" -# Clear release state only once the release has actually finished. -rm -f .git/RELEASE diff --git a/src/gitutils/bin/git-release.sh b/src/gitutils/bin/git-release.sh deleted file mode 100755 index c875978e..00000000 --- a/src/gitutils/bin/git-release.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -# This script is a utility to dispatch calls to various utilities with the same prefix. -zz_dispatch $0 "$@" diff --git a/src/gitutils/bin/git-unset.sh b/src/gitutils/bin/git-unset.sh deleted file mode 100755 index 5391178c..00000000 --- a/src/gitutils/bin/git-unset.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -#### Goto repository root -cd "$(git rev-parse --show-toplevel)" >/dev/null - -#### Unset all git config keys starting with the given prefix -git config ${2:---local} --get-regexp "^${1:-[a-z]+}\\." | cut -d ' ' -f 1 | while read alias_key; do - git config ${2:---local} --unset-all "$alias_key" -done diff --git a/src/gitutils/bin/git-workspaces.sh b/src/gitutils/bin/git-workspaces.sh deleted file mode 100755 index 1e229187..00000000 --- a/src/gitutils/bin/git-workspaces.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/sh - -# Function to print help and manage arguments -eval $( - zz_args "List workspace directories and affected workspaces" $0 "$@" <<-help - r range range Git range to check for affected workspaces (prints only affected workspaces) -help -) - -#### Go to repository root -cd "$(git rev-parse --show-toplevel)" - -list_workspace_dirs() { - if [ -f "package.json" ] && command -v jq >/dev/null 2>&1; then - local workspaces - workspaces=$(jq -r '.workspaces[]? // empty' package.json 2>/dev/null || true) - - if [ -n "$workspaces" ]; then - echo "$workspaces" | while read -r workspace_pattern; do - if echo "$workspace_pattern" | grep -q '\*'; then - # Expand simple glob patterns like "packages/*" or "src/*" - find . -path "./$workspace_pattern" -type d -mindepth 1 -maxdepth 2 2>/dev/null || true - else - if [ -d "$workspace_pattern" ]; then - echo "./$workspace_pattern" - fi - fi - done - return 0 - fi - fi - - # Fallback: list all immediate subdirectories - find . -mindepth 1 -maxdepth 1 -type d -} - -get_affected_workspaces() { - local range="$1" - - # If no range provided, nothing to do - if [ -z "$range" ]; then - return 0 - fi - - # Get all changed files in the range and determine which workspaces they belong to - git log --name-only --pretty=format: "$range" 2>/dev/null \ - | while read -r line; do - if [ -n "$line" ]; then - # This is a file path, check which workspace it belongs to - list_workspace_dirs | while read -r workspace_dir; do - workspace_dir_clean=$(echo "$workspace_dir" | sed 's|^./||') - if echo "$line" | grep -q "^$workspace_dir_clean/"; then - echo "$workspace_dir" - fi - done - # Check for root-level files (not in any workspace subdirectory) - if ! echo "$line" | grep -q "/"; then - echo "." - fi - fi - done -} - -# Main execution logic -if [ -n "$range" ]; then - get_affected_workspaces "$range" | sort -u -else - list_workspace_dirs -fi diff --git a/src/gitutils/devcontainer-feature.json b/src/gitutils/devcontainer-feature.json index b1cb1423..60fb8926 100644 --- a/src/gitutils/devcontainer-feature.json +++ b/src/gitutils/devcontainer-feature.json @@ -30,10 +30,10 @@ "ghcr.io/devcontainers/features/node:1": { "version": "lts" }, - "ghcr.io/tomgrv/devcontainer-features/common-utils:7": { + "ghcr.io/tomgrv/devcontainer-features/common-utils:8": { "utils": "jq dos2unix git-flow" }, - "ghcr.io/tomgrv/devcontainer-features/gitversion:7": {} + "ghcr.io/tomgrv/devcontainer-features/gitversion:8": {} }, "name": "Git Aliases", "version": "8.1.1", diff --git a/src/gitutils/install.sh b/src/gitutils/install.sh index ebd6aa0a..770497ee 100755 --- a/src/gitutils/install.sh +++ b/src/gitutils/install.sh @@ -1,4 +1,43 @@ #!/bin/sh -### Install this feature -zz_feature -i $0 +### Resolve this script's directory as an absolute path, regardless of +### whether $0 was invoked relatively or with an already-absolute path +dir=$(dirname $(readlink -f $0)) + +### Bootstrap the shared zz_* core + this feature's git-* scripts from +### https://github.com/tomgrv/scripts โ€” the scripts formerly kept in bin/ +### here now live there, shared across every tomgrv repo (the same move +### common-utils' install.sh already made). Idempotent: a zz_use already +### on PATH (from common-utils installing first, per this feature's own +### dependsOn) is reused as-is; zz_use itself only fetches/installs +### whatever isn't already present. +if ! command -v zz_use > /dev/null 2>&1; then + _zz_setup_tmp=$(mktemp) || { + echo "install.sh: mktemp failed" >&2 + exit 1 + } + if ! curl -fsSL "${ZZ_SCRIPTS_SETUP_URL:-https://raw.githubusercontent.com/tomgrv/scripts/main/setup.sh}" -o "$_zz_setup_tmp"; then + echo "install.sh: failed to download the zz_use bootstrap" >&2 + rm -f "$_zz_setup_tmp" + exit 1 + fi + sh "$_zz_setup_tmp" + _zz_setup_rc=$? + rm -f "$_zz_setup_tmp" + [ "$_zz_setup_rc" -eq 0 ] || exit "$_zz_setup_rc" +fi +export PATH="${INSTALL_BIN_DIR:-/usr/local/bin}:$PATH" + +zz_use git-align git-autorebase git-co git-degit git-fix git-fix-author \ + git-fix-base git-fix-blanks git-fix-children git-fix-date git-fix-del \ + git-fix-emoji git-fix-last git-fix-lock git-fix-message git-fix-mode \ + git-fix-privacy git-fix-prune git-fix-rights git-fix-secrets git-fix-up \ + git-forall git-getcommit git-integrate git-pick git-release \ + git-release-alpha git-release-beta git-release-hotfix git-release-prod \ + git-unset git-workspaces + +### Run this feature's own installers: git-flow (install-gitflow.sh), +### alias/config wiring (install-config.sh), and stubs/config deployment โ€” +### there's no bin/ left here for it to copy, the git-* scripts above cover +### that now. +zz_feature -i -s $dir diff --git a/src/gitutils/stubs/.github/workflows/release-main.yml b/src/gitutils/stubs/.github/workflows/release-main.yml index 4885af5b..d6983f39 100644 --- a/src/gitutils/stubs/.github/workflows/release-main.yml +++ b/src/gitutils/stubs/.github/workflows/release-main.yml @@ -4,6 +4,11 @@ name: 'Release Main' on: workflow_dispatch: + inputs: + dry_run: + description: 'Run through beta/prod without pushing to main (verification only)' + type: boolean + default: false permissions: contents: write @@ -18,33 +23,8 @@ jobs: fetch-depth: 0 ref: develop - - name: Set up Node.js - uses: tomgrv/actions/setup-node@v2 - - - name: Install GitUtils - # zz_use/install-feature link git-beta/git-prod/etc. onto a bin dir - # that only lasts for *this* step's own shell unless it's also - # appended to $GITHUB_PATH - a later step (Run git beta then git - # prod) otherwise can't find them even though this step's install - # succeeded. Pinning INSTALL_BIN_DIR here keeps both in sync. - env: - INSTALL_BIN_DIR: ${{ runner.temp }}/gitutils-bin - run: | - mkdir -p "$INSTALL_BIN_DIR" - echo "$INSTALL_BIN_DIR" >> "$GITHUB_PATH" - npm exec --yes --legacy-peer-deps --package github:tomgrv/devcontainer-features -- devcontainer-features -- add gitutils - - - name: Verify GitUtils aliases - run: | - git config --global --get alias.beta >/dev/null || { echo 'Missing git beta alias after GitUtils install; check the "Install GitUtils" step logs.'; false; } - git config --global --get alias.prod >/dev/null || { echo 'Missing git prod alias after GitUtils install; check the "Install GitUtils" step logs.'; false; } - - - name: Configure git identity - run: | - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - - - name: Run git beta then git prod - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: git beta && git prod + - name: Release promote + uses: tomgrv/actions/release-promote@v2.22.0 + with: + scripts-ref: v0.3.0 + dry-run: ${{ inputs.dry_run == true }} diff --git a/src/gitutils/stubs/.vscode/tasks.json b/src/gitutils/stubs/.vscode/tasks.json index 163b85b2..a5d8fcff 100755 --- a/src/gitutils/stubs/.vscode/tasks.json +++ b/src/gitutils/stubs/.vscode/tasks.json @@ -19,18 +19,6 @@ "command": "git pf", "problemMatcher": [] }, - { - "label": "๐ŸŽˆ Beta", - "type": "shell", - "command": "git release beta", - "problemMatcher": [] - }, - { - "label": "๐Ÿš€ Prod", - "type": "shell", - "command": "git release prod", - "problemMatcher": [] - }, { "label": "๐Ÿš‘ Hotfix", "type": "shell", diff --git a/src/gitversion/README.md b/src/gitversion/README.md index f65b7ab8..fee059c1 100644 --- a/src/gitversion/README.md +++ b/src/gitversion/README.md @@ -12,7 +12,7 @@ More information about GitVersion can be found on the [GitVersion GitHub page](h ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/gitversion:6": {} + "ghcr.io/tomgrv/devcontainer-features/gitversion:8": {} } ``` diff --git a/src/gitversion/devcontainer-feature.json b/src/gitversion/devcontainer-feature.json index b5ab9269..dbd9ee30 100644 --- a/src/gitversion/devcontainer-feature.json +++ b/src/gitversion/devcontainer-feature.json @@ -10,7 +10,7 @@ "ghcr.io/devcontainers/features/docker-in-docker:3": { "moby": false }, - "ghcr.io/tomgrv/devcontainer-features/common-utils:7": {} + "ghcr.io/tomgrv/devcontainer-features/common-utils:8": {} }, "name": "GitVersion feature", "options": { diff --git a/src/larasets/README.md b/src/larasets/README.md index 7a1991a0..527fc2a9 100644 --- a/src/larasets/README.md +++ b/src/larasets/README.md @@ -8,7 +8,7 @@ This feature provides a set of settings and utilities for working with Laravel p ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/larasets:7": {} + "ghcr.io/tomgrv/devcontainer-features/larasets:8": {} } ``` diff --git a/src/larasets/devcontainer-feature.json b/src/larasets/devcontainer-feature.json index a992c676..9887183d 100644 --- a/src/larasets/devcontainer-feature.json +++ b/src/larasets/devcontainer-feature.json @@ -44,7 +44,7 @@ "version": "8.3" }, "ghcr.io/metcalfc/devcontainer-features/doppler:0": {}, - "ghcr.io/tomgrv/devcontainer-features/common-utils:7": {} + "ghcr.io/tomgrv/devcontainer-features/common-utils:8": {} }, "name": "Laravel settings", "options": { @@ -55,9 +55,7 @@ } }, "version": "8.1.0", - "legacyIds": [ - "larasets" - ], + "legacyIds": ["larasets"], "postCreateCommand": { "config": "zz_feature -c larasets" } diff --git a/src/larasets/stubs/.github/skills/feature-larasets/SKILL.md b/src/larasets/stubs/.github/skills/feature-larasets/SKILL.md index 9970877a..6c44cc6a 100644 --- a/src/larasets/stubs/.github/skills/feature-larasets/SKILL.md +++ b/src/larasets/stubs/.github/skills/feature-larasets/SKILL.md @@ -38,7 +38,6 @@ Use this feature for Laravel-focused developer experience in dev containers, inc ## Do Not Use For -- Generic PHP extension installation (use `pecl`). - Kubernetes/local cluster workflows (use `minikube`). ## Agent Guidance diff --git a/src/minikube/README.md b/src/minikube/README.md index 5cd633dd..43517a13 100644 --- a/src/minikube/README.md +++ b/src/minikube/README.md @@ -12,7 +12,7 @@ More information about Minikube can be found on the [official Minikube GitHub re ```json "features": { - "ghcr.io/tomgrv/devcontainer-features/minikube:6": {} + "ghcr.io/tomgrv/devcontainer-features/minikube:8": {} } ``` diff --git a/src/pecl/README.md b/src/pecl/README.md deleted file mode 100644 index f2584181..00000000 --- a/src/pecl/README.md +++ /dev/null @@ -1,35 +0,0 @@ - - -# PECL Feature - -This feature installs PHP extensions from the [PHP Extension Community Library (PECL)](https://pecl.php.net/). - -## Quick Start โ€” devcontainer.json - -```json -"features": { - "ghcr.io/tomgrv/devcontainer-features/pecl:5": { - "extension": "zip" - } -} -``` - -## Quick Install โ€” console - -```sh -npx tomgrv/devcontainer-features -- add pecl -# or, without node/npm: -curl -fsSL https://raw.githubusercontent.com/tomgrv/devcontainer-features/develop/setup.sh | sh -s -- add pecl -``` - -## Quick Install โ€” npm - -```sh -npm install --save-dev @tomgrv/devcontainer-features-pecl -``` - -## Options - -| Options Id | Description | Type | Default Value | -| ---------- | ------------------------------ | ------ | ------------- | -| extension | The PECL extension to install. | string | zip | diff --git a/src/pecl/devcontainer-feature.json b/src/pecl/devcontainer-feature.json deleted file mode 100644 index 3f89016c..00000000 --- a/src/pecl/devcontainer-feature.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "pecl", - "name": "PECL Install feature", - "description": "Add pecl", - "version": "7.18.0", - "dependsOn": { - "ghcr.io/devcontainers/features/common-utils": {}, - "ghcr.io/devcontainers/features/php:1": { - "version": "8.2" - } - }, - "installsAfter": [ - "ghcr.io/devcontainers/features/common-utils", - "ghcr.io/devcontainers/features/php" - ], - "options": { - "extension": { - "default": "zip", - "description": "The extension to install", - "type": "string" - } - } -} diff --git a/src/pecl/install.sh b/src/pecl/install.sh deleted file mode 100755 index 741e53ab..00000000 --- a/src/pecl/install.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -set -e - -echo "Activating pecl extension '${EXTENSION}'" - -# Get php version -phpVersion=$(php -v | head -n 1 | awk '{print $2}') - -# Install pecl extension -apt-get update && export DEBIAN_FRONTEND=noninteractive && - apt-get -y install --no-install-recommends lib${EXTENSION}-dev - -pecl install ${EXTENSION} && - mkdir -p /usr/local/php/$phpVersion/ini/conf.d && - echo "extension=${EXTENSION}" >>/usr/local/php/$phpVersion/ini/conf.d/${EXTENSION}.ini && - rm -rf /tmp/pear && - apt-get clean -y && rm -rf /var/lib/apt/lists/* diff --git a/src/pecl/package.json b/src/pecl/package.json deleted file mode 100644 index 3deea852..00000000 --- a/src/pecl/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@tomgrv/devcontainer-features-pecl", - "description": "PECL extension installer for PHP development", - "version": "7.18.0", - "files": [ - "install.sh" - ], - "peerDependencies": { - "@tomgrv/devcontainer-features-common-utils": "*" - }, - "peerDependenciesMeta": { - "@tomgrv/devcontainer-features-common-utils": { - "optional": true - } - }, - "scripts": {} -} diff --git a/src/pecl/stubs/.github/skills/feature-pecl/SKILL.md b/src/pecl/stubs/.github/skills/feature-pecl/SKILL.md deleted file mode 100644 index 4f0c4afe..00000000 --- a/src/pecl/stubs/.github/skills/feature-pecl/SKILL.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: feature-pecl -description: Install and manage PHP extensions from PECL within dev containers. ---- - - - -# pecl - -## Description - -Use this feature when an agent needs to install or manage PHP extensions from PECL in the dev container. - -## Commands - -- `pecl install ` - Install a PHP extension from PECL. -- `pecl list` - List installed PECL extensions. -- `pecl info ` - Show metadata/details for an extension. -- `php -m` - List loaded PHP modules to verify extension activation. -- `php --ri ` - Print runtime info for an activated extension. - -## Use For - -- Installing required PHP extensions declared by project dependencies. -- Enabling extension support needed by tests or runtime. -- Standardizing extension setup through feature configuration. - -## Do Not Use For - -- Laravel workflow automation (use `larasets`). -- General system utility setup (use `common-utils`). - -## Agent Guidance - -- Install only extensions required by the project/task. -- Keep extension configuration explicit and reproducible. -- Validate PHP environment compatibility after extension changes.