aboutsummaryrefslogtreecommitdiffstats
path: root/pw-check
blob: 0eb8c974bc0fa95ce66d0044f4a0dc905230c9fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Major parts from verify_fixes.sh and verify_signedoff.sh by
# Stephen and Greg. Only integrated the checks into pw-check.
#
# Copyright (C) 2019 Stephen Rothwell <sfr@canb.auug.org.au>
# Copyright (C) 2019 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
# Copyright (C) 2019 Daniel Borkmann <daniel@iogearbox.net>

usage()
{
  cat <<-EOF
  usage: pw-check [-h] [-s START_COMMIT] [-e END_COMMIT]
EOF
  exit
}

split_re='^([Cc][Oo][Mm][Mm][Ii][Tt])?[[:space:]]*([[:xdigit:]]{5,})([[:space:]]*)(.*)$'
nl=$'\n'
tab=$'\t'

strip_spaces()
{
  [[ "$1" =~ ^[[:space:]]*(.*[^[:space:]])[[:space:]]*$ ]]
  echo "${BASH_REMATCH[1]}"
}

verify_fixes()
{
  git_range=$1
  error=0
  commits=$(git rev-list --no-merges -i --grep='^[[:space:]]*Fixes:' "${git_range}")
  if [ -z "$commits" ]; then
    return 0
  fi

  for c in $commits; do
    commit_log=$(git log -1 --format='%h ("%s")' "$c")
    commit_msg="Commit: $commit_log
"
    fixes_lines=$(git log -1 --format='%B' "$c" |
      grep -i '^[[:space:]]*Fixes:')

    while read -r fline; do
      [[ "$fline" =~ ^[[:space:]]*[Ff][Ii][Xx][Ee][Ss]:[[:space:]]*(.*)$ ]]
      f="${BASH_REMATCH[1]}"
      fixes_msg="	Fixes tag: $fline
	Has these problem(s):
"
      sha=
      subject=
      msg=
      if [[ "$f" =~ $split_re ]]; then
        first="${BASH_REMATCH[1]}"
        sha="${BASH_REMATCH[2]}"
        spaces="${BASH_REMATCH[3]}"
        subject="${BASH_REMATCH[4]}"
        if [ "$first" ]; then
          msg="${msg:+${msg}${nl}}${tab}${tab}- leading word '$first' unexpected"
        fi
        if [ -z "$subject" ]; then
          msg="${msg:+${msg}${nl}}${tab}${tab}- missing subject"
        elif [ -z "$spaces" ]; then
          msg="${msg:+${msg}${nl}}${tab}${tab}- missing space between the SHA1 and the subject"
        fi
      else
        printf '%s%s\t\t- %s\n' "$commit_msg" "$fixes_msg" 'No SHA1 recognised'
	commit_msg=''
	error=1
	continue
      fi
      if ! git rev-parse -q --verify "$sha" >/dev/null; then
        printf '%s%s\t\t- %s\n' "$commit_msg" "$fixes_msg" 'Target SHA1 does not exist'
	commit_msg=''
	error=1
	continue
      fi

      if [ "${#sha}" -lt 12 ]; then
        msg="${msg:+${msg}${nl}}${tab}${tab}- SHA1 should be at least 12 digits long${nl}${tab}${tab}  Can be fixed by setting core.abbrev to 12 (or more) or (for git v2.11${nl}${tab}${tab}  or later) just making sure it is not set (or set to \"auto\")."
      fi
      # reduce the subject to the part between () if there
      if [[ "$subject" =~ ^\((.*)\) ]]; then
        subject="${BASH_REMATCH[1]}"
      elif [[ "$subject" =~ ^\((.*) ]]; then
        subject="${BASH_REMATCH[1]}"
        msg="${msg:+${msg}${nl}}${tab}${tab}- Subject has leading but no trailing parentheses"
      fi

      # strip matching quotes at the start and end of the subject
      # the unicode characters in the classes are
      # U+201C LEFT DOUBLE QUOTATION MARK
      # U+201D RIGHT DOUBLE QUOTATION MARK
      # U+2018 LEFT SINGLE QUOTATION MARK
      # U+2019 RIGHT SINGLE QUOTATION MARK
      re1=$'^[\"\u201C](.*)[\"\u201D]$'
      re2=$'^[\'\u2018](.*)[\'\u2019]$'
      re3=$'^[\"\'\u201C\u2018](.*)$'

      if [[ "$subject" =~ $re1 ]]; then
        subject="${BASH_REMATCH[1]}"
      elif [[ "$subject" =~ $re2 ]]; then
        subject="${BASH_REMATCH[1]}"
      elif [[ "$subject" =~ $re3 ]]; then
        subject="${BASH_REMATCH[1]}"
        msg="${msg:+${msg}${nl}}${tab}${tab}- Subject has leading but no trailing quotes"
      fi

      subject=$(strip_spaces "$subject")

      target_subject=$(git log -1 --format='%s' "$sha")
      target_subject=$(strip_spaces "$target_subject")

      # match with ellipses
      case "$subject" in
        *...) subject="${subject%...}"
              target_subject="${target_subject:0:${#subject}}"
              ;;
        ...*) subject="${subject#...}"
              target_subject="${target_subject: -${#subject}}"
              ;;
        *\ ...\ *)
              s1="${subject% ... *}"
              s2="${subject#* ... }"
              subject="$s1 $s2"
              t1="${target_subject:0:${#s1}}"
              t2="${target_subject: -${#s2}}"
              target_subject="$t1 $t2"
              ;;
      esac

      subject=$(strip_spaces "$subject")
      target_subject=$(strip_spaces "$target_subject")

      if [ "$subject" != "${target_subject:0:${#subject}}" ]; then
        msg="${msg:+${msg}${nl}}${tab}${tab}- Subject does not match target commit subject${nl}${tab}${tab}  Just use${nl}${tab}${tab}${tab}git log -1 --format='Fixes: %h (\"%s\")'"
      fi

      lsha=$(git rev-parse -q --verify "$sha")
      if [ -z "$lsha" ]; then
        count=$(git rev-list --count "$sha".."$c")
        if [ "$count" -eq 0 ]; then
          msg="${msg:+${msg}${nl}}${tab}${tab}- Target is not an ancestor of this commit"
        fi
      fi

      if [ "$msg" ]; then
        printf '%s%s%s\n' "$commit_msg" "$fixes_msg" "$msg"
        commit_msg=''
        error=1
      fi
    done <<< "$fixes_lines"
  done

  if [ ${error} -eq 1 ] ; then
    exit 1
  fi
}

verify_signedoff()
{
  git_range=$1
  error=false
  for c in $(git rev-list --no-merges "${git_range}"); do
    ae=$(git log -1 --format='%ae' "$c")
    aE=$(git log -1 --format='%aE' "$c")
    an=$(git log -1 --format='%an' "$c")
    aN=$(git log -1 --format='%aN' "$c")
    ce=$(git log -1 --format='%ce' "$c")
    cE=$(git log -1 --format='%cE' "$c")
    cn=$(git log -1 --format='%cn' "$c")
    cN=$(git log -1 --format='%cN' "$c")
    sob=$(git log -1 --format='%b' "$c" | grep -i '^[[:space:]]*Signed-off-by:')

    am=false
    cm=false
    grep -i -q "<$ae>" <<<"$sob" ||
      grep -i -q "<$aE>" <<<"$sob" ||
      grep -i -q ":[[:space:]]*${an}[[:space:]]*<" <<<"$sob" ||
      grep -i -q ":[[:space:]]*${aN}[[:space:]]*<" <<<"$sob" ||
      am=true
    grep -i -q "<$ce>" <<<"$sob" ||
      grep -i -q "<$cE>" <<<"$sob" ||
      grep -i -q ":[[:space:]]*${cn}[[:space:]]*<" <<<"$sob" ||
      grep -i -q ":[[:space:]]*${cN}[[:space:]]*<" <<<"$sob" ||
      cm=true

    if "$am" || "$cm"; then
      printf "Commit %s\n" "$(git show -s --abbrev-commit --abbrev=12 --pretty=format:"%h (\"%s\")%n" "${c}")"
             "$am" && printf "\tauthor Signed-off-by missing\n"
             "$cm" && printf "\tcommitter Signed-off-by missing\n"
      printf "\tauthor email:    %s\n" "$ae"
      printf "\tcommitter email: %s\n"  "$ce"
      readarray -t s <<< "${sob}"
      printf "\t%s\n" "${s[@]}"
      printf "\n"
      error=true
    fi
  done
  if "$error"; then
    echo "Errors in tree with Signed-off-by, please fix!"
    exit 1
  fi
}

from=""
to=""
while true; do
  case "$1" in
    -s | --start ) from="$2"; shift 2 ;;
    -e | --end ) to="$2"; shift 2 ;;
    -h | --help ) usage; break ;;
    * )  break ;;
  esac
done
[ -z "$from" ] && usage
[ -z "$to" ]  && usage
verify_signedoff "$from..$to"
verify_fixes "$from..$to"
echo "Checked $from -> $to: OK"