1818
1919set -euo pipefail
2020
21+ log () {
22+ echo " ==> $* "
23+ }
24+
25+ error () {
26+ echo " ==> ERROR: $* " >&2
27+ }
28+
29+ fail () {
30+ error " $@ "
31+ exit 1
32+ }
33+
34+ # Catch-all for any command that fails without its own error message.
35+ trap ' error "Command failed (exit $?) at line $LINENO: $BASH_COMMAND"' ERR
36+
2137remote=" ${1:- } "
2238
2339if [[ -z " $remote " ]]; then
2440 read -r -p " Remote (e.g. origin): " remote < /dev/tty
2541fi
2642
2743if [[ -z " $remote " ]]; then
44+ error " Remote is required."
2845 echo " Usage: $0 <remote> [branches]" >&2
2946 exit 1
3047fi
4259fi
4360
4461if ! git remote get-url " $remote " > /dev/null 2>&1 ; then
45- echo " Error: remote '$remote ' does not exist." >&2
46- exit 1
62+ fail " Remote '$remote ' does not exist."
4763fi
4864
4965branches=()
@@ -55,66 +71,97 @@ while IFS= read -r line; do
5571done <<< " $input"
5672
5773if [[ ${# branches[@]} -lt 2 ]]; then
58- echo " Error: at least 2 branches are required." >&2
59- exit 1
74+ fail " At least 2 branches are required, got ${# branches[@]} ."
6075fi
6176
6277for branch in " ${branches[@]} " ; do
6378 if ! git rev-parse --verify --quiet " refs/heads/$branch " > /dev/null; then
64- echo " Error: local branch '$branch ' does not exist." >&2
65- exit 1
79+ fail " Local branch '$branch ' does not exist."
6680 fi
6781done
6882
6983if [[ -n " $( git status --porcelain --untracked-files=no) " ]]; then
70- echo " Error: working tree has uncommitted changes. Commit or stash them first." >&2
71- exit 1
84+ fail " Working tree has uncommitted changes. Commit or stash them first."
7285fi
7386
7487start_branch=" $( git rev-parse --abbrev-ref HEAD) "
88+ branch_count=${# branches[@]}
89+ merge_count=$(( branch_count - 1 ))
7590
7691in_merge () {
7792 git rev-parse --verify --quiet MERGE_HEAD > /dev/null
7893}
7994
80- for (( i = 1 ; i < ${# branches[@]} ; i++ )) ; do
95+ log " Remote: $remote "
96+ log " Merge order: $( printf ' %s -> ' " ${branches[@]} " | sed ' s/ -> $//' ) "
97+
98+ for (( i = 1 ; i < branch_count; i++ )) ; do
8199 source_branch=" ${branches[i - 1]} "
82100 target_branch=" ${branches[i]} "
83101
84102 echo
85- echo " ==> Merging '$source_branch ' into '$target_branch '"
86- git checkout --quiet " $target_branch "
103+ log " [$i /$merge_count ] Checking out '$target_branch '"
104+ if ! git checkout --quiet " $target_branch " ; then
105+ fail " Could not check out '$target_branch '."
106+ fi
87107
108+ if git merge-base --is-ancestor " $source_branch " HEAD; then
109+ log " [$i /$merge_count ] '$target_branch ' already contains '$source_branch ', nothing to merge"
110+ continue
111+ fi
112+
113+ log " [$i /$merge_count ] Merging '$source_branch ' into '$target_branch '"
88114 if git merge --no-edit " $source_branch " ; then
115+ log " [$i /$merge_count ] Merged '$source_branch ' into '$target_branch '"
89116 continue
90117 fi
91118
92119 if ! in_merge; then
93- echo " Error: merge of '$source_branch ' into '$target_branch ' failed." >&2
94- exit 1
120+ fail " Merge of '$source_branch ' into '$target_branch ' failed (see git output above). You are on '$target_branch '."
95121 fi
96122
123+ error " Merge conflicts while merging '$source_branch ' into '$target_branch ':"
124+ git diff --name-only --diff-filter=U | sed ' s/^/ /' >&2
97125 while true ; do
98126 echo
99- echo " Merge conflicts in '$target_branch '. Resolve them and commit the merge."
127+ echo " Resolve the conflicts in '$target_branch ' and commit the merge."
100128 read -r -p " Press Enter to continue..." < /dev/tty
101129
102130 if in_merge; then
103- echo " Merge is still in progress (unresolved conflicts or not committed yet)."
131+ error " Merge is still in progress (unresolved conflicts or not committed yet)."
104132 elif ! git merge-base --is-ancestor " $source_branch " HEAD; then
105- echo " '$source_branch ' is not merged into '$target_branch ' (was the merge aborted?)."
106- echo " Run 'git merge $source_branch ', resolve and commit, or press Ctrl+C to exit."
133+ error " '$source_branch ' is not merged into '$target_branch ' (was the merge aborted?)."
134+ echo " Run 'git merge $source_branch ', resolve and commit, or press Ctrl+C to exit." >&2
107135 else
136+ log " [$i /$merge_count ] Merged '$source_branch ' into '$target_branch ' (conflicts resolved)"
108137 break
109138 fi
110139 done
111140done
112141
113- git checkout --quiet " $start_branch "
114-
115142echo
116- echo " ==> Pushing to '$remote ': ${branches[*]} "
117- git push " $remote " " ${branches[@]} "
143+ log " Switching back to '$start_branch '"
144+ if ! git checkout --quiet " $start_branch " ; then
145+ error " Could not switch back to '$start_branch ', continuing with push."
146+ fi
147+
148+ failed_pushes=()
149+ for (( i = 0 ; i < branch_count; i++ )) ; do
150+ branch=" ${branches[i]} "
151+
152+ echo
153+ log " [$(( i + 1 )) /$branch_count ] Pushing '$branch ' to '$remote '"
154+ if git push --quiet " $remote " " $branch " ; then
155+ log " [$(( i + 1 )) /$branch_count ] Pushed '$branch ' to '$remote '"
156+ else
157+ error " Failed to push '$branch ' to '$remote '."
158+ failed_pushes+=(" $branch " )
159+ fi
160+ done
118161
119162echo
120- echo " ==> Done. Merged and pushed: ${branches[*]} "
163+ if [[ ${# failed_pushes[@]} -gt 0 ]]; then
164+ fail " Merged all branches, but failed to push: ${failed_pushes[*]} "
165+ fi
166+
167+ log " Done. Merged and pushed: ${branches[*]} "
0 commit comments