#!/usr/bin/env bash # tbd_branch_create — crea rama TBD desde master/main actualizado # # Uso: # tbd_branch_create issue # tbd_branch_create quick # # Opciones especiales: # --test ejecutar suite de tests internos y salir set -euo pipefail # --------------------------------------------------------------------------- # helpers # --------------------------------------------------------------------------- _tbd_detect_base() { # Retorna 'master' o 'main' — el primero que exista localmente if git show-ref --verify --quiet refs/heads/master 2>/dev/null; then echo "master" elif git show-ref --verify --quiet refs/heads/main 2>/dev/null; then echo "main" else echo "" fi } _tbd_require_git_repo() { if ! git rev-parse --git-dir > /dev/null 2>&1; then echo "ERROR: el directorio actual no es un repositorio git." >&2 return 1 fi } _tbd_pull_rebase() { # Hace pull --rebase solo si hay upstream configurado; si no, es no-op. local has_upstream has_upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || echo "") if [[ -n "$has_upstream" ]]; then git pull --rebase else echo "(sin remote upstream — saltando pull)" fi } # --------------------------------------------------------------------------- # funcion principal # --------------------------------------------------------------------------- tbd_branch_create() { local mode="${1:-}" if [[ -z "$mode" ]] || [[ "$mode" != "issue" && "$mode" != "quick" ]]; then echo "Uso: tbd_branch_create issue " >&2 echo " tbd_branch_create quick " >&2 return 1 fi # Verificar repo git _tbd_require_git_repo # Detectar rama base local base base=$(_tbd_detect_base) if [[ -z "$base" ]]; then echo "ERROR: no se encontro rama 'master' ni 'main' en el repo local." >&2 return 1 fi # Construir nombre de rama segun modo local branch_name if [[ "$mode" == "issue" ]]; then local num="${2:-}" local slug="${3:-}" if [[ -z "$num" || -z "$slug" ]]; then echo "Uso: tbd_branch_create issue " >&2 return 1 fi if [[ ! "$num" =~ ^[0-9]{4}$ ]]; then echo "ERROR: debe ser exactamente 4 digitos numericos (ej: 0042)." >&2 return 1 fi if [[ ! "$slug" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then echo "ERROR: slug debe ser kebab-case ASCII (ej: fix-typo, add-auth)." >&2 return 1 fi branch_name="issue/${num}-${slug}" else # quick local slug="${2:-}" if [[ -z "$slug" ]]; then echo "Uso: tbd_branch_create quick " >&2 return 1 fi if [[ ! "$slug" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then echo "ERROR: slug debe ser kebab-case ASCII (ej: fix-typo, update-readme)." >&2 return 1 fi branch_name="quick/${slug}" fi # Verificar si la rama ya existe if git show-ref --verify --quiet "refs/heads/${branch_name}" 2>/dev/null; then echo "ERROR: la rama '${branch_name}' ya existe localmente." >&2 return 1 fi # Asegurarse de estar en la rama base local current current=$(git branch --show-current) if [[ "$current" != "$base" ]]; then echo "Cambiando a ${base}..." git checkout "$base" fi # Verificar working tree limpio local dirty dirty=$(git status --porcelain) if [[ -n "$dirty" ]]; then echo "ERROR: working tree dirty. Commit o stash los cambios antes de crear la rama." >&2 return 1 fi # Actualizar base desde remote si hay upstream echo "Actualizando ${base}..." _tbd_pull_rebase # Crear la rama git checkout -b "$branch_name" echo "Rama '${branch_name}' creada desde ${base} actualizada." return 0 } # --------------------------------------------------------------------------- # modo --test # --------------------------------------------------------------------------- _tbd_branch_create_tests() { local PASS=0 local FAIL=0 _assert_eq() { local name="$1" expected="$2" got="$3" if [[ "$expected" == "$got" ]]; then echo "PASS: $name" PASS=$((PASS + 1)) else echo "FAIL: $name — expected '${expected}', got '${got}'" FAIL=$((FAIL + 1)) fi } _assert_exit() { local name="$1" expected_exit="$2" shift 2 local got_exit=0 "$@" > /dev/null 2>&1 || got_exit=$? if [[ "$expected_exit" == "$got_exit" ]]; then echo "PASS: $name (exit ${got_exit})" PASS=$((PASS + 1)) else echo "FAIL: $name — expected exit ${expected_exit}, got ${got_exit}" FAIL=$((FAIL + 1)) fi } # --------------------------------------------------------------------------- # Setup: bare remote + repo clonado (master) # --------------------------------------------------------------------------- local tmproot tmproot=$(mktemp -d) local bare="$tmproot/remote.git" local work="$tmproot/work" git -c init.defaultBranch=master init --bare -q "$bare" git clone -q "$bare" "$work" ( cd "$work" git config user.email "test@test.com" git config user.name "Test" echo "init" > README.md git add README.md git commit -q -m "chore: init" git push -q -u origin master ) # --------------------------------------------------------------------------- # Test: rama issue valida # --------------------------------------------------------------------------- ( cd "$work" tbd_branch_create issue 0042 add-auth ) > /dev/null 2>&1 local result result=$(cd "$work" && git branch --show-current) _assert_eq "issue branch created" "issue/0042-add-auth" "$result" # Volver a master (cd "$work" && git checkout -q master) # --------------------------------------------------------------------------- # Test: rama quick valida # --------------------------------------------------------------------------- ( cd "$work" tbd_branch_create quick fix-typo ) > /dev/null 2>&1 result=$(cd "$work" && git branch --show-current) _assert_eq "quick branch created" "quick/fix-typo" "$result" (cd "$work" && git checkout -q master) # --------------------------------------------------------------------------- # Test: numero de issue invalido (3 digitos) # --------------------------------------------------------------------------- _assert_exit "issue number must be 4 digits" 1 bash -c " cd '$work' source '${BASH_SOURCE[0]}' tbd_branch_create issue 042 fix " # --------------------------------------------------------------------------- # Test: slug invalido (mayusculas) # --------------------------------------------------------------------------- _assert_exit "slug must be kebab-case" 1 bash -c " cd '$work' source '${BASH_SOURCE[0]}' tbd_branch_create issue 0001 Fix-Typo " # --------------------------------------------------------------------------- # Test: modo invalido # --------------------------------------------------------------------------- _assert_exit "invalid mode exits 1" 1 bash -c " cd '$work' source '${BASH_SOURCE[0]}' tbd_branch_create hotfix my-slug " # --------------------------------------------------------------------------- # Test: sin argumentos # --------------------------------------------------------------------------- _assert_exit "no args exits 1" 1 bash -c " cd '$work' source '${BASH_SOURCE[0]}' tbd_branch_create " # --------------------------------------------------------------------------- # Test: working tree dirty → error # --------------------------------------------------------------------------- echo "dirty" > "$work/dirty.txt" _assert_exit "dirty tree exits 1" 1 bash -c " cd '$work' source '${BASH_SOURCE[0]}' tbd_branch_create quick clean-slug " rm -f "$work/dirty.txt" # --------------------------------------------------------------------------- # Test: rama ya existe → error # --------------------------------------------------------------------------- (cd "$work" && git checkout -q -b issue/0099-existing && git checkout -q master) _assert_exit "existing branch exits 1" 1 bash -c " cd '$work' source '${BASH_SOURCE[0]}' tbd_branch_create issue 0099 existing " # --------------------------------------------------------------------------- # Test: funciona con 'main' como rama base # --------------------------------------------------------------------------- local bare2="$tmproot/remote2.git" local work2="$tmproot/work2" git -c init.defaultBranch=main init --bare -q "$bare2" git clone -q "$bare2" "$work2" ( cd "$work2" git config user.email "test@test.com" git config user.name "Test" echo "init" > README.md git add README.md git commit -q -m "chore: init" git push -q -u origin main tbd_branch_create quick use-main ) > /dev/null 2>&1 result=$(cd "$work2" && git branch --show-current) _assert_eq "works with main as base" "quick/use-main" "$result" # --------------------------------------------------------------------------- # Cleanup y resultado # --------------------------------------------------------------------------- rm -rf "$tmproot" echo "---" echo "Results: ${PASS} passed, ${FAIL} failed" [[ $FAIL -eq 0 ]] || exit 1 } # --------------------------------------------------------------------------- # entry point # --------------------------------------------------------------------------- if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then if [[ "${1:-}" == "--test" ]]; then _tbd_branch_create_tests else tbd_branch_create "$@" fi fi