Skip to content
Snippets Groups Projects
  • Administrator's avatar
    b2fdee5a
    Fix code owner functionality · b2fdee5a
    Administrator authored
    There was a problem with the script such that in some cases to many code
    owners were informed. When in between the creation of the MR and the
    runtime of the script the master branch of the official repository was
    updated the code owners of the MR changes as well as the code owners of
    the updated code from the official repository wer informed.
    To avoid sending information for code which is already implemented in the
    official repository the local working copy is rebased before extracting the
    changed files.
    
    Fix user name of Sergey Gorbunov in CODEOWNERS file. Sergey has two GitLab
    accounts and the wrong was chosen.
    b2fdee5a
    History
    Fix code owner functionality
    Administrator authored
    There was a problem with the script such that in some cases to many code
    owners were informed. When in between the creation of the MR and the
    runtime of the script the master branch of the official repository was
    updated the code owners of the MR changes as well as the code owners of
    the updated code from the official repository wer informed.
    To avoid sending information for code which is already implemented in the
    official repository the local working copy is rebased before extracting the
    changed files.
    
    Fix user name of Sergey Gorbunov in CODEOWNERS file. Sergey has two GitLab
    accounts and the wrong was chosen.
inform_codeowners.sh 5.76 KiB
#!/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

  # Rebase the MR to extract the changed files properly
  # If upstream/master was changed while the pipline was running one would otherwise get
  # a wrong list of files
  git rebase $UPSTREAM/master
  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 "$@"