#!/usr/bin/env bash # ───────────────────────────────────────────────────────────── # 🌱 Git History Grafting Tool # Attach your rewrite branch to an existing remote's history. # # Author: Owais J # Updated: 2025-11-03 # # Description: # This script grafts a rewrite branch (e.g., refactor) onto # an existing Git remote history, preserving ancestry while # discarding old files. # ───────────────────────────────────────────────────────────── set -euo pipefail C_BLUE=110 # cyan-blue accents C_CYAN=117 # bright cyan C_PURPLE=141 # icy lavender C_GREY=249 # soft gray text C_DIM=244 # dimmer gray C_GREEN=78 # sea green success C_YELLOW=179 # muted yellow warning C_RED=203 # red error BASE_BRANCH="main" REMOTE="" NEW_BRANCH="" NON_INTERACTIVE=false header() { gum style --foreground "$C_BLUE" --bold "🌱 Git History Grafting Tool" gum style --foreground "$C_DIM" "Attach your rewrite branch to an existing remote's history safely." echo } show_help() { SCRIPT_NAME=$(basename "$0") header printf "Usage:\n %s [options]\n\n" "$SCRIPT_NAME" | gum style --foreground "$C_GREY" printf "Options:\n" | gum style --foreground "$C_GREY" --bold { echo "--remote " echo " Git remote to graft onto (e.g. origin)" echo echo "--branch " echo " Your local rewrite branch (e.g. refactor)" echo echo "--base " echo " Base branch on the remote (default: main)" echo echo "--yes, --non-interactive" echo " Skip all confirmations (CI-safe)" echo echo "-h, --help" echo " Show this help message and exit" } | gum style --foreground "$C_DIM" echo printf "Examples:\n" | gum style --foreground "$C_GREY" --bold printf " Interactive mode:\n" | gum style --foreground "$C_DIM" printf " %s\n" "./$SCRIPT_NAME" | gum style --foreground "$C_CYAN" echo printf " Non-interactive (CI):\n" | gum style --foreground "$C_DIM" printf " %s --remote origin --branch refactor --base main --yes\n" "./$SCRIPT_NAME" \ | gum style --foreground "$C_CYAN" echo printf "Theme:\n" | gum style --foreground "$C_GREY" --bold printf " • Iceberg.vim-inspired — subtle cyan, lavender, and gray tones\n" \ | gum style --foreground "$C_PURPLE" echo } while [[ $# -gt 0 ]]; do case "$1" in --remote) REMOTE="$2"; shift 2;; --branch) NEW_BRANCH="$2"; shift 2;; --base) BASE_BRANCH="$2"; shift 2;; --yes|--non-interactive) NON_INTERACTIVE=true; shift;; -h|--help) show_help; exit 0;; -*) echo "Unknown option: $1" >&2; exit 1;; *) shift;; esac done header git rev-parse --is-inside-work-tree >/dev/null if [ -z "$REMOTE" ]; then mapfile -t REMOTES < <(git remote) if [ ${#REMOTES[@]} -eq 0 ]; then gum style --foreground "$C_RED" "❌ No remotes found. Please add one (e.g., git remote add origin ...)." exit 1 fi REMOTE=$(gum choose "${REMOTES[@]}" --header "Select the remote to graft onto:") fi if [ -z "$NEW_BRANCH" ]; then LOCAL_BRANCH=$(git branch --show-current || true) DEFAULT_BRANCH=${LOCAL_BRANCH:-"refactor"} NEW_BRANCH=$(gum input --placeholder "Enter the name of your rewrite branch" --value "$DEFAULT_BRANCH") fi if ! git show-ref --verify --quiet "refs/heads/$NEW_BRANCH"; then gum style --foreground "$C_RED" "❌ Branch '$NEW_BRANCH' not found." exit 1 fi gum style --foreground "$C_BLUE" "Using remote: $REMOTE" gum style --foreground "$C_BLUE" "Using base branch: $REMOTE/$BASE_BRANCH" gum style --foreground "$C_BLUE" "Using rewrite branch: $NEW_BRANCH" if [ "$NON_INTERACTIVE" = false ]; then gum confirm "Proceed to graft '$NEW_BRANCH' onto '$REMOTE/$BASE_BRANCH'?" || exit 0 fi gum spin --spinner line --title "Fetching $REMOTE..." -- git fetch "$REMOTE" "$BASE_BRANCH" gum spin --spinner line --title "Checking out $NEW_BRANCH..." -- git checkout "$NEW_BRANCH" ROOT_COMMIT=$(git rev-list --max-parents=0 HEAD) BASE_COMMIT=$(git rev-parse "$REMOTE/$BASE_BRANCH") { echo -e "Key\tValue" echo -e "$(gum style --foreground "$C_PURPLE" 'Root Commit')\t$ROOT_COMMIT" echo -e "$(gum style --foreground "$C_PURPLE" 'Base Commit')\t$BASE_COMMIT" } | gum table gum spin --spinner minidot --title "Creating temporary graft..." -- \ git replace --graft "$ROOT_COMMIT" "$BASE_COMMIT" gum style --foreground "$C_GREEN" "✓ Graft created. Showing recent commits..." git --no-pager log --oneline --graph --decorate -10 | gum format --theme=dark if command -v git-filter-repo >/dev/null; then gum spin --spinner pulse --title "Rewriting branch '$NEW_BRANCH'..." -- \ git filter-repo --force --refs "refs/heads/$NEW_BRANCH" --preserve-commit-hashes else gum style --foreground "$C_YELLOW" "⚠ git-filter-repo not found; using slower git filter-branch" gum spin --spinner pulse --title "Rewriting history..." -- \ git filter-branch -- --refs "refs/heads/$NEW_BRANCH" fi if git show-ref --quiet "refs/replace/$ROOT_COMMIT"; then git replace -d "$ROOT_COMMIT" fi if [ ! -d .git/refs/remotes ]; then gum style --foreground "$C_DIM" "Restoring remote refs..." git fetch --all fi if [ "$NON_INTERACTIVE" = true ] || gum confirm "Push '$NEW_BRANCH' to $REMOTE?"; then gum spin --spinner line --title "Pushing to $REMOTE..." -- \ git push "$REMOTE" "$NEW_BRANCH" --force-with-lease fi gum style --foreground "$C_GREEN" "✓ Done!" gum style --foreground "$C_DIM" "Your rewrite branch '$NEW_BRANCH' now descends from '$REMOTE/$BASE_BRANCH'."