# GitFlow - Shared Functions

> **Usage:** This file contains reusable functions. Reference with `{{REF:section_name}}` in other files.

---

## DETECT_PLATFORM

Detects the current platform (WSL, Windows, macOS, Linux) and shell environment.
Must be called before any path operations.

```bash
detect_platform() {
  # WSL detection (highest priority - WSL reports as Linux but needs path translation)
  if [ -f /proc/version ] && grep -qi "microsoft\|wsl" /proc/version 2>/dev/null; then
    GF_PLATFORM="wsl"
    GF_SHELL="bash"
  elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "mingw"* ]] || [[ "$OSTYPE" == "cygwin" ]]; then
    GF_PLATFORM="windows"
    GF_SHELL="gitbash"
  elif [[ -n "$WINDIR" ]] || [[ -n "$SystemRoot" ]]; then
    GF_PLATFORM="windows"
    GF_SHELL="powershell"
  elif [[ "$OSTYPE" == "darwin"* ]]; then
    GF_PLATFORM="macos"
    GF_SHELL="zsh"
  elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
    GF_PLATFORM="linux"
    GF_SHELL="bash"
  else
    GF_PLATFORM="unknown"
    GF_SHELL="bash"
  fi
}
```

---

## NORMALIZE_PATH

Path translation functions for cross-platform compatibility (Windows ↔ WSL ↔ POSIX).

```bash
# Translates a path to the format expected by the CURRENT platform
normalize_path_for_platform() {
  local input_path="$1"
  case "$GF_PLATFORM" in
    wsl)
      # Windows path → WSL path: D:\foo\bar or D:/foo/bar → /mnt/d/foo/bar
      if [[ "$input_path" =~ ^[A-Za-z]:[\\/] ]]; then
        local drive=$(echo "${input_path:0:1}" | tr '[:upper:]' '[:lower:]')
        local rest="${input_path:2}"
        rest="${rest//\\//}"
        echo "/mnt/$drive$rest"
      else
        echo "$input_path"
      fi
      ;;
    *)
      # WSL path → Windows path: /mnt/d/foo → D:/foo
      if [[ "$input_path" =~ ^/mnt/([a-z])/(.*) ]]; then
        local drive=$(echo "${BASH_REMATCH[1]}" | tr '[:lower:]' '[:upper:]')
        echo "${drive}:/${BASH_REMATCH[2]}"
      else
        # Normalize backslashes to forward slashes
        echo "${input_path//\\//}"
      fi
      ;;
  esac
}

# Translates a path to storage-neutral format for config.json (forward slashes, drive letter)
# Always converts WSL paths to Windows-style, regardless of current platform
normalize_path_for_storage() {
  local input_path="$1"
  # WSL path → Windows-style: /mnt/d/foo → D:/foo (always, regardless of platform)
  if [[ "$input_path" =~ ^/mnt/([a-z])/(.*) ]]; then
    local drive=$(echo "${BASH_REMATCH[1]}" | tr '[:lower:]' '[:upper:]')
    echo "${drive}:/${BASH_REMATCH[2]}"
  elif [[ "$input_path" =~ ^[A-Za-z]:[\\/] ]]; then
    # Normalize backslashes only
    echo "${input_path//\\//}"
  else
    echo "$input_path"
  fi
}

# Returns current directory in platform-appropriate format
get_current_dir() {
  local raw_pwd
  raw_pwd="$(pwd)"
  normalize_path_for_platform "$raw_pwd"
}
```

---

## DETECT_PROVIDER

Detects GitHub or Azure DevOps from remote URL.

```bash
detect_git_provider() {
  REMOTE_URL=$(git remote get-url origin 2>/dev/null || echo "")

  if [[ "$REMOTE_URL" == *"dev.azure.com"* ]] || [[ "$REMOTE_URL" == *"visualstudio.com"* ]]; then
    GIT_PROVIDER="azuredevops"
    [[ "$REMOTE_URL" =~ dev\.azure\.com/([^/]+)/([^/]+)/_git/([^/]+) ]] && {
      AZURE_ORG="${BASH_REMATCH[1]}"
      AZURE_PROJECT="${BASH_REMATCH[2]}"
      AZURE_REPO="${BASH_REMATCH[3]}"
    }
  elif [[ "$REMOTE_URL" == *"github.com"* ]]; then
    GIT_PROVIDER="github"
  else
    GIT_PROVIDER="unknown"
  fi
}
```

---

## NORMALIZE_NAME

Normalizes a branch name (accents, spaces, special characters).

```bash
normalize_branch_name() {
  local input="$1" max_length="${2:-50}"
  echo "$input" | tr '[:upper:]' '[:lower:]' | \
    sed 'y/àâäéèêëïîôùûüçœæ/aaaeeeeiioouucoa/' | \
    sed "s/[ _']/-/g" | sed 's/[^a-z0-9-]//g' | \
    sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-$max_length | sed 's/-$//'
}
```

---

## RESOLVE_CONFIG_PATH

Resolves the config.json path regardless of current working directory.
Searches in: pwd, develop worktree, then git root.

```bash
resolve_gitflow_config_path() {
  # 1. Try current directory
  [ -f ".claude/gitflow/config.json" ] && { echo ".claude/gitflow/config.json"; return 0; }

  # 2. Try to find develop worktree from git worktree list
  local DEVELOP_PATH
  DEVELOP_PATH=$(git worktree list --porcelain 2>/dev/null | grep -A1 "branch refs/heads/develop" | grep "^worktree " | sed 's/^worktree //')
  [ -n "$DEVELOP_PATH" ] && [ -f "$DEVELOP_PATH/.claude/gitflow/config.json" ] && {
    echo "$DEVELOP_PATH/.claude/gitflow/config.json"
    return 0
  }

  # 3. Try git toplevel
  local GIT_ROOT
  GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
  [ -n "$GIT_ROOT" ] && [ -f "$GIT_ROOT/.claude/gitflow/config.json" ] && {
    echo "$GIT_ROOT/.claude/gitflow/config.json"
    return 0
  }

  return 1
}
```

---

## READ_GITFLOW_CONFIG

Reads GitFlow configuration from config.json. Exposes all variables needed by steps and phases.

```bash
read_gitflow_config() {
  # Detect platform first (needed for path translation)
  detect_platform

  local CONFIG_FILE
  CONFIG_FILE=$(resolve_gitflow_config_path) || {
    echo "❌ GitFlow config not found. Run /gitflow init first."
    return 1
  }

  # Config version
  GF_CONFIG_VERSION=$(grep -oP '"version":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)

  # Repository
  GF_PROJECT_NAME=$(grep -oP '"name":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_ROOT_FOLDER=$(grep -oP '"rootFolder":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_REMOTE_URL=$(grep -oP '"remoteUrl":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)

  # Git provider and branches
  GF_PROVIDER=$(grep -oP '"provider":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_MAIN_BRANCH=$(grep -oP '"main":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_DEVELOP_BRANCH=$(grep -oP '"develop":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)

  # Prefixes
  GF_FEATURE_PREFIX=$(grep -oP '"feature":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_RELEASE_PREFIX=$(grep -oP '"release":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_HOTFIX_PREFIX=$(grep -oP '"hotfix":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)

  # Worktrees
  GF_WORKTREE_MODE=$(grep -oP '"mode":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_MAIN_PATH=$(grep -A1 '"structure"' "$CONFIG_FILE" | grep -oP '"main":\s*"\K[^"]+' | head -1)
  GF_DEVELOP_PATH=$(grep -A2 '"structure"' "$CONFIG_FILE" | grep -oP '"develop":\s*"\K[^"]+' | head -1)
  GF_FEATURES_PATH=$(grep -oP '"features":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_RELEASES_PATH=$(grep -oP '"releases":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_HOTFIXES_PATH=$(grep -oP '"hotfixes":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)

  # Language
  GF_LANG=$(grep -oP '"code":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)

  # Versioning
  GF_VERSION=$(grep -oP '"current":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_TAG_PREFIX=$(grep -oP '"tagPrefix":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)

  # Platform (v2.1.0+, optional)
  GF_CONFIG_PLATFORM=$(grep -oP '"detected":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
  GF_CONFIG_PLATFORM=${GF_CONFIG_PLATFORM:-$GF_PLATFORM}

  # Workspace (v2.1.0+, optional)
  GF_WORKSPACE_PATH=$(grep -A1 '"workspace"' "$CONFIG_FILE" | grep -oP '"path":\s*"\K[^"]+' | head -1)
  GF_WORKSPACE_NAME=$(grep -A2 '"workspace"' "$CONFIG_FILE" | grep -oP '"name":\s*"\K[^"]+' | head -1)

  # Defaults for missing values
  GF_LANG=${GF_LANG:-en}
  GF_WORKTREE_MODE=${GF_WORKTREE_MODE:-organized}
  GF_MAIN_BRANCH=${GF_MAIN_BRANCH:-main}
  GF_DEVELOP_BRANCH=${GF_DEVELOP_BRANCH:-develop}
  GF_FEATURE_PREFIX=${GF_FEATURE_PREFIX:-feature/}
  GF_RELEASE_PREFIX=${GF_RELEASE_PREFIX:-release/}
  GF_HOTFIX_PREFIX=${GF_HOTFIX_PREFIX:-hotfix/}
  GF_TAG_PREFIX=${GF_TAG_PREFIX:-v}
  GF_VERSION=${GF_VERSION:-0.1.0}

  # Translate all stored paths to current platform format
  # This ensures WSL paths are /mnt/d/... and Windows paths use forward slashes
  GF_ROOT_FOLDER=$(normalize_path_for_platform "$GF_ROOT_FOLDER")
  GF_MAIN_PATH=$(normalize_path_for_platform "$GF_MAIN_PATH")
  GF_DEVELOP_PATH=$(normalize_path_for_platform "$GF_DEVELOP_PATH")
  GF_FEATURES_PATH=$(normalize_path_for_platform "$GF_FEATURES_PATH")
  GF_RELEASES_PATH=$(normalize_path_for_platform "$GF_RELEASES_PATH")
  GF_HOTFIXES_PATH=$(normalize_path_for_platform "$GF_HOTFIXES_PATH")
  [ -n "$GF_WORKSPACE_PATH" ] && GF_WORKSPACE_PATH=$(normalize_path_for_platform "$GF_WORKSPACE_PATH")

  # Export config file path for steps that need to update it
  GF_CONFIG_FILE="$CONFIG_FILE"
}
```

---

## DETECT_WORKTREE_MODE

Detects worktree mode. Reads config first, falls back to filesystem detection.

```bash
detect_worktree_mode() {
  # 1. Try config first (authoritative)
  local CONFIG_FILE
  CONFIG_FILE=$(resolve_gitflow_config_path 2>/dev/null)
  if [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
    WORKTREE_MODE=$(grep -oP '"mode":\s*"\K[^"]+' "$CONFIG_FILE" | head -1)
    [ -n "$WORKTREE_MODE" ] && return 0
  fi

  # 2. Fallback: filesystem detection
  if [ -d "../features" ] && [ -d "../releases" ] && [ -d "../hotfixes" ]; then
    WORKTREE_MODE="organized"
  elif [ -d "../01-Main" ] && [ -d "../02-Develop" ]; then
    WORKTREE_MODE="organized"
  elif [ -d "../worktrees" ]; then
    WORKTREE_MODE="adjacent"
  else
    WORKTREE_MODE="organized"
  fi
}
```

---

## CLEANUP_WORKTREE

Cleans up a worktree for a given branch. Uses config paths when available.

```bash
cleanup_worktree_for_branch() {
  local BRANCH=$1 WORKTREE_PATH=""

  # Try config-based paths first (absolute, reliable)
  read_gitflow_config 2>/dev/null
  if [ -n "$GF_FEATURES_PATH" ]; then
    case "$BRANCH" in
      feature/*) WORKTREE_PATH="$GF_FEATURES_PATH/${BRANCH#feature/}" ;;
      release/*) WORKTREE_PATH="$GF_RELEASES_PATH/${BRANCH#release/}" ;;
      hotfix/*)  WORKTREE_PATH="$GF_HOTFIXES_PATH/${BRANCH#hotfix/}" ;;
    esac
  else
    # Fallback: relative paths
    case "$BRANCH" in
      feature/*) WORKTREE_PATH="../features/${BRANCH#feature/}" ;;
      release/*) WORKTREE_PATH="../releases/${BRANCH#release/}" ;;
      hotfix/*)  WORKTREE_PATH="../hotfixes/${BRANCH#hotfix/}" ;;
    esac
  fi

  [ -n "$WORKTREE_PATH" ] && [ -d "$WORKTREE_PATH" ] && {
    git worktree remove "$WORKTREE_PATH" --force 2>/dev/null || rm -rf "$WORKTREE_PATH"
    git worktree prune
    echo "✓ Worktree cleaned: $WORKTREE_PATH"
  }
}
```

---

## REPAIR_WORKTREE_PATHS

Repairs worktree metadata files that contain paths from a different platform (e.g., WSL paths `/mnt/d/...` on Windows, or Windows paths `D:/...` on WSL). Scans `.bare/worktrees/*/gitdir` and the corresponding `{worktree}/.git` files.

```bash
repair_worktree_paths() {
  local BARE_DIR="$1"  # Path to .bare directory
  local WORKTREES_DIR="$BARE_DIR/worktrees"
  [ ! -d "$WORKTREES_DIR" ] && return 0

  local REPAIRED=0
  for wt_dir in "$WORKTREES_DIR"/*/; do
    [ ! -d "$wt_dir" ] && continue
    local wt_name=$(basename "$wt_dir")
    local gitdir_file="$wt_dir/gitdir"
    [ ! -f "$gitdir_file" ] && continue

    # Fix .bare/worktrees/{name}/gitdir
    local stored_path=$(cat "$gitdir_file" | tr -d '\n\r')
    local fixed_path=$(normalize_path_for_platform "$stored_path")
    if [ "$stored_path" != "$fixed_path" ]; then
      echo "$fixed_path" > "$gitdir_file"
      echo "REPAIRED: $wt_name/gitdir → $fixed_path"
      REPAIRED=$((REPAIRED + 1))
    fi

    # Fix the worktree's .git file
    local wt_real_dir=$(dirname "$fixed_path")
    if [ -f "$wt_real_dir/.git" ]; then
      local git_content=$(cat "$wt_real_dir/.git" | tr -d '\n\r')
      local git_path="${git_content#gitdir: }"
      local fixed_git_path=$(normalize_path_for_platform "$git_path")
      if [ "$git_path" != "$fixed_git_path" ]; then
        echo "gitdir: $fixed_git_path" > "$wt_real_dir/.git"
        echo "REPAIRED: $wt_name/.git → $fixed_git_path"
        REPAIRED=$((REPAIRED + 1))
      fi
    fi
  done

  [ "$REPAIRED" -gt 0 ] && echo "Worktree paths: $REPAIRED file(s) repaired"
}
```

---

## RESOLVE_WORKSPACE

Resolves the workspace JSON file by walking up the directory tree.

```bash
resolve_workspace_path() {
  local dir="$(pwd)"
  while [ "$dir" != "/" ] && [ "$dir" != "." ]; do
    [ -f "$dir/.smartstack-workspace.json" ] && {
      echo "$dir/.smartstack-workspace.json"
      return 0
    }
    dir=$(dirname "$dir")
  done
  return 1
}
```

---

## SYNC_REMOTE

Syncs with remote and displays status.

```bash
sync_with_remote() {
  git fetch --all --quiet
  local BRANCH=$(git branch --show-current)
  local AHEAD=$(git rev-list --count origin/$BRANCH..HEAD 2>/dev/null || echo "0")
  local BEHIND=$(git rev-list --count HEAD..origin/$BRANCH 2>/dev/null || echo "0")

  [ "$BEHIND" -gt 0 ] && echo "⚠️ $BRANCH: $BEHIND commit(s) behind"
  [ "$AHEAD" -gt 0 ] && echo "ℹ️ $BRANCH: $AHEAD commit(s) not pushed"
}
```

---

## CONFIG_TEMPLATE

GitFlow configuration JSON template v2.1.0 (aligned with `templates/config.json`).

```json
{
  "version": "2.1.0",
  "platform": {
    "detected": "",
    "shell": "",
    "detectedAt": ""
  },
  "workspace": {
    "path": "",
    "name": ""
  },
  "repository": {
    "name": "",
    "rootFolder": "",
    "nameVariants": {
      "pascalCaseDot": "",
      "pascalCase": "",
      "kebabCase": "",
      "snakeCase": "",
      "displayName": ""
    },
    "defaultBranch": "main",
    "remoteUrl": ""
  },
  "git": {
    "provider": "",
    "branches": {
      "main": "main",
      "develop": "develop"
    },
    "prefixes": {
      "feature": "feature/",
      "release": "release/",
      "hotfix": "hotfix/"
    }
  },
  "worktrees": {
    "enabled": true,
    "mode": "organized",
    "structure": {
      "main": "",
      "develop": "",
      "features": "",
      "releases": "",
      "hotfixes": ""
    }
  },
  "versioning": {
    "strategy": "semver",
    "current": "0.1.0",
    "tagPrefix": "v",
    "sources": ["csproj", "package.json", "VERSION"]
  },
  "efcore": {
    "enabled": true,
    "validateOnCommit": true,
    "blockDestructive": true,
    "migrationNaming": "{context}_v{version}_{sequence}_{Description}"
  },
  "workflow": {
    "push": {
      "afterCommit": "worktree"
    },
    "pr": {
      "autoLabels": true,
      "requireReview": true
    }
  },
  "language": {
    "code": "en"
  }
}
```

**worktrees.mode options:**
- `organized`: Uses absolute paths from config (`structure.features`, etc.)
- `adjacent`: `{rootFolder}/worktrees/{type}/`
- `disabled`: No worktrees

---

## PR_COMMANDS

PR commands by provider.

### GitHub
```bash
gh pr create --title "$TITLE" --body "$BODY" --base "$BASE"
gh pr view $NUMBER --json state,mergeable,reviews
gh pr merge $NUMBER --squash --delete-branch
```

### Azure DevOps
```bash
az repos pr create --title "$TITLE" --description "$BODY" --source-branch "$SOURCE" --target-branch "$BASE"
az repos pr show --id $NUMBER --query "{status:status,mergeStatus:mergeStatus}"
az repos pr update --id $NUMBER --status completed --squash true --delete-source-branch true
```

---

## OUTPUT_FORMAT

Standard output format (replaces ASCII art).

```bash
output_header() { echo -e "\n## $1\n"; }
output_section() { echo "### $1"; }
output_item() { echo "- $1"; }
output_success() { echo "✓ $1"; }
output_warning() { echo "⚠️ $1"; }
output_error() { echo "❌ $1"; }
output_next() { echo "→ Next: $1"; }
```
