#!/usr/bin/env bash set -euo pipefail if [ "$#" -lt 2 ]; then echo "Usage: $0 [-m \"commit message\"]" exit 1 fi source_branch="$1" new_branch="$2" shift 2 msg="" while [[ $# -gt 0 ]]; do case "$1" in -m|--message) shift msg="$1" shift ;; *) echo "Unknown option: $1" exit 1 ;; esac done if [ -z "$msg" ]; then echo "Commit message required (-m)" exit 1 fi # check if source branch exists if ! git rev-parse --verify "$source_branch" >/dev/null 2>&1; then echo "Source branch '$source_branch' does not exist" exit 1 fi # check if new branch already exists if git rev-parse --verify "$new_branch" >/dev/null 2>&1; then echo "Branch '$new_branch' already exists" exit 1 fi # check for staged changes if git diff --cached --quiet; then echo "No staged changes to commit" exit 1 fi # 1. save staged diff patchfile=$(mktemp) git diff --cached > "$patchfile" # 2. prepare a temporary index rooted at source branch idx=$(mktemp) GIT_INDEX_FILE="$idx" git read-tree "$source_branch" # 3. apply the staged diff to that index if ! GIT_INDEX_FILE="$idx" git apply --cached --allow-empty "$patchfile"; then echo "Failed to apply staged changes on top of $source_branch" rm -f "$patchfile" "$idx" exit 1 fi # 4. create commit from that temporary index tree=$(GIT_INDEX_FILE="$idx" git write-tree) commit=$(echo "$msg" | GIT_INDEX_FILE="$idx" git commit-tree -p "$source_branch" "$tree") # 5. create branch pointing at the new commit git branch "$new_branch" "$commit" echo "Created branch '$new_branch' from '$source_branch' at commit $commit" rm -f "$patchfile" "$idx" # 6. remove all the staged changes git stash push --staged --quiet git stash drop --quiet