summaryrefslogtreecommitdiff
path: root/dev/tools/check-owners.sh
blob: 1a97508abb5e65f0b8ea7c8c41d803d17399b6c9 (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
#!/usr/bin/env bash

# Determine CODEOWNERS of the files given in argument
# For a given commit range:
# git diff --name-only -z COMMIT1 COMMIT2 | xargs -0 dev/tools/check-owners.sh [opts]

# NB: gitignore files will be messed up if you interrupt the script.
# You should be able to just move the .gitignore.bak files back manually.

usage() {
    { echo "usage: $0 [--show-patterns] [--owner OWNER] [FILE]..."
      echo "  --show-patterns: instead of printing file names print the matching patterns (more compact)"
      echo "  --owner: show only files/patterns owned by OWNER (use Nobody to see only non-owned files)"
    } >&2
}

case "$1" in
    "--help"|"-h")
        usage
        if [ $# = 1 ]; then exit 0; else exit 1; fi
esac

if ! [ -e .github/CODEOWNERS ]; then
    >&2 echo "No CODEOWNERS set up or calling from wrong directory."
    exit 1
fi

files=()
show_patterns=false

target_owner=""

while [[ "$#" -gt 0 ]]; do
    case "$1" in
        "--show-patterns")
            show_patterns=true
            shift;;
        "--owner")
            if [[ "$#" = 1 ]]; then
                >&2 echo "Missing argument to --owner"
                usage
                exit 1
            elif [[ "$target_owner" != "" ]]; then
                >&2 echo "Only one --owner allowed"
                usage
                exit 1
            fi
            target_owner="$2"
            shift 2;;
        *)
            files+=("$@")
            break;;
    esac
done

# CODEOWNERS uses .gitignore patterns so we want to use git to parse it
# The only available tool for that is git check-ignore
# However it provides no way to use alternate .gitignore files
# so we rename them temporarily

find . -name .gitignore -print0 | while IFS= read -r -d '' f; do
    if [ -e "$f.bak" ]; then
        >&2 echo "$f.bak exists!"
        exit 1
    else
        mv "$f" "$f.bak"
    fi
done

# CODEOWNERS is not quite .gitignore patterns:
# after the pattern is the owner (space separated)
# git would interpret that as a big pattern containing spaces
# so we create a valid .gitignore by removing all but the first field

while read -r pat _; do
    printf '%s\n' "$pat" >> .gitignore
done < .github/CODEOWNERS

# associative array [file => owner]
declare -A owners

for f in "${files[@]}"; do
    data=$(git check-ignore --verbose --no-index "./$f")
    code=$?

    if [[ "$code" = 1 ]] || ! [[ "$data" =~ .gitignore:.* ]] ; then
        # no match, or match from non tracked gitignore (eg global gitignore)
        if [ "$target_owner" != "" ] && [ "$target_owner" != Nobody ] ; then
            owner=""
        else
            owner="Nobody"
            pat="$f" # no patterns for unowned files
        fi
    else
        # data looks like [.gitignore:$line:$pattern $file]
        # extract the line to look it up in CODEOWNERS
        data=${data#'.gitignore:'}
        line=${data%%:*}

        # NB: supports multiple owners
        # Does not support secondary owners declared in comment
        read -r pat fowners < <(sed "${line}q;d" .github/CODEOWNERS)

        owner=""
        if [ "$target_owner" != "" ]; then
            for o in $fowners; do # do not quote: multiple owners possible
                if [ "$o" = "$target_owner" ]; then
                    owner="$o"
                fi
            done
        else
            owner="$fowners"
        fi
    fi

    if [ "$owner" != "" ]; then
        if $show_patterns; then
            owners[$pat]="$owner"
        else
            owners[$f]="$owner"
        fi
    fi
done

for f in "${!owners[@]}"; do
    printf '%s: %s\n' "$f" "${owners[$f]}"
done | sort -k 2 -k 1 # group by owner

# restore gitignore files
rm .gitignore
find . -name .gitignore.bak -print0 | while IFS= read -r -d '' f; do
    base=${f%.bak}
    if [ -e "$base" ]; then
        >&2 echo "$base exists!"
    else
        mv "$f" "$base"
    fi
done