Last active
October 28, 2025 10:09
-
-
Save Konamiman/77f7858a0267b5bc3d506fad69961654 to your computer and use it in GitHub Desktop.
A script to easily handle multiple WooCommerce branches using git worktrees
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # WooCommerce Worktree Management Script | |
| # By Konamiman (and Claude) | |
| # This script is provided as-is without warranty of any kind. | |
| # It's not officially supported by WooCommerce or Automattic. | |
| # Hint: add it to your ~/.bashrc or equivalent: | |
| # alias wwt='~/woo-worktrees.sh' | |
| set -e | |
| # Adjust these three to your setup. | |
| # Note: the default value of WOO_WP_PLUGINS assumes a ~/plugins symlink | |
| # is in place pointing to the WordPress' plugins directory. | |
| WOO_REPO="$HOME/woocommerce" | |
| WOO_WORKTREES="$HOME/worktrees" | |
| WOO_WP_PLUGIN="$HOME/plugins/woocommerce" | |
| WOO_CORE="$WOO_REPO/plugins/woocommerce" | |
| show_help() { | |
| cat << EOF | |
| WooCommerce Worktree Management | |
| Usage: | |
| woo-worktrees a[dd] <branch> [base] Create a new worktree for the specified branch | |
| woo-worktrees d[el] <branch> [opts] Delete a worktree (fails if uncommitted changes) | |
| woo-worktrees s[et] <branch> Set plugin symlink to worktree | |
| woo-worktrees p[rint] Show currently linked worktree | |
| woo-worktrees r[eset] Reset plugin symlink to main repository | |
| woo-worktrees Show this help | |
| Options: | |
| -f, -force Force delete worktree even with uncommitted changes | |
| -db, --delete-branch Also delete the local branch after removing worktree | |
| Examples: | |
| woo-worktrees add feature-123 # Create worktree for branch feature-123 from current HEAD | |
| woo-worktrees add feature-123 trunk # Create worktree for feature-123 based on trunk branch | |
| woo-worktrees add . # Create worktree for current branch, switch ~/woocommerce to trunk | |
| woo-worktrees add . develop # Create worktree for current branch, switch ~/woocommerce to develop | |
| woo-worktrees set feature-123 # Switch to feature-123 worktree | |
| woo-worktrees print # Show currently linked worktree | |
| woo-worktrees reset # Switch back to main repository | |
| woo-worktrees del feature-123 # Delete feature-123 worktree | |
| woo-worktrees del feature-123 -f # Force delete even with uncommitted changes | |
| woo-worktrees del feature-123 -db # Delete worktree and local branch | |
| woo-worktrees del feature-123 -f -db # Force delete worktree and branch | |
| Alias: wwt <command> <args> | |
| EOF | |
| } | |
| cmd_add() { | |
| local branch="$1" | |
| local base_branch="$2" | |
| # Strip trailing slash (for directory autocompletion) | |
| branch="${branch%/}" | |
| base_branch="${base_branch%/}" | |
| if [ -z "$branch" ]; then | |
| echo "Error: Branch name required" | |
| echo "Usage: woo-worktrees add <branch> [base-branch]" | |
| echo " woo-worktrees add . [base-branch] # Use current branch from ~/woocommerce" | |
| exit 1 | |
| fi | |
| if [ ! -d "$WOO_REPO" ]; then | |
| echo "Error: Main WooCommerce repository not found: $WOO_REPO" | |
| exit 1 | |
| fi | |
| cd "$WOO_REPO" | |
| # Special case: "." means use current branch, then switch to base | |
| if [ "$branch" = "." ]; then | |
| branch=$(git rev-parse --abbrev-ref HEAD) | |
| echo "Current branch: $branch" | |
| # Default to trunk if no base branch specified | |
| if [ -z "$base_branch" ]; then | |
| base_branch="trunk" | |
| fi | |
| echo "Switching ~/woocommerce to: $base_branch" | |
| git checkout "$base_branch" | |
| if [ $? -ne 0 ]; then | |
| echo "Error: Failed to checkout $base_branch" | |
| exit 1 | |
| fi | |
| fi | |
| local worktree_dir="$WOO_WORKTREES/$branch" | |
| if [ -d "$worktree_dir" ]; then | |
| echo "Error: Worktree directory already exists: $worktree_dir" | |
| exit 1 | |
| fi | |
| # Create parent directories if branch name contains slashes (e.g., feature/my-branch) | |
| mkdir -p "$(dirname "$worktree_dir")" | |
| # Check if branch exists locally | |
| local branch_exists_locally=false | |
| if git show-ref --verify --quiet "refs/heads/$branch"; then | |
| branch_exists_locally=true | |
| fi | |
| # Check if branch exists on remote | |
| local branch_exists_remotely=false | |
| if git show-ref --verify --quiet "refs/remotes/origin/$branch"; then | |
| branch_exists_remotely=true | |
| fi | |
| # If base branch is specified, verify it exists and create new branch from it | |
| if [ -n "$base_branch" ]; then | |
| if ! git show-ref --verify --quiet "refs/heads/$base_branch"; then | |
| echo "Error: Base branch '$base_branch' does not exist" | |
| exit 1 | |
| fi | |
| if [ "$branch_exists_locally" = true ]; then | |
| echo "Creating worktree for existing branch: $branch" | |
| git worktree add "$worktree_dir" "$branch" 2>&1 | grep -v "fatal: bad object" | |
| else | |
| echo "Creating worktree with new branch '$branch' from '$base_branch'..." | |
| git worktree add -b "$branch" "$worktree_dir" "$base_branch" 2>&1 | grep -v "fatal: bad object" | |
| fi | |
| else | |
| # No base branch specified | |
| if [ "$branch_exists_locally" = true ]; then | |
| echo "Creating worktree for local branch: $branch" | |
| git worktree add "$worktree_dir" "$branch" 2>&1 | grep -v "fatal: bad object" | |
| elif [ "$branch_exists_remotely" = true ]; then | |
| echo "Creating worktree for remote branch: $branch" | |
| git worktree add --track -b "$branch" "$worktree_dir" "origin/$branch" 2>&1 | grep -v "fatal: bad object" | |
| else | |
| # Branch doesn't exist anywhere, create from current branch | |
| echo "Creating worktree with new branch '$branch' from current branch..." | |
| local current_branch=$(git rev-parse --abbrev-ref HEAD) | |
| git worktree add -b "$branch" "$worktree_dir" "$current_branch" 2>&1 | grep -v "fatal: bad object" | |
| fi | |
| fi | |
| echo "✓ Worktree created: $worktree_dir" | |
| # Create utility symlink for shorter path (especially useful for shell prompt) | |
| # Replace slashes with underscores in symlink name | |
| local symlink_name="_${branch//\//_}" | |
| local symlink_path="$WOO_WORKTREES/$symlink_name" | |
| local symlink_target="$worktree_dir/plugins/woocommerce" | |
| if [ -d "$symlink_target" ]; then | |
| ln -s "$symlink_target" "$symlink_path" | |
| echo "✓ Utility symlink created: $symlink_path" | |
| else | |
| echo "⚠️ Warning: Could not create utility symlink (plugins/woocommerce not found)" | |
| fi | |
| } | |
| cmd_del() { | |
| local branch="$1" | |
| shift | |
| local force=false | |
| local delete_branch=false | |
| # Strip trailing slash (for directory autocompletion) | |
| branch="${branch%/}" | |
| if [ -z "$branch" ]; then | |
| echo "Error: Branch name required" | |
| echo "Usage: woo-worktrees del <branch> [-f] [-db]" | |
| exit 1 | |
| fi | |
| # Parse all flags | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| -f|-force|--force) | |
| force=true | |
| ;; | |
| -db|--delete-branch) | |
| delete_branch=true | |
| ;; | |
| *) | |
| echo "Error: Unknown flag: $1" | |
| echo "Usage: woo-worktrees del <branch> [-f] [-db]" | |
| exit 1 | |
| ;; | |
| esac | |
| shift | |
| done | |
| local worktree_dir="$WOO_WORKTREES/$branch" | |
| local worktree_exists=false | |
| if [ -d "$worktree_dir" ]; then | |
| worktree_exists=true | |
| fi | |
| # If worktree doesn't exist but -db is specified, just delete the branch | |
| if [ "$worktree_exists" = false ]; then | |
| if [ "$delete_branch" = true ]; then | |
| cd "$WOO_REPO" | |
| if git show-ref --verify --quiet "refs/heads/$branch"; then | |
| echo "Worktree does not exist, but branch does. Deleting branch: $branch" | |
| if [ "$force" = true ]; then | |
| git branch -D "$branch" | |
| else | |
| git branch -d "$branch" | |
| fi | |
| echo "✓ Branch deleted: $branch" | |
| # Also remove utility symlink if it exists | |
| local symlink_name="_${branch//\//_}" | |
| local symlink_path="$WOO_WORKTREES/$symlink_name" | |
| if [ -L "$symlink_path" ]; then | |
| rm "$symlink_path" | |
| echo "✓ Utility symlink removed: $symlink_path" | |
| fi | |
| return 0 | |
| else | |
| echo "Error: Neither worktree nor branch exists: $branch" | |
| exit 1 | |
| fi | |
| else | |
| echo "Error: Worktree directory does not exist: $worktree_dir" | |
| exit 1 | |
| fi | |
| fi | |
| # Check for uncommitted changes unless force is enabled | |
| if [ "$force" = false ]; then | |
| echo "Checking for uncommitted changes..." | |
| cd "$worktree_dir" | |
| if [ -n "$(git status --porcelain)" ]; then | |
| echo "Error: Worktree has uncommitted changes:" | |
| git status --short | |
| echo "" | |
| echo "Use -f or -force to delete anyway" | |
| exit 1 | |
| fi | |
| else | |
| echo "⚠️ Force deleting worktree (ignoring uncommitted changes)" | |
| fi | |
| echo "Removing worktree: $branch" | |
| cd "$WOO_REPO" | |
| if [ "$force" = true ]; then | |
| git worktree remove --force "$worktree_dir" | |
| else | |
| git worktree remove "$worktree_dir" | |
| fi | |
| echo "✓ Worktree removed: $worktree_dir" | |
| # Remove utility symlink if it exists | |
| local symlink_name="_${branch//\//_}" | |
| local symlink_path="$WOO_WORKTREES/$symlink_name" | |
| if [ -L "$symlink_path" ]; then | |
| rm "$symlink_path" | |
| echo "✓ Utility symlink removed: $symlink_path" | |
| fi | |
| # Clean up empty parent directories (for branches with slashes like feature/my-branch) | |
| local parent_dir=$(dirname "$worktree_dir") | |
| while [ "$parent_dir" != "$WOO_WORKTREES" ] && [ -d "$parent_dir" ]; do | |
| if [ -z "$(ls -A "$parent_dir")" ]; then | |
| echo "Removing empty directory: $parent_dir" | |
| rmdir "$parent_dir" | |
| parent_dir=$(dirname "$parent_dir") | |
| else | |
| # Directory not empty, stop here | |
| break | |
| fi | |
| done | |
| # Delete the branch if requested | |
| if [ "$delete_branch" = true ]; then | |
| if git show-ref --verify --quiet "refs/heads/$branch"; then | |
| echo "Deleting local branch: $branch" | |
| if [ "$force" = true ]; then | |
| git branch -D "$branch" | |
| else | |
| git branch -d "$branch" | |
| fi | |
| echo "✓ Branch deleted: $branch" | |
| else | |
| echo "⚠️ Branch does not exist: $branch (skipping branch deletion)" | |
| fi | |
| else | |
| # -db not specified, check if branch exists and inform user | |
| if git show-ref --verify --quiet "refs/heads/$branch"; then | |
| echo "ℹ️ Branch '$branch' still exists. Run with -db to delete it." | |
| fi | |
| fi | |
| } | |
| cmd_set() { | |
| local branch="$1" | |
| # Strip trailing slash (for directory autocompletion) | |
| branch="${branch%/}" | |
| if [ -z "$branch" ]; then | |
| echo "Error: Branch name required" | |
| echo "Usage: woo-worktrees set <branch>" | |
| exit 1 | |
| fi | |
| local worktree_dir="$WOO_WORKTREES/$branch" | |
| local target="$worktree_dir/plugins/woocommerce" | |
| if [ ! -d "$target" ]; then | |
| echo "Error: Target directory does not exist: $target" | |
| echo "Make sure the worktree exists and contains plugins/woocommerce" | |
| exit 1 | |
| fi | |
| # Remove existing symlink if it exists | |
| if [ -L "$WOO_WP_PLUGIN" ] || [ -e "$WOO_WP_PLUGIN" ]; then | |
| echo "Removing existing symlink/directory..." | |
| rm -f "$WOO_WP_PLUGIN" | |
| fi | |
| # Create parent directory if needed | |
| mkdir -p "$(dirname "$WOO_WP_PLUGIN")" | |
| ln -s "$target" "$WOO_WP_PLUGIN" | |
| echo "✓ Symlink updated:" | |
| echo " $WOO_WP_PLUGIN -> $target" | |
| } | |
| cmd_print() { | |
| if [ ! -L "$WOO_WP_PLUGIN" ] && [ ! -e "$WOO_WP_PLUGIN" ]; then | |
| echo "No symlink exists at: $WOO_WP_PLUGIN" | |
| exit 1 | |
| fi | |
| if [ ! -L "$WOO_WP_PLUGIN" ]; then | |
| echo "Error: $WOO_WP_PLUGIN exists but is not a symlink" | |
| exit 1 | |
| fi | |
| local target=$(readlink "$WOO_WP_PLUGIN") | |
| echo "Plugin symlink: $WOO_WP_PLUGIN" | |
| echo "Points to: $target" | |
| # Determine if it's main repo or a worktree | |
| if [[ "$target" == "$WOO_CORE" ]]; then | |
| echo "Type: Main repository" | |
| elif [[ "$target" == $WOO_WORKTREES/* ]]; then | |
| # Extract branch name from path | |
| local branch_path="${target#$WOO_WORKTREES/}" | |
| local branch="${branch_path%/plugins/woocommerce}" | |
| echo "Type: Worktree" | |
| echo "Branch: $branch" | |
| else | |
| echo "Type: Unknown (not in expected location)" | |
| fi | |
| } | |
| cmd_reset() { | |
| local target="$WOO_CORE" | |
| if [ ! -d "$target" ]; then | |
| echo "Error: Target directory does not exist: $target" | |
| exit 1 | |
| fi | |
| # Remove existing symlink if it exists | |
| if [ -L "$WOO_WP_PLUGIN" ] || [ -e "$WOO_WP_PLUGIN" ]; then | |
| echo "Removing existing symlink/directory..." | |
| rm -f "$WOO_WP_PLUGIN" | |
| fi | |
| # Create parent directory if needed | |
| mkdir -p "$(dirname "$WOO_WP_PLUGIN")" | |
| ln -s "$target" "$WOO_WP_PLUGIN" | |
| echo "✓ Symlink reset to main repository:" | |
| echo " $WOO_WP_PLUGIN -> $target" | |
| } | |
| # Main script logic | |
| main() { | |
| local command="$1" | |
| shift || true | |
| case "$command" in | |
| a|add) | |
| cmd_add "$@" | |
| ;; | |
| d|del) | |
| cmd_del "$@" | |
| ;; | |
| s|set) | |
| cmd_set "$@" | |
| ;; | |
| p|print) | |
| cmd_print "$@" | |
| ;; | |
| r|reset) | |
| cmd_reset "$@" | |
| ;; | |
| -h|--help|help|"") | |
| show_help | |
| ;; | |
| *) | |
| echo "Error: Unknown command: $command" | |
| echo "" | |
| show_help | |
| exit 1 | |
| ;; | |
| esac | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment