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