Blob Blame History Raw
#!/bin/bash

usage()
{
    echo "Check whether a given list of commit is available in"
    echo "a given list of branches."
    echo
    echo "Usage: ${0##*/} [branches.conf] term..."
    echo
    echo "Parameters:"
    echo "	branches.conf: file with the list of branches to be checked"
    echo "	               one per line; must be the very first argument"
    echo "	term: hash of the commit|CVE|bsc to be found"
    echo "	-g <pattern>: pattern for grep to filter out branches you are interested in"
    echo "	-h: show this message"
    echo "	-c: show colored results always"
    echo "	-C: show colored results only if stdout is connected to the terminal"
    echo "	--: end of command line options"
    echo
    echo "Environment:"
    echo "	LINUX_GIT: This environment variable should specify the location"
    echo "	           of a clone of the upstream Linux git repository"
}

fetch_branches()
{
    local CACHED_BRANCHES="/tmp/$USER-branches.conf"
    local URL="https://kerncvs.suse.de/branches.conf"
    local EXPIRE=7
    branches=$CACHED_BRANCHES
    if [[ $(find "$CACHED_BRANCHES" -mtime -$EXPIRE -print 2>/dev/null) \
            && -s "$CACHED_BRANCHES" ]]; then
        echo "Using cached $CACHED_BRANCHES" >&2
        return
    fi
    curl "$URL" -o "$CACHED_BRANCHES"
}

setup_colors()
{
    RED_FONT="$(tput setaf 1)$(tput bold)"
    GREEN_FONT="$(tput setaf 2)$(tput bold)"
    YELLOW_FONT="$(tput setaf 3)$(tput bold)"
    MAGENTA_FONT="$(tput setaf 5)$(tput bold)"
    NORMAL_FONT="$(tput sgr0)"
}

if [ $# -lt 1 ] ; then
    usage
    exit 1
fi

[[ -z $LINUX_GIT ]] && echo "LINUX_GIT is unset, therefore I cannot check the base kernel and false negatives are possible!" >&2

# Ideally, this would be under -f option, but for now I'm leaving it here for backwards compatibility.  !!! TODO !!!
branches=$1
if [ ! -f "$branches" ] ; then
    echo "Branches file not specified, trying to fetch it..." >&2
    if ! fetch_branches ; then
        "Error: Can't find the file with the list of branches: $branches nor fetch it"
        exit 1
    fi
else
    shift;
fi

KBC_CHECK_TERMS=()
while [[ $# -gt 0 ]]
do
    case "$1" in
    -h | --h | --he | --hel | --help)
        usage
        exit 0
        ;;
    -g | --g | --gr | --gre | --grep)
        GREP_PATTERN="$2"
        if [[ -z $GREP_PATTERN ]] || [[ $GREP_PATTERN = -- ]]; then
                echo "Pattern must be non-empty!" >&2
                exit 1
        fi
        shift 2
        ;;
    -c | --c | --co | --col | --colo | --color | --colou | --colour)
        setup_colors
        shift 1
        ;;
    -C | --C | --Co | --Col | --Colo | --Color | --Colou | --Colour)
        [[ -t 1 ]] && setup_colors
        shift 1
        ;;
    --)
        shift
        KBC_CHECK_TERMS+=($*)
        shift $#
        ;;
    -*)
        echo "Skipping unrecognized option: $1" >&2
        shift 1
        ;;
    *)
        KBC_CHECK_TERMS+=("$1")
        shift 1
        ;;
    esac
done

term2regex()
{
    shopt -q nocasematch
    local t=$1
    case $t in
        # CVEs first
        2[0-9][0-9][0-9]-*)
            t=cve-$t
            ;&
        cve-*)
            echo "^References:.*$t"
            ;;
        # looks like a hash, look for commits
        [a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]*)
            echo "^Git-commit:.*$t"
            ;;
        # treat rest as a generic reference
        *)
            echo "^References:.*$t"
            ;;
    esac
}

check_with_upstream_tag()
{
    local ret=1
    local line="$(git grep -e SRCVERSION "remotes/origin/$1" -- rpm/config.sh)"
    local tag_suffix="${line##*=}"

    if [[ -n $tag_suffix ]] ; then
        git --git-dir="$LINUX_GIT/.git" merge-base --is-ancestor "$2" "v$tag_suffix"
        ret=$?
    fi

    return $ret
}

declare -A TBL_GREP_PATCHES

check_branch()
{
    local verbose=0
    if [ "$1" = "-v" ] ; then
        verbose=1
        shift
    fi

    local branch="$1"
    git show-ref --verify --quiet "refs/remotes/origin/${branch}" || return 42
    local found=""
    local blacklisted=""
    local missing=""
    local term
    for term in "${KBC_CHECK_TERMS[@]}" ; do
	term_to_search_for="$(term2regex $term)"

	if [[ $term_to_search_for == ^Git-commit* ]] && [[ -n $LINUX_GIT ]] && check_with_upstream_tag "$branch" "$term" ; then
	    found="$found $term"
	    continue
        fi

        local tbl_patches_ret
        local key="${term_to_search_for};${branch}"
        if [[ -z ${TBL_GREP_PATCHES["$key"]} ]]; then
            git grep -qi "$term_to_search_for" "remotes/origin/$branch" -- 'patches.*' 2>/dev/null
            tbl_patches_ret=$?
            TBL_GREP_PATCHES["$key"]=$tbl_patches_ret
        else
            tbl_patches_ret=${TBL_GREP_PATCHES["$key"]}
        fi
        if [ $tbl_patches_ret -eq 0 ] ; then
            found="$found $term"
        else
	    git grep -qi "$term" "remotes/origin/$branch" -- 'blacklist.conf' 2>/dev/null
	    if [ $? -eq 0 ] ; then
		blacklisted="$blacklisted $term"
	    else
		missing="$missing $term"
	    fi
        fi
    done

    # found
    if [ -z "$missing" ] && [ -z "$blacklisted" ] ; then
        return 0
    fi

    # blacklisted
    if [ -z "$found" ] && [ -z "$missing" ] ; then
        return 1
    fi

    # missing
    if [ -z "$found" ] && [ -z "$blacklisted" ] ; then
        return 3
    fi

    # partly
    if [ $verbose -ne 0 ] ; then
	if [ -n "$missing" ] ; then
            echo "        missing hash:"
            local hash
            for hash in $missing ; do
		echo "                $hash"
            done
	fi

	if [ -n "$blacklisted" ] ; then
            echo "        blacklisted hash:"
            local hash
            for hash in $blacklisted ; do
		echo "                $hash"
            done
	fi

        echo
    fi
    return 2
}

check_parents()
{
    local last_branch=""
    local branch
    for branch in "$@" ; do
        check_branch $branch
        case $? in
            0)
                echo "    (found in $branch)"
                return
                ;;
            2)
                echo "    (partly in $branch)"
                return
                ;;
            *)
                ;;
        esac
        last_branch="$branch"
    done

    # not found anywhere
    echo "    (not even in $last_branch)"
}

grep -w build "$branches" | grep -v -E "^(master|vanilla|linux-next|cve)" | \
while read line ; do
    line=${line%%\#*}
    branch=${line%%:*}

    # empty line or comment
    if [ -z "$branch" ] ; then
       continue
    fi

    if [[ -n $GREP_PATTERN ]] && echo "$branch" | grep -q -E -v -- "$GREP_PATTERN"; then
        continue
    fi

    # always check also the _EMBARGO branch as a possible parent
    parents="${branch}_EMBARGO"
    set dummy ${line#$branch:}
    while [ $# -gt 0 ] ; do
        shift
        [[ "$1" =~ "merge:" ]] || continue
        tmp="${1//*merge:-/}"
        parents="$parents ${tmp//*merge:/}"
    done

    printf "%-23s" "$branch"
    check_branch "$branch"

    case $? in
	42)
	    echo "branch does not exist"
	    ;;
        0)
            echo "${GREEN_FONT}<ok>${NORMAL_FONT}"
            ;;
        1)
            echo "${MAGENTA_FONT}<blacklisted>${NORMAL_FONT}"
            ;;
        2)
            echo -n "${YELLOW_FONT}<partly>${NORMAL_FONT} "
            check_parents $parents
            # print missing commits
            check_branch -v "$branch"
            ;;
        *)
            echo -n "${RED_FONT}<missing>${NORMAL_FONT}"
            check_parents "${branch}_EMBARGO" $parents 
    esac
done