Parent directory

executable_gsrb

3065 bytes
  1#!/bin/zsh
  2set -e
  3
  4function git_main_branch() {
  5	command git rev-parse --git-dir &>/dev/null || return
  6
  7	local remote ref
  8
  9	for ref in refs/{heads,remotes/{origin,upstream}}/{main,trunk,mainline,default,stable,master}; do
 10		if command git show-ref -q --verify $ref; then
 11			echo ${ref:t}
 12			return 0
 13		fi
 14	done
 15
 16	for remote in origin upstream; do
 17		ref=$(command git rev-parse --abbrev-ref $remote/HEAD 2>/dev/null)
 18		if [[ $ref == $remote/* ]]; then
 19			echo ${ref#"$remote/"}; return 0
 20		fi
 21	done
 22
 23	echo master
 24	return 1
 25}
 26
 27interactive=""
 28abort=false
 29push=false
 30while getopts "iap" opt; do
 31	case $opt in
 32		i) interactive="-i" ;;
 33		a) abort=true ;;
 34		p) push=true ;;
 35		*) echo "usage: gsrb [-i] [-a] [-p] [base]" >&2; exit 1 ;;
 36	esac
 37done
 38shift $((OPTIND - 1))
 39
 40gitdir=$(git rev-parse --git-dir)
 41statefile="$gitdir/gsrb-state"
 42
 43if $abort; then
 44	if [ -d "$gitdir/rebase-merge" ] || [ -d "$gitdir/rebase-apply" ]; then
 45		git rebase --abort
 46	fi
 47	rm -f "$statefile"
 48	echo "stack rebase aborted"
 49	exit 0
 50fi
 51
 52if [ -d "$gitdir/rebase-merge" ] || [ -d "$gitdir/rebase-apply" ]; then
 53	echo "rebase in progress — run 'git rebase --continue' or 'gsrb -a' to abort" >&2
 54	exit 1
 55fi
 56
 57typeset -A old_tips
 58remaining=()
 59rebased=()
 60
 61if [ -f "$statefile" ]; then
 62	while IFS=' ' read -r key val; do
 63		case "$key" in
 64			current) current="$val" ;;
 65			prev) prev="$val" ;;
 66			prev_old) prev_old="$val" ;;
 67			*) remaining+=("$key"); old_tips[$key]="$val" ;;
 68		esac
 69	done < "$statefile"
 70	echo "resuming stack: ${remaining[*]}"
 71else
 72	if [ -n "$1" ]; then
 73		main="$1"
 74	else
 75		main=$(git_main_branch)
 76	fi
 77
 78	current=$(git branch --show-current)
 79	merge_base=$(git merge-base HEAD "$main")
 80
 81	# collect branches in stack order (closest to main first)
 82	branches=()
 83	for commit in $(git rev-list --reverse "$merge_base"..HEAD); do
 84		for b in $(git branch --points-at "$commit" --format='%(refname:short)'); do
 85			case "$b" in $main) continue ;; esac
 86			branches+=("$b")
 87		done
 88	done
 89
 90	if (( ${#branches} == 0 )); then
 91		echo "no branches found in stack"
 92		exit 0
 93	fi
 94
 95	echo "stack: ${branches[*]}"
 96
 97	for b in "${branches[@]}"; do
 98		old_tips[$b]=$(git rev-parse "$b")
 99	done
100
101	remaining=("${branches[@]}")
102	prev="$main"
103	prev_old="$merge_base"
104fi
105
106while (( ${#remaining} > 0 )); do
107	b="${remaining[1]}"
108	old="${old_tips[$b]}"
109	remaining=("${remaining[@]:1}")
110
111	# save state for recovery: what's needed after b completes
112	{
113		echo "current $current"
114		echo "prev $b"
115		echo "prev_old $old"
116		for r in "${remaining[@]}"; do
117			echo "$r ${old_tips[$r]}"
118		done
119	} > "$statefile"
120
121	echo "rebasing $b onto $prev"
122	git rebase $interactive --onto "$prev" "$prev_old" "$b"
123
124	if [ -d "$gitdir/rebase-merge" ] || [ -d "$gitdir/rebase-apply" ]; then
125		echo "rebase paused — complete it, then re-run gsrb" >&2
126		exit 1
127	fi
128
129	rebased+=("$b")
130	prev_old="$old"
131	prev="$b"
132done
133
134rm -f "$statefile"
135
136if $push && (( ${#rebased} > 0 )); then
137	echo "pushing ${#rebased} branches"
138	git push --force-with-lease origin "${rebased[@]}"
139fi
140
141if [ -n "$current" ] && [ "$current" != "$b" ]; then
142	git checkout "$current"
143fi