git-commit-branch.sh
edited
1#!/usr/bin/env bash
2set -euo pipefail
3
4if [ "$#" -lt 2 ]; then
5 echo "Usage: $0 <source-branch> <new-branch> [-m \"commit message\"]"
6 exit 1
7fi
8
9source_branch="$1"
10new_branch="$2"
11shift 2
12
13msg=""
14while [[ $# -gt 0 ]]; do
15 case "$1" in
16 -m|--message)
17 shift
18 msg="$1"
19 shift
20 ;;
21 *)
22 echo "Unknown option: $1"
23 exit 1
24 ;;
25 esac
26done
27
28if [ -z "$msg" ]; then
29 echo "Commit message required (-m)"
30 exit 1
31fi
32
33# check if source branch exists
34if ! git rev-parse --verify "$source_branch" >/dev/null 2>&1; then
35 echo "Source branch '$source_branch' does not exist"
36 exit 1
37fi
38
39# check if new branch already exists
40if git rev-parse --verify "$new_branch" >/dev/null 2>&1; then
41 echo "Branch '$new_branch' already exists"
42 exit 1
43fi
44
45# check for staged changes
46if git diff --cached --quiet; then
47 echo "No staged changes to commit"
48 exit 1
49fi
50
51# 1. save staged diff
52patchfile=$(mktemp)
53git diff --cached > "$patchfile"
54
55# 2. prepare a temporary index rooted at source branch
56idx=$(mktemp)
57GIT_INDEX_FILE="$idx" git read-tree "$source_branch"
58
59# 3. apply the staged diff to that index
60if ! GIT_INDEX_FILE="$idx" git apply --cached --allow-empty "$patchfile"; then
61 echo "Failed to apply staged changes on top of $source_branch"
62 rm -f "$patchfile" "$idx"
63 exit 1
64fi
65
66# 4. create commit from that temporary index
67tree=$(GIT_INDEX_FILE="$idx" git write-tree)
68commit=$(echo "$msg" | GIT_INDEX_FILE="$idx" git commit-tree -p "$source_branch" "$tree")
69
70# 5. create branch pointing at the new commit
71git branch "$new_branch" "$commit"
72
73echo "Created branch '$new_branch' from '$source_branch' at commit $commit"
74rm -f "$patchfile" "$idx"
75
76# 6. remove all the staged changes
77git stash push --staged --quiet
78git stash drop --quiet