blob: d8854bf82aa002dcc456b142004687123a1f8602 [file] [log] [blame]
Willy Tarreau62b71ee2016-05-10 12:04:13 +02001#!/bin/bash
2#
3# Compares multiple branches against a reference and shows which ones contain
4# each commit, and the level of backports since the origin or its own ancestors.
5#
6# Copyright (c) 2016 Willy Tarreau <w@1wt.eu>
7#
8# The purpose is to make it easy to visualize what backports might be missing
9# in a maintenance branch, and to easily spot the ones that are needed and the
10# ones that are not. It solely relies on the "cherry-picked from" tags in the
11# commit messages to find what commit is available where, and can even find a
12# reference commit's ancestor in another branch's commit ancestors as well to
13# detect that the patch is present. When done with the proper references and
14# a correct ordering of the branches, it can be used to quickly apply a set of
15# fixes to a branch since it dumps suggested commands at the end. When doing
16# so it is a good idea to use "HEAD" as the last branch to avoid doing mistakes.
17#
18# Examples :
19# - find what's in master and not in current branch :
20# show-backports -q -m -r master HEAD
21# - find what's in 1.6/master and in hapee-maint-1.5r2 but not in current branch :
22# show-backports -q -m -r 1.6/master hapee-maint-1.5r2 HEAD | grep ' [a-f0-9]\{8\}[-+][0-9] '
23# - check that no recent fix from master is missing in any maintenance branch :
24# show-backports -r master hapee-maint-1.5r2 aloha-7.5 hapee-maint-1.5r1 aloha-7.0
25# - see what was recently merged into 1.6 and has no equivalent in local master :
26# show-backports -q -m -r 1.6/master -b "1.6/master@{1 week ago}" master
27# - check what extra backports are present in hapee-r2 compared to hapee-r1 :
28# show-backports -q -m -r hapee-r2 hapee-r1
29
30
Willy Tarreau29b684b2016-05-16 16:39:38 +020031USAGE="Usage: ${0##*/} [-q] [-m] [-u] [-r reference] [-l logexpr] [-s subject] [-b base] branch [...]"
Willy Tarreau62b71ee2016-05-10 12:04:13 +020032BRANCHES=( )
33REF=master
34BASE=
35QUIET=
36LOGEXPR=
37SUBJECT=
38MISSING=
Willy Tarreau29b684b2016-05-16 16:39:38 +020039UPSTREAM=
Willy Tarreau62b71ee2016-05-10 12:04:13 +020040
41die() {
42 [ "$#" -eq 0 ] || echo "$*" >&2
43 exit 1
44}
45
46err() {
47 echo "$*" >&2
48}
49
50quit() {
51 [ "$#" -eq 0 ] || echo "$*"
52 exit 0
53}
54
55short() {
56 # git rev-parse --short $1
57 echo "${1::8}"
58}
59
60dump_commit_matrix() {
61 title=":$REF:"
62 for branch in "${BRANCHES[@]}"; do
63 #echo -n " $branch"
64 title="$title :${branch}:"
65 done
66 title="$title |"
67
68 count=0
69 # now look up commits
70 while read ref subject; do
71 if [ -n "$MISSING" -a "${subject:0:9}" = "[RELEASE]" ]; then
72 continue
73 fi
74
75 upstream="none"
76 missing=0
Willy Tarreau29b684b2016-05-16 16:39:38 +020077 line=""
Willy Tarreau62b71ee2016-05-10 12:04:13 +020078 for branch in "${BRANCHES[@]}"; do
79 set -- $(grep -m 1 $ref "$WORK/${branch//\//_}")
80 newhash=$1 ; shift
81 # count the number of cherry-picks after this one. Since we shift,
82 # the result is in "$#"
83 while [ -n "$1" -a "$1" != "$ref" ]; do
84 shift
85 done
86 if [ -n "$newhash" ]; then
87 line="${line} $(short $newhash)-$#"
88 else
89 # before giving up we can check if our current commit was
90 # itself cherry-picked and check this again. In order not
91 # to have to do it all the time, we can cache the result
92 # for the current line. If a match is found we report it
93 # with the '+' delimiter instead of '-'.
94 if [ "$upstream" = "none" ]; then
95 upstream=( $(git log -1 --pretty --format=%B "$ref" | \
96 sed -n 's/^commit \([^)]*\) upstream\.$/\1/p;s/^(cherry picked from commit \([^)]*\))/\1/p') )
97 fi
98 newhash=""
99 for h in ${upstream[@]}; do
100 set -- $(grep -m 1 $h "$WORK/${branch//\//_}")
101 newhash=$1 ; shift
102 while [ -n "$1" -a "$1" != "$h" ]; do
103 shift
104 done
105 if [ -n "$newhash" ]; then
106 line="${line} $(short $newhash)+$#"
107 break
108 fi
109 done
110 if [ -z "$newhash" ]; then
111 line="${line} -"
112 missing=1
113 fi
114 fi
115 done
116 line="${line} |"
117 if [ -z "$MISSING" -o $missing -gt 0 ]; then
118 [ $((count++)) -gt 0 ] || echo $title
119 [ "$QUIET" != "" -o $count -lt 20 ] || count=0
Willy Tarreau29b684b2016-05-16 16:39:38 +0200120 if [ -z "$UPSTREAM" -o "$upstream" = "none" -o -z "$upstream" ]; then
121 echo "$(short $ref) $line"
122 else
123 echo "$(short $upstream) $line"
124 fi
Willy Tarreau62b71ee2016-05-10 12:04:13 +0200125 fi
126 done < "$WORK/${REF//\//_}"
127}
128
129while [ -n "$1" -a -z "${1##-*}" ]; do
130 case "$1" in
131 -b) BASE="$2" ; shift 2 ;;
132 -r) REF="$2" ; shift 2 ;;
133 -l) LOGEXPR="$2" ; shift 2 ;;
134 -s) SUBJECT="$2" ; shift 2 ;;
135 -q) QUIET=1 ; shift ;;
136 -m) MISSING=1 ; shift ;;
Willy Tarreau29b684b2016-05-16 16:39:38 +0200137 -u) UPSTREAM=1 ; shift ;;
Willy Tarreau62b71ee2016-05-10 12:04:13 +0200138 -h|--help) quit "$USAGE" ;;
139 *) die "$USAGE" ;;
140 esac
141done
142
143BRANCHES=( "$@" )
144if [ ${#BRANCHES[@]} = 0 ]; then
145 die "$USAGE"
146fi
147
148for branch in "$REF" "${BRANCHES[@]}"; do
149 if ! git rev-parse --verify -q "$branch" >/dev/null; then
150 die "Failed to check git branch $branch."
151 fi
152done
153
154if [ -z "$BASE" ]; then
155 err "Warning! No base specified, looking for common ancestor."
156 BASE=$(git merge-base --all "$REF" "${BRANCHES[@]}")
157 if [ -z "$BASE" ]; then
158 die "Couldn't find a common ancestor between these branches"
159 fi
160fi
161
162# we want to go to the git root dir
163DIR="$PWD"
164cd $(git rev-parse --show-toplevel)
165
166mkdir -p .git/.show-backports #|| die "Can't create .git/.show-backports"
167WORK=.git/.show-backports
168
169rm -f "$WORK/${REF//\//_}"
170git log --reverse ${LOGEXPR:+--grep $LOGEXPR} --pretty="%H %s" "$BASE".."$REF" | grep "${SUBJECT}" > "$WORK/${branch//\//_}" > "$WORK/${REF//\//_}"
171
172# for each branch, enumerate all commits and their ancestry
173for branch in "${BRANCHES[@]}"; do
174 rm -f "$WORK/${branch//\//_}"
175 git log --reverse --pretty="%H %s" "$BASE".."$branch" | grep "${SUBJECT}" | while read h subject; do
176 echo "$h" $(git log -1 --pretty --format=%B "$h" | \
177 sed -n 's/^commit \([^)]*\) upstream\.$/\1/p;s/^(cherry picked from commit \([^)]*\))/\1/p')
178 done > "$WORK/${branch//\//_}"
179done
180
181count=0
182dump_commit_matrix | column -t | \
183(
184 left_commits=( )
185 right_commits=( )
186 while read line; do
187 # append the subject at the end of the line
188 set -- $line
189 echo -n "$line "
190 if [ "${line::1}" = ":" ]; then
191 echo "---- Subject ----"
192 else
193 # doing it this way prevents git from abusing the terminal
194 echo $(git log -1 --pretty="%s" "$1")
195 left_commits[${#left_commits[@]}]="$1"
196 comm=""
197 while [ -n "$1" -a "$1" != "-" -a "$1" != "|" ]; do
198 comm="${1%-*}"
199 shift
200 done
201 right_commits[${#right_commits[@]}]="$comm"
202 fi
203 done
204 if [ -n "$MISSING" -a ${#left_commits[@]} -eq 0 ]; then
205 echo "No missing commit to apply."
206 elif [ -n "$MISSING" ]; then
207 echo
208 echo "In order to apply all leftmost commits to current branch :"
209 echo " git cherry-pick -x ${left_commits[@]}"
210 echo
211 echo "In order to apply all rightmost commits to current branch :"
212 echo " git cherry-pick -x ${right_commits[@]}"
213 fi
214)