| #!/bin/bash |
| # SPDX-License-Identifier: GPL-2.0 |
| # |
| # Copyright 2024 Google LLC |
| # |
| # Author: Lee Jones <lee@kernel.org> |
| # |
| # Usage |
| # voting_results [ v6.7.1..v6.7.2 ] |
| # |
| # * Conducts voting system between the present reviewers |
| |
| # set -x # Uncomment to enable debugging |
| |
| # ------- ACTION REQUIRED ------- |
| # Change these to suit your own setup |
| STABLEREMOTE=stable # Whatever you called your Stable remote |
| |
| # Define primary reviewers - other reviewers are determined dynamically |
| declare -a PRIMARY_REVIEWERS=("greg" "lee" "sasha") |
| |
| # Define colors if we're in a terminal |
| if [[ -t 1 ]]; then |
| RED="\e[1;31m" |
| BLUE="\e[1;34m" |
| RESET="\e[0m" |
| else |
| RED="" |
| BLUE="" |
| RESET="" |
| fi |
| |
| print_red() { echo -e "${RED}$*${RESET}"; } |
| print_blue() { echo -e "${BLUE}$*${RESET}"; } |
| |
| function usage() { |
| echo "Usage: $(basename $0) [RANGE]" |
| echo |
| echo "Conducts voting system between reviewers for the specified Git range" |
| echo |
| echo "Arguments:" |
| echo " RANGE Git range in the format 'v6.7.1..v6.7.2'" |
| echo " --help, -h Display this help message and exit" |
| echo |
| echo "Example:" |
| echo " $(basename $0) v6.7.1..v6.7.2" |
| exit 0 |
| } |
| |
| function print_annotations() |
| { |
| oneline="${1}" |
| sha="$(echo ${oneline} | cut -d' ' -f1)" |
| |
| for f in ${ANNOTATEDFILES}; do |
| annotation=$(grep -A1 ${sha} ${PROPOSED}/${f} | tail -n1) |
| if [ "${annotation}" != "" ]; then |
| echo " ${annotation}" |
| fi |
| done |
| } |
| |
| while [ $# -gt 0 ]; do |
| case $1 in |
| *..*) |
| RANGE=${1} |
| ;; |
| --help|-h) |
| usage |
| ;; |
| *) |
| print_red "Unrecognised argument: ${1}" |
| usage |
| ;; |
| esac |
| shift |
| done |
| |
| if [ "${RANGE}" == "" ]; then |
| print_red "Please supply a Git range (e.g v6.7.1..v6.7.2)" |
| usage |
| fi |
| |
| if [ ! -e .git ] || [ ! -f MAINTAINERS ]; then |
| print_red "Not in a kernel directory" |
| exit 1 |
| fi |
| |
| print_blue "Fetching from ${STABLEREMOTE}" |
| git fetch ${STABLEREMOTE} |
| |
| TOP=${RANGE#*..} |
| BOTTOM=${RANGE%..*} |
| SCRIPTDIR=$(dirname ${0}) |
| PROPOSED=${SCRIPTDIR}/../cve/review/proposed |
| |
| # Determine all reviewers dynamically by looking at review files |
| declare -a REVIEWERS=() |
| if [ -d "${PROPOSED}" ]; then |
| # Extract reviewer names from review filenames |
| for file in $(ls ${PROPOSED} | grep -E "${TOP}-[a-z]+$"); do |
| reviewer=${file#*-} |
| # Check if reviewer is not already in array |
| if [[ ! " ${REVIEWERS[*]} " =~ " ${reviewer} " ]]; then |
| REVIEWERS+=("$reviewer") |
| fi |
| done |
| else |
| print_red "Cannot find review directory: ${PROPOSED}" |
| exit 1 |
| fi |
| |
| # If no reviewers found, use primary reviewers as fallback |
| if [ ${#REVIEWERS[@]} -eq 0 ]; then |
| print_red "No reviewers found in review files, using only primary reviewers" |
| REVIEWERS=("${PRIMARY_REVIEWERS[@]}") |
| fi |
| |
| # Create reviewer pattern for grep |
| REVIEWER_PATTERN=$(IFS="|"; echo "${REVIEWERS[*]}") |
| REVIEWFILES=$(ls ${PROPOSED} | grep -E "${TOP}-(${REVIEWER_PATTERN})$") |
| ANNOTATEDFILES=$(ls ${PROPOSED} | grep -E "${TOP}.*-annotated-(${REVIEWER_PATTERN})$") |
| |
| # Identify guest reviewers - anyone who is not a primary reviewer |
| declare -a GUEST_REVIEWERS=() |
| for reviewer in "${REVIEWERS[@]}"; do |
| is_primary=0 |
| for primary in "${PRIMARY_REVIEWERS[@]}"; do |
| if [ "$reviewer" = "$primary" ]; then |
| is_primary=1 |
| break |
| fi |
| done |
| |
| if [ $is_primary -eq 0 ]; then |
| GUEST_REVIEWERS+=("$reviewer") |
| fi |
| done |
| |
| # Display found reviewers for debugging |
| print_blue "Primary reviewers: ${PRIMARY_REVIEWERS[*]}" |
| print_blue "All reviewers found: ${REVIEWERS[*]}" |
| if [ ${#GUEST_REVIEWERS[@]} -gt 0 ]; then |
| print_blue "Guest reviewers: ${GUEST_REVIEWERS[*]}" |
| else |
| print_blue "No guest reviewers found" |
| fi |
| |
| # Initialize arrays for storing commits by vote pattern |
| declare -A commits_by_pattern |
| commits_by_pattern["cve"]="" |
| commits_by_pattern["all"]="" |
| |
| # Initialize vote patterns |
| for reviewer in "${REVIEWERS[@]}"; do |
| commits_by_pattern["$reviewer"]="" |
| done |
| |
| # Two-reviewer combinations for primary reviewers |
| for ((i=0; i<${#PRIMARY_REVIEWERS[@]}; i++)); do |
| for ((j=i+1; j<${#PRIMARY_REVIEWERS[@]}; j++)); do |
| pattern="${PRIMARY_REVIEWERS[i]},${PRIMARY_REVIEWERS[j]}" |
| commits_by_pattern["$pattern"]="" |
| done |
| done |
| |
| # Combinations with guest reviewers |
| for reviewer in "${PRIMARY_REVIEWERS[@]}"; do |
| for guest in "${GUEST_REVIEWERS[@]}"; do |
| pattern="${reviewer},${guest}" |
| commits_by_pattern["$pattern"]="" |
| done |
| done |
| |
| for stablesha in $(git log --format=%h ${BOTTOM}..${TOP}); do |
| mainlinelongsha=$(git --no-pager log -n1 ${stablesha} | grep -i upstream | grep -oE "[a-f0-9]{40,}") || true |
| |
| # If the commit does not contain a Mainline SHA, we'll assume it's Stable only |
| if [ "${mainlinesha}" == "" ]; then |
| mainlinesha=${stablesha} |
| fi |
| |
| oneline=$(git --no-pager log --format="%h %s" -n1 ${mainlinelongsha}) |
| subject=$(echo ${oneline} | cut -d' ' -f 2-) |
| mainlinesha=$(echo ${oneline} | cut -d' ' -f 1) |
| |
| # Track votes using associative array |
| declare -A reviewer_votes |
| total_votes=0 |
| |
| for reviewer in "${REVIEWERS[@]}"; do |
| reviewer_votes["$reviewer"]=0 |
| done |
| |
| # Count votes |
| for f in ${REVIEWFILES}; do |
| reviewer=${f#*-} |
| if grep -qF "${subject}" ${PROPOSED}/${f}; then |
| reviewer_votes["$reviewer"]=1 |
| total_votes=$((total_votes + 1)) |
| fi |
| done |
| |
| # Skip if no votes |
| if [ ${total_votes} -eq 0 ]; then |
| continue |
| fi |
| |
| # Check for CVE |
| found=$(${SCRIPTDIR}/cve_search ${mainlinesha}) |
| found_result=$? |
| if [ "${found_result}" == "0" ]; then |
| commits_by_pattern["cve"]="${commits_by_pattern["cve"]} |
| ${oneline}" |
| continue |
| fi |
| |
| # Check for all reviewers agreeing |
| all_agreed=1 |
| for reviewer in "${REVIEWERS[@]}"; do |
| if [ ${reviewer_votes["$reviewer"]} -eq 0 ]; then |
| all_agreed=0 |
| break |
| fi |
| done |
| |
| if [ ${all_agreed} -eq 1 ]; then |
| commits_by_pattern["all"]="${commits_by_pattern["all"]} |
| ${oneline}" |
| continue |
| fi |
| |
| # Generate vote pattern string and store commit |
| if [ ${total_votes} -eq 1 ]; then |
| # Single reviewer |
| for reviewer in "${REVIEWERS[@]}"; do |
| if [ ${reviewer_votes["$reviewer"]} -eq 1 ]; then |
| commits_by_pattern["$reviewer"]="${commits_by_pattern["$reviewer"]} |
| ${oneline}" |
| fi |
| done |
| else |
| # Check two-reviewer combinations |
| for ((i=0; i<${#REVIEWERS[@]}; i++)); do |
| for ((j=i+1; j<${#REVIEWERS[@]}; j++)); do |
| if [ ${reviewer_votes[${REVIEWERS[i]}]} -eq 1 ] && [ ${reviewer_votes[${REVIEWERS[j]}]} -eq 1 ]; then |
| pattern="${REVIEWERS[i]},${REVIEWERS[j]}" |
| commits_by_pattern["$pattern"]="${commits_by_pattern["$pattern"]} |
| ${oneline}" |
| fi |
| done |
| done |
| fi |
| |
| echo "${oneline}" |
| echo -ne "\tCVE:\t${found_result}\t" |
| for reviewer in "${REVIEWERS[@]}"; do |
| echo -ne "${reviewer^}:\t${reviewer_votes[$reviewer]}\t" |
| done |
| echo |
| done |
| |
| # Print results |
| print_blue "\nAlready assigned a CVE" |
| echo "${commits_by_pattern["cve"]}" | while read -r commit; do |
| if [ ! -z "$commit" ]; then |
| echo " $commit" |
| print_annotations "$commit" |
| fi |
| done |
| |
| print_blue "\nEveryone agrees" |
| echo "${commits_by_pattern["all"]}" | while read -r commit; do |
| if [ ! -z "$commit" ]; then |
| echo " $commit" |
| print_annotations "$commit" |
| fi |
| done |
| |
| # Print primary reviewer combinations |
| for ((i=0; i<${#PRIMARY_REVIEWERS[@]}; i++)); do |
| for ((j=i+1; j<${#PRIMARY_REVIEWERS[@]}; j++)); do |
| pattern="${PRIMARY_REVIEWERS[i]},${PRIMARY_REVIEWERS[j]}" |
| print_blue "\n${PRIMARY_REVIEWERS[i]^} and ${PRIMARY_REVIEWERS[j]^} agree" |
| echo "${commits_by_pattern["$pattern"]}" | while read -r commit; do |
| if [ ! -z "$commit" ]; then |
| echo " $commit" |
| print_annotations "$commit" |
| fi |
| done |
| done |
| done |
| |
| # Print single primary reviewer results |
| for reviewer in "${PRIMARY_REVIEWERS[@]}"; do |
| print_blue "\n${reviewer^} only" |
| echo "${commits_by_pattern["$reviewer"]}" | while read -r commit; do |
| if [ ! -z "$commit" ]; then |
| echo " $commit" |
| print_annotations "$commit" |
| fi |
| done |
| done |
| |
| print_blue "\n------------ GUEST RESULTS BELOW, use for re-review only at this time ----------------" |
| |
| # Print combinations with guest reviewers |
| for reviewer in "${PRIMARY_REVIEWERS[@]}"; do |
| for guest in "${GUEST_REVIEWERS[@]}"; do |
| pattern="${reviewer},${guest}" |
| print_blue "\n${reviewer^} and guest (${guest^}) agree" |
| echo "${commits_by_pattern["$pattern"]}" | while read -r commit; do |
| if [ ! -z "$commit" ]; then |
| echo " $commit" |
| print_annotations "$commit" |
| fi |
| done |
| done |
| done |
| |
| # Print guest-only results - consolidated into a single section |
| print_blue "\nGuest-only results" |
| for guest in "${GUEST_REVIEWERS[@]}"; do |
| print_blue " ${guest^}:" |
| echo "${commits_by_pattern["$guest"]}" | while read -r commit; do |
| if [ ! -z "$commit" ]; then |
| echo " $commit" |
| print_annotations "$commit" |
| fi |
| done |
| done |