diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index abc064355eeb2aaaf1f89eead951c9db74df313d..5faeed9da50825a27b1c1adf12e1d8e04f15bd83 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ stages: - checkRepository - checkFormat - build + - finalise - documentation RebaseCheck: @@ -257,6 +258,22 @@ CbmRoot_Merge_realData: - cat Dart.cfg - $PWD/Dart.sh MergeRequest Dart.cfg +InformCodeOwners: + stage: finalise + tags: + - CbmRoot_macosx + only: + refs: + - merge_requests + variables: + - $CI_MERGE_REQUEST_PROJECT_PATH == "computing/cbmroot" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" + script: + # Get the upstream repository manually. I did not find any other way to have it for + # comparison + - scripts/connect_upstream_repo.sh $CI_MERGE_REQUEST_PROJECT_URL + - git fetch upstream + - scripts/inform_codeowners.sh upstream + pages: stage: documentation image: alpine diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000000000000000000000000000000000..49d223de302c6066efd1308a276bd60f08c7a3b8 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,110 @@ +# Ending a path in a `/` will specify the code owners for every file +# nested in that directory, on any level +/ @f.uhlig @v.friese @p.-a.loizeau +/cmake/ @f.uhlig +/external/ @f.uhlig +/scripts/ @f.uhlig @p.-a.loizeau + +# Cleanup and proper separation needed +/MQ/ @f.uhlig @p.-a.loizeau +/fles/ @f.uhlig @p.-a.loizeau +/macro/ @f.uhlig @p.-a.loizeau + +/macro/C2F/ @i.selyuzhenkov +/macro/analysis/ @i.selyuzhenkov +/macro/KF/ @s.gorbunov @v.akishina + +# Core +/core/ @f.uhlig @v.friese @p.-a.loizeau +/core/qa/ @s.gorbunov +/core/data/test/ @f.uhlig +/core/data/raw/ @p.-a.loizeau +/core/base/utils/ @f.uhlig @s.lebedev +/core/base/report/ @s.lebedev +/core/base/draw/ @s.lebedev + +# Simulation +/sim/ @f.uhlig @v.friese @p.-a.loizeau +/sim/transport/geosetup/ @e.lavrik + +# Reco +/reco/ @f.uhlig @v.friese @p.-a.loizeau +/reco/littrack/ @s.lebedev @andrey.lebedev +/reco/L1/ @s.gorbunov @v.akishina +/reco/eventbuilder/ @d.smith +/reco/KF/ @s.gorbunov @v.akishina + +# Analysis +/analysis/ @i.selyuzhenkov +/analysis/PWGC2F/femtoscopy/ @d.wielanek +/analysis/common/ @i.selyuzhenkov +/analysis/PWGHAD/ @i.selyuzhenkov +/analysis/PWGCHA/ @i.selyuzhenkov +/analysis/PWGDIL/ @i.selyuzhenkov +/analysis/PWGDIL/dielectron/pi0eta/ @c.pauly +/analysis/PWGDIL/dielectron/papaframework/ @praisig @ebechtel +/analysis/PWGDIL/dielectron/lmvm/ @i.selyuzhenkov +/analysis/PWGDIL/dielectron/conversion/ @c.pauly +/analysis/PWGDIL/dimuon/ @i.selyuzhenkov + +## Detectors + +# TRD +/core/data/trd/ @praisig +/core/data/test/trd/ @praisig +/core/detectors/trd/ @praisig +/macro/trd/ @praisig +/sim/detectors/trd/ @praisig +/reco/detectors/trd/ @praisig + +# PSD +/analysis/detectors/psd/ @karpushkin_AT_inr.ru +/core/data/psd/ @karpushkin_AT_inr.ru +/core/data/test/psd/ @karpushkin_AT_inr.ru +/core/detectors/psd/ @karpushkin_AT_inr.ru +/macro/psd/ @karpushkin_AT_inr.ru +/sim/detectors/psd/ @karpushkin_AT_inr.ru +/reco/detectors/psd/ @karpushkin_AT_inr.ru + +# MUCH +/core/data/much/ @v.singhal +/core/data/test/much/ @v.singhal +/core/detectors/much/ @v.singhal +/macro/much/ @v.singhal +/sim/detectors/much/ @v.singhal +/reco/detectors/much/ @v.singhal + +# TOF +/analysis/detectors/tof/ @n.herrmann @i.deppner +/core/data/tof/ @n.herrmann @i.deppner +/core/data/test/tof/ @n.herrmann @i.deppner +/core/detectors/tof/ @n.herrmann @i.deppner +/fles/star2019/ @n.herrmann @i.deppner +/macro/tof/ @n.herrmann @i.deppner +/sim/detectors/tof/ @n.herrmann @i.deppner +/reco/detectors/tof/ @n.herrmann @i.deppner +/MQ/eTOF/ @n.herrmann @i.deppner +/MQ/hitbuilder/ @n.herrmann @i.deppner + +# RICH +/core/data/rich/ @s.lebedev +/core/data/test/rich/ @s.lebedev +/core/detectors/rich/ @s.lebedev +/macro/rich/ @s.lebedev +/sim/detectors/rich/ @s.lebedev +/reco/detectors/rich/ @s.lebedev + +# STS +/analysis/detectors/sts/ @v.friese +/core/data/sts/ @v.friese +/core/data/test/sts/ @v.friese +/core/detectors/sts/ @v.friese +/macro/sts/ @v.friese +/sim/detectors/sts/ @v.friese +/reco/detectors/sts/ @v.friese +/MQ/sts/ @v.friese + +# MVD +/core/data/mvd/ @c.muentz @m.deveaux +/macro/mvd/ @c.muentz @m.deveaux +/mvd/ @c.muentz @m.deveaux diff --git a/scripts/inform_codeowners.sh b/scripts/inform_codeowners.sh new file mode 100755 index 0000000000000000000000000000000000000000..4403dd252aebf0c65cff9557ede3768af65e5969 --- /dev/null +++ b/scripts/inform_codeowners.sh @@ -0,0 +1,178 @@ +#!/bin/bash + +main () { + # get passead parameters + # 1. git name of the official repo + if [[ ! $# -eq 1 ]]; then + echo "Pass the name of the upstream repository as argument" + exit 1 + fi + + # Since bash only supports dictionaries from version 4 + # the dictionary is implemented as list. The values in + # the list are the directory (key) and the list of users owning + # this directory separated by a colon. If there is more than one + # user owning a directory the list of users is separated by a + # semicolon. + # An example of such an entry is "/macro/:f.uhlig;v.friese" + declare -a dictionary + declare -a unique_users + + parse_codeowners_file + #declare -p dictionary + + CHANGED_FILES="" + generate_file_list "$1" + + ALL_USERS="" + generate_codeowners_list "$CHANGED_FILES" + + generate_comment +} + + +# Add a key value pair to the dictionary +function add_hash () { + dictionary+=("$1":"$2") +} + +# extract the directory (key) from the passed string +function get_key () { + dir="${1%%:*}" +} + +# extract the user list (value) from the passed string +function get_value () { + users="${1##*:}" +} + +# Parse the CODEOWNERS file and store the information of owned directories +# in a dictionray with the directory as key and the code owners as value +# read a file line by line +# Parse the remaining lines, split them at the first blank +# First entry of the array is the directory, all other entries +# are the list of people to inform +# We have to check that the CODEOWNERS file is properly formated since the +# used parser is very simply +function parse_codeowners_file () { + while read line; do + # exclude commented and empty lines + if [[ $line =~ ^\# || $line =~ ^$ ]]; then + dummy=$line + else + # split the line at blank characters and store the results in an array + IFS=' ' read -ra my_array <<< "$line" + # first entry is the directory + directory="${my_array[0]}" + # in case of two entries the second is the user + if [[ ${#my_array[@]} -eq 2 ]]; then + users="${my_array[1]}" + # in case of more entries the entries from the second to the last are a list of users + # Add the users to a semicolon separated list and store the list in the user array + else + arr2=("${my_array[@]:1:${#my_array[@]}}") + newUserList="" + for i in "${arr2[@]}"; do + newUserList="$newUserList;$i" + done + newUserList="${newUserList:1}" + users="$newUserList" + fi + add_hash "$directory" "$users" + fi + done < CODEOWNERS +} + +function generate_codeowners_list () { + CHANGED_FILES=$1 + all_users="" + for file in $CHANGED_FILES; do +# echo $file + file="/$file" + inform_users="" + longest_match=0 + for entry in "${dictionary[@]}"; do + get_key $entry + if [[ $file =~ ^$dir ]]; then + get_value $entry + # echo "The length of the match is ${#dir}" + if [[ ${#dir} -gt $longest_match ]]; then + inform_users=$users + fi + fi + done +# echo "We have to inform user $inform_users for file $file" + all_users="$all_users;$inform_users" + done + ALL_USERS="${all_users:1}" + + # remove duplications from string + IFS=';' + read -ra users <<< "$ALL_USERS" + + for user in "${users[@]}"; do # access each element of array + if [[ ! " ${unique_users[@]} " =~ " ${user} " ]]; then + unique_users+=("$user") + fi + done + OWNERS_LIST=${unique_users[@]} + + echo "We have to inform the following users about the code ownership: $OWNERS_LIST" +} + +function generate_file_list() { + + if [[ $# -eq 1 ]]; then + UPSTREAM=$1 + else + if [ -z $UPSTREAM ]; then + UPSTREAM=$(git remote -v | grep git.cbm.gsi.de[:/]computing/cbmroot | cut -f1 | uniq) + if [ -z $UPSTREAM ]; then + echo "Error: Name of upstream repository not provided and not found by automatic means" + echo 'Please provide if by checking your remotes with "git remote -v" and exporting UPSTREAM' + echo "or passing as an argument" + exit -1 + fi + fi + fi + echo "Upstream name is :" $UPSTREAM + + BASE_COMMIT=$UPSTREAM/master + CHANGED_FILES=$(git diff --name-only $BASE_COMMIT) +} + +function generate_comment() { + + # Get Information about the MR in JSON format + # Check if the returned string contains the signature that the CodeOwners label is set + # Only create GitLab comment if there is no comment yet (CodeOwners label not set) + MRInfo=$(curl -s --request GET --header "PRIVATE-TOKEN: $COMMENT_TOKEN" "https://git.cbm.gsi.de/api/v4/projects/$CI_MERGE_REQUEST_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID" | grep labels.*\\[.*CodeOwners.*\\]) + if [[ -z $MRInfo ]]; then + + comment="Dear " + for user in "${unique_users[@]}"; do + comment="$comment $user," + done + comment="$comment + +you have been identified as code owner of at least one file which was changed with this merge request. + +Please check the changes and approve them or request changes." + + + # Add a comment to the merge request which informs code owners about their duty to review + curl -s --request POST \ + -d "body=$comment" \ + --header "PRIVATE-TOKEN: $COMMENT_TOKEN" \ + "https://git.cbm.gsi.de/api/v4/projects/$CI_MERGE_REQUEST_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" + + # On first call set a label which can be checked later on to avoid sending the comment over and over again + curl -s --request PUT \ + --header "PRIVATE-TOKEN: $COMMENT_TOKEN" \ + "https://git.cbm.gsi.de/api/v4/projects/$CI_MERGE_REQUEST_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID?add_labels=CodeOwners" + else + echo "Information message already exits, so there is no need to create another one." + fi +} + +main "$@"