pre-commit-src/pre-commit-script.sh
author o.omelyanchuk <o.omelyanchuk@eyeo.com>
Thu, 10 Apr 2025 18:36:27 +0200
changeset 26188 03ee44637e68
parent 25718 68b00816bd6c
permissions -rw-r--r--
Added filter for Uponit ads

#!/bin/bash

# Improve error handling, option e is not picked because it's expected for functions
# to return non-0 statuses.
set -Eeuo pipefail

# Allow user input during commit
exec < /dev/tty

# Allow modifying aliases
shopt -s expand_aliases

templates_content='{}'
templates_names=()
unique_json_files=()
unique_filterlists_to_include=()
all_domains_variables_in_json_files='[]'
all_domains_variables_names='{}'
all_json_files_contents='{}'
variables_in_json_files='{}'
variables_in_included_filterlists='{}'
all_domain_variables_matches_in_filterlists='[]'
last_error=''
unstaged_files=''
testing=false

# When using Windows there is a problem with return character, therefore there is a need for extra --binary option. Look https://jqlang.github.io/jq/manual/
# In general running this script directly within windows is very slow, so use of WSL2 is recommended but in case of some tests performed in windows, those lines are still needed.
if [ $OSTYPE = 'msys' ] || [ $OSTYPE = 'cygwin' ]; then
    alias jq='jq -b'
else
    alias jq='command jq'
fi

# Mac grep doesn't have Perl regex, therefor it needs to be checked with another method
if [[ $OSTYPE == 'darwin'* ]]; then
    alias grep='ggrep'
else
    alias grep='command grep'
fi

# In case on WSL (Windows) it's faster to invoke git.exe directly from Windows (since the files are in the windows FS).
if [ -n "${WSL_DISTRO_NAME:-}" ]; then
    alias git='git.exe'
else
    alias git='command git'
fi

error_handler() {
    local exit_code=$?
    local line_number=$1
    echo "Error: Script failed with exit code $exit_code at line $line_number"
    if [ "${BASH_COMMAND:-}" = "return 1" ]; then
        echo -e "Last error message:\n$last_error"
    else
        echo -e "\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
        echo "THIS ERROR SHOULDN'T HAPPEN, PLEASE REPORT IT TO AFB TEAM OR KRIS"
        echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n"
        echo "Last executed command: ${BASH_COMMAND:-}"
    fi
    if [ "$testing" = true ] && [ "${BASH_COMMAND:-}" = "return 1" ]; then
        exit 0
    else
        exit $exit_code
    fi
}

# Set up trap to catch errors and invoke the error handler
trap 'error_handler $LINENO' ERR

get_unstaged_files() {
    unstaged_files=$(git ls-files . --exclude-standard --others -m)
    local git_exit_code=$?

    if [ $git_exit_code -ne 0 ]; then
        last_error="Wrong setting of git, be sure that you run git as the same user as you cloned repo"
        return 1
    fi

    if [ -n "$unstaged_files" ]; then
        read -p "There are changes not staged for commit. The script will check only the staged version. Do you want to continue? (y/n): " choice
        case "$choice" in 
            y|Y ) echo "";;
            n|N ) echo "Aborting."; exit 1;;
            * ) echo "Invalid choice. Aborting."; exit 1;;
        esac
    fi
}

get_staged_files() {
  local -n staged_files_nameref=$1
  staged_files_nameref=$(git diff --name-only --cached)
}

check_if_jq_is_installed() {
    if ! command -v jq &>/dev/null; then
        last_error="ERROR: jq is not installed. Please install jq to continue."
        return 1
    fi
}

check_if_file_exists() {
    local file_path="$1"
    if ! [ -f "$file_path" ]; then
        last_error="ERROR: File $file_path does not exist"
        return 1
    fi
}

check_if_unstaged_changes() {
    local file_path=$1
    local -n answer_nameref=$2
    check_if_file_exists "$file_path"
    local git_status=$(git status $file_path)
    local lines_indicating_changes=$(grep -s "Changes not staged for commit" <<< $git_status || grep -s "Untracked files:" <<< $git_status || echo '')

    if [ -n "$lines_indicating_changes" ]; then
        answer_nameref=true
    else
        answer_nameref=false
    fi
}

get_staged_version_of_a_file() {
    local file_name="$1"

    # Using name reference in order to not create subshells for each assignment and allow to use global variables
    # and global error handling
    local -n staged_file_content_nameref=$2

    check_if_file_exists "$file_name"
    local matched_unstaged_file=$(grep -x "$file_name" <<< "$unstaged_files" || echo '')

    if [ -n "$matched_unstaged_file" ]; then
        staged_file_content_nameref=$(git show ":$file_name" || echo '')
    else
        staged_file_content_nameref=$(cat "$file_name")
    fi

    if [ -z "$staged_file_content_nameref" ]; then
        last_error="ERROR: File $file_name was requested by a template but it's not tracked neither staged."
        return 1
    fi
}
parse_template_data() {
    local template="$1"
    local -n file_data_nameref=$2
    local staged_template

    get_staged_version_of_a_file "$template" staged_template

    # Extract lines starting with %domainsVariables exceptionrules: and process them with jq
    local json_files_in_template=$(grep "^%domainsVariables exceptionrules:" <<< $staged_template | sed 's/^%domainsVariables exceptionrules://; s/%$//' | jq -Rs 'split("\n") | map(select(length > 0))' || echo '')
    # Extract lines starting with %include exceptionrules: and process them with jq
    local included_filterlists_files_in_template=$(grep "^%include exceptionrules:" <<< $staged_template | sed 's/^%include exceptionrules://; s/%$//' | jq -Rs 'split("\n") | map(select(length > 0))' || echo '')

    if [ "$included_filterlists_files_in_template" = "[]" ]; then
        last_error="ERROR: There is no list included in template $template_name"
        return 1
    fi

    # Create a JSON object with the template name as the key and the extracted domainsVariables and include data as values
    file_data_nameref=$(jq -n --arg template "$template" --argjson json_files_in_template "$json_files_in_template" --argjson included_filterlists_files_in_template "$included_filterlists_files_in_template" '
        {
            $template: {
                "domainsVariables": $json_files_in_template,
                "include": $included_filterlists_files_in_template
            }
        }')
}

update_templates_content() {
    local file_template="$1"
    # Merge the new template data into the existing templates_content JSON
    templates_content=$(jq -n --argjson templates_content "$templates_content" --argjson file_template "$file_template" '$templates_content + $file_template')
}

update_unique_json_files() {
    local template_name="$1"
    local file_data="$2"
    # Extract the list of domainsVariables files from the template data
    local json_files_list=$(jq -r --arg template_name "$template_name" '.[$template_name].domainsVariables[]' <<< $file_data)
    for json_file in $json_files_list; do
        local matches_in_unique_json_files=$(grep -swF "$json_file" <<< ${unique_json_files[@]} || echo '')
        if [ -z "$matches_in_unique_json_files" ]; then
            unique_json_files+=("$json_file")
        fi
    done
}

update_unique_includes() {
    local template_name="$1"
    local file_data="$2"
    # Extract the list of included files from the template data
    local included_files_list=$(jq -r --arg file "$template_name" '.[$file].include[]' <<< $file_data)

    for included_filterlist in $included_files_list; do
        local matches_in_unique_filterlists=$(grep -swF "$included_filterlist" <<< ${unique_filterlists_to_include[@]} || echo '')
        if [ -z "$matches_in_unique_filterlists" ]; then
            unique_filterlists_to_include+=("$included_filterlist")
        fi
    done
}

check_if_valid_json() {
    local json_file_path="$1"
    if ! jq -e . >/dev/null 2>&1 <<< $(cat "$json_file_path"); then
        last_error="ERROR: Invalid JSON content in $json_file_path"
        return 1
    fi
}

get_domains_variables_names_incl_duplicates() {
    local json_file_content="$1"
    local -n domains_variables_names_incl_duplicated_nameref=$2

    # If jq would be used the duplicates would be automatically removed, therefore I used perl
    domains_variables_names_incl_duplicated_nameref=$(perl -0777 -ne 'print "$1\n" while /"([^"]+?)"(?=[\s\r\n]*:)/g' <<< $json_file_content)
}
check_if_correct_domains_variables_json_structure() {
    local json_file_content="$1"
    local json_file_path="$2"
    # Check if the JSON structure is valid and matches the expected format:
    # { "variable1": ["domain1", "domain2" (...)], "variable2": ["domain1", "domain3" (...)], (...)}
    if ! echo $json_file_content | jq -e '
        type == "object" and
        ([keys[] as $k | .[$k] | type == "array" and all(.[]; type == "string")] | all)
        '   >/dev/null 2>&1;
    then
        last_error="ERROR: JSON structure is invalid in $json_file_path"
        return 1
    fi
}

check_if_duplicated_domains_variable_name_in_single_file() {
    local json_file_path="$1"
    local domains_variables_names="$2"
    if [ "$(echo "$domains_variables_names" | sort | uniq -d)" ]; then
        last_error="ERROR: Duplicate domains_variables_names found in $json_file_path"
        return 1
    fi
}

check_if_valid_domains_variable_name() {
    local domains_variable_name="$1"
    if ! [[ "$domains_variable_name" =~ ^[[:alnum:]_]+$ ]]; then
        last_error="ERROR: Invalid domains variable name: $domains_variable_name, only alphanumeric characters and underscores are allowed"
        return 1
    fi
}

check_if_duplicated_domains() {
    local json_file_path="$1"
    local key="$2"
    local value="$3"
    # Value of domainsVariable is an array of strings representing domains,
    # therefore the error message mentions domains.
    if [ -n "$(echo "$value" | sort | uniq -di)" ]; then
        last_error="ERROR: There are duplicated domains in $key in file $json_file_path:\n"
        last_error+="$(echo "$value" | sort | uniq -di)"
        return 1
    fi
}

check_if_correct_domain() {
    local json_file_path="$1"
    local domains_variable_name="$2"
    local domains="$3"
    # Preselect candidates for invalid domains, then check them 
    local candidates=$(grep -vP "^(([A-Za-z0-9][A-Za-z0-9-]{0,61})?[A-Za-z0-9]\.)+([A-Za-z]{2,63}|\*|xn--[a-zA-Z0-9-]{1,59})$" <<< $domains || echo '')
    if [ -n "$candidates" ]; then
      last_error="ERROR: Invalid domain in $domains_variable_name: $candidates in file $json_file_path"
      return 1
    fi
}

check_if_correct_domains_variables() {
    local json_file_path="$1"
    local domains_variables_names="$2"
    local json_file_content="$3"
    check_if_duplicated_domains_variable_name_in_single_file "$json_file_path" "$domains_variables_names"
    for domains_variable_name in $domains_variables_names; do
        check_if_valid_domains_variable_name "$domains_variable_name"
        # Extract the value associated with the domains variable name from the JSON file
        local domains=$(jq -r --arg key "$domains_variable_name" '.[$key][]' <<< $json_file_content)

        check_if_duplicated_domains "$json_file_path" "$domains_variable_name" "$domains"
        check_if_correct_domain "$json_file_path" "$domains_variable_name" "$domains"
    done
}

update_domains_variables_data() {
    local domains_variables_names="$1"
    local json_file_path="$2"
    local json_file_content="$3"

    # Merge the new domains variables into the existing all_domains_variables_in_json_files array
    all_domains_variables_in_json_files=$(jq -n --argjson all_domains_variables_in_json_files "$all_domains_variables_in_json_files" --argjson domains_variables_names "$(echo "$domains_variables_names" | jq -Rs 'split("\n") | map(select(length > 0))')" '$all_domains_variables_in_json_files + $domains_variables_names | unique')

    # Adding all the json files content to the variable was causing "Too big variable" error
    # Therefore, the content is saved to a temporary file and then loaded back to the variable

    all_json_files_temp_file=$(mktemp)
    echo "$all_json_files_contents" > "$all_json_files_temp_file"
    json_file_content_temp=$(mktemp)
    echo "$json_file_content" > "$json_file_content_temp"

    all_json_files_contents=$(jq --arg json_file_path "$json_file_path" --slurpfile json_file_content "$json_file_content_temp" '.[$json_file_path] = $json_file_content[0]' "$all_json_files_temp_file")

    rm "$json_file_content_temp"
    rm "$all_json_files_temp_file"

    # Update the variables_in_json_files object with the keys from the JSON file
    variables_in_json_files=$(echo "$variables_in_json_files" | jq --arg key "$json_file_path" --argjson value "$(echo "$all_json_files_contents" | jq 'to_entries | map({key: .key, value: (.value | keys)}) | from_entries' | jq --arg key "$json_file_path" '.[$key]')" '. * {($key): $value}')
}

check_if_duplicated_domains_variable_name_between_files() {
    local json_file_path="$1"
    local domains_variables_names="$2"
    for domains_variable_name in $domains_variables_names; do

        # Check if the domains variable name exists in the all_domains_variables_in_json_files array
        if jq -e --arg name "$domains_variable_name" 'index($name) != null' <<< $all_domains_variables_in_json_files >/dev/null 2>&1; then
            # If a duplicate is found, identify the file containing the duplicate and report an error
            local file_with_duplicate=$(jq -r --arg name "$domains_variable_name" '
            to_entries | map(select(.value | has($name))) | .[0].key
            ' <<< $all_json_files_contents)
            last_error="ERROR: Duplicate domains variable found in $json_file_path and $file_with_duplicate file: $domains_variable_name"
            return 1
        fi
    done
}

find_domain_variables_syntax_in_filterlist() {
    local filterlist_content="$1"
    local filterlist_path="$2"
    local -n all_lines_with_domain_variables_in_filterlist_nameref=$3
    # Find lines containing domain variables in the filterlist
    # The regex to find them is simpler than in filterlist delivery to also catch domains variables in the wrong place
    # without starting with a complex regex. The full regex is in one of the next steps
    all_lines_with_domain_variables_in_filterlist_nameref=$(grep -P '%<\{.*\}>%' <<< $filterlist_content || echo '')
}

check_for_similar_to_domain_variable() {
    local filterlist_content="$1"
    local filterlist_path="$2"


    # Precheck to quickly gather only the potentiall candidates for the full check
    local weak_candidates=$(grep -P "[%{}<>].\w+.[%{}<>]" <<< $filterlist_content || echo '')
    local candidates=$(grep -P "[%{}<>].?[%{}<>].*[%{}<>].?[%{}<>]" <<< $weak_candidates || echo '')
    local strong_candidates=$(grep -oP "([^%{}<>]|^)[%{}<>]+.?[%{}<>]+.*[%{}<>]+.?[%{}<>]+([^%{}<>]|$)" <<< $candidates || echo '')
    if [ -n "$strong_candidates" ]; then
        local lines_with_similar_to_domain_variable=$(grep -vP "([|,=]|^)%<\{\w+\}>%([,|#]|$)" <<< $candidates || echo '')
        if [ -n "$lines_with_similar_to_domain_variable" ]; then
            last_error="ERROR: Found a line in $filterlist_path that is similar to a domain variable, but it's not a domain variable:\n\n"
            last_error+="$lines_with_similar_to_domain_variable"
            return 1
        fi
    fi
    }

process_filters() {
    local all_lines_with_domain_variables_in_filterlist="$1"
    local -n domains_variables_collected_from_filterlist_nameref="$2"
    domains_variables_collected_from_filterlist_nameref='[]'

    for filter in $all_lines_with_domain_variables_in_filterlist; do
        # Extract the domain variable from the filter
        local domains_variable_match=$(grep -oP '(?<=%<\{).*?(?=\}>%)' <<< $filter)
        if [ "$(echo "$domains_variable_match" | wc -l)" -gt 1 ]; then
            last_error="ERROR: More than 2 domain variables found in filter: $filter"
            return 1
        fi
        # Ensure the domain variable is correctly formatted in the filter
        local true_matches
        true_matches=$(grep -P '(%<{(\w+)}>%(?:,~?[a-zA-Z0-9*.~-]+)*#[?@$]?#)|([,$]domain=(?:[a-zA-Z0-9*.~-]+\|)*%<{(\w+)}>%)' <<< $filter)

        if [ -z "$true_matches" ]; then
            last_error="ERROR: Domain variable added in a wrong way in filter: $filter"
            return 1
        fi
        domains_variables_collected_from_filterlist_nameref=$(jq --arg domains_variable_match "$domains_variable_match" '. + [$domains_variable_match]' <<< $domains_variables_collected_from_filterlist_nameref)
    done
}

update_matches_and_variables() {
    local domains_variables_collected_from_filterlist="$1"
    local file_path="$2"
    
    # Update the list of all domain variable matches in filterlists
    all_domain_variables_matches_in_filterlists=$(jq -n --argjson all_domain_variables_matches_in_filterlists "$all_domain_variables_matches_in_filterlists" --argjson matches "$domains_variables_collected_from_filterlist" '$all_domain_variables_matches_in_filterlists + $matches | unique')

    # Update the variables_in_included_filterlists object with the matches from the current filterlist
    variables_in_included_filterlists=$(jq --arg key "$file_path" --argjson value "$domains_variables_collected_from_filterlist" '.[$key] = $value' <<< $variables_in_included_filterlists)
}

extract_domains_variables_in_included_filterlists() {

    local template_name="$1"
    local -n domains_variables_in_included_filterlists_nameref=$2
    # Extract the list of included filterlists from the template
    local included_filterlists=$(jq -r --arg template_name "$template_name" '.[$template_name].include[]' <<< "$templates_content")
    domains_variables_in_included_filterlists_nameref=()
    for included_filterlist in $included_filterlists; do
        # Extract the domain variables from each included filterlist
        local domains_variables=$(jq -r --arg included_filterlist "$included_filterlist" '.[$included_filterlist][]' <<< "$variables_in_included_filterlists")
        for domain_variable in $domains_variables; do
            domains_variables_in_included_filterlists_nameref+=("$domain_variable")
        done
    done
}

extract_domains_variables_in_included_json_files() {
    local template_name="$1"
    local -n domains_variables_in_included_json_files_nameref=$2

    # Extract the list of included JSON files from the template
    local included_json_files=$(jq -r --arg template_name "$template_name" '.[$template_name].domainsVariables[]' <<< $templates_content)
    domains_variables_in_included_json_files_nameref=()

    for included_json_file in $included_json_files; do
        # Extract the domain variables from each included JSON file
        local domains_variables=$(jq -r --arg key "$included_json_file" '.[$key][]' <<< $variables_in_json_files)
        for domain_variable in $domains_variables; do
            domains_variables_in_included_json_files_nameref+=("$domain_variable")
        done
    done
}

check_domain_variables_in_filterlists() {
    local template_name="$1"
    local domains_variables_in_included_filterlists=()
    local domains_variables_in_included_json_files=()

    # When for example $2 was empty, then the array had one element with empty string
    if [ -n "$2" ]; then
        domains_variables_in_included_filterlists=($2)
    fi
    if [ -n "$3" ]; then
        domains_variables_in_included_json_files=($3)
    fi

    # Extract the list of included filterlists and JSON files from the template
    local included_filterlists=$(jq -r --arg template_name "$template_name" '.[$template_name].include[]' <<< $templates_content)
    local included_json_files=$(jq -r --arg template_name "$template_name" '.[$template_name].domainsVariables[]' <<< $templates_content)

    for domain_variable_in_filterlist in ${domains_variables_in_included_filterlists[@]}; do
        local found=false
        for domain_variable_in_json_file in ${domains_variables_in_included_json_files[@]}; do
            if [ "$domain_variable_in_filterlist" = "$domain_variable_in_json_file" ]; then
                found=true
                break
            fi
        done
        if ! $found; then
            last_error="Error: One of the filterlists:\n\n"
            last_error+="$included_filterlists\n\n"
            last_error+="included in the template $template_name contain a domain variable $domain_variable_in_filterlist "
            last_error+="which wasn't found in any of the domains variables files included in that template:\n\n"
            last_error+="$included_json_files"
            return 1
        fi
    done
}

check_if_domains_variables_are_identical_in_lists_and_jsons() {
    if [ "$all_domains_variables_in_json_files" != "$all_domain_variables_matches_in_filterlists" ]; then
        last_error="Error: the domain variables in domain-variables file and the filter list are not the same\n"
        last_error+="Extra variables in domain-variables files:\n"
        last_error+="$(jq -n --argjson all_domains_variables_in_json_files "$all_domains_variables_in_json_files" --argjson all_domain_variables_matches_in_filterlists "$all_domain_variables_matches_in_filterlists" '$all_domains_variables_in_json_files - $all_domain_variables_matches_in_filterlists')\n"
        last_error+="Extra variables in filter lists:\n"
        last_error+=$(jq -n --argjson all_domains_variables_in_json_files "$all_domains_variables_in_json_files" --argjson all_domain_variables_matches_in_filterlists "$all_domain_variables_matches_in_filterlists" '$all_domain_variables_matches_in_filterlists - $all_domains_variables_in_json_files')
        return 1
    fi
}


main() {
    get_unstaged_files
    check_if_jq_is_installed
    for template_name in *.txt; do
        templates_names+=("$template_name")
        # To avoid creating a subshell, the variable is passed as a reference to parse_template_data function
        # That helps with the error handling and allows to use global variables
        local file_data
        # Parse data from the template
        parse_template_data "$template_name" file_data
        # Update the templates_content JSON with the data from the file
        update_templates_content "$file_data"

        update_unique_json_files "$template_name" "$file_data"
        update_unique_includes "$template_name" "$file_data"
    done

    for domains_variables_path in ${unique_json_files[@]}; do
        local staged_domains_variables_file
        get_staged_version_of_a_file "$domains_variables_path" staged_domains_variables_file
        check_if_correct_domains_variables_json_structure "$staged_domains_variables_file" "$domains_variables_path"
        local domains_variables_names_incl_duplicates
        get_domains_variables_names_incl_duplicates "$staged_domains_variables_file" domains_variables_names_incl_duplicates

        check_if_correct_domains_variables "$domains_variables_path" "$domains_variables_names_incl_duplicates" "$staged_domains_variables_file"

        check_if_duplicated_domains_variable_name_between_files "$domains_variables_path" "$domains_variables_names_incl_duplicates"
        update_domains_variables_data "$domains_variables_names_incl_duplicates" "$domains_variables_path" "$staged_domains_variables_file"
    done

    for filterlist_path in ${unique_filterlists_to_include[@]}; do
        local filterlist_content
        get_staged_version_of_a_file "$filterlist_path" filterlist_content

        check_for_similar_to_domain_variable  "$filterlist_content" "$filterlist_path"
        local all_lines_with_domain_variables_in_filterlist
        find_domain_variables_syntax_in_filterlist "$filterlist_content" "$filterlist_path" all_lines_with_domain_variables_in_filterlist

        if [ -z "$all_lines_with_domain_variables_in_filterlist" ]; then
            # In case of lack of matches, the value of all_lines_with_domain_variables_in_filterlist should have just
            # a message to show.
            local domains_variables_collected_from_filterlist='[]'
        else
            local domains_variables_collected_from_filterlist
            process_filters "$all_lines_with_domain_variables_in_filterlist" domains_variables_collected_from_filterlist
        fi

        update_matches_and_variables "$domains_variables_collected_from_filterlist" "$filterlist_path"
    done

    local domains_variables_in_included_filterlists=()
    for template_name in ${templates_names[@]}; do
        extract_domains_variables_in_included_filterlists "$template_name" domains_variables_in_included_filterlists

        local domains_variables_in_included_json_files
        extract_domains_variables_in_included_json_files "$template_name" domains_variables_in_included_json_files
        check_domain_variables_in_filterlists "$template_name" "$(echo ${domains_variables_in_included_filterlists[@]})" "$(echo ${domains_variables_in_included_json_files[@]})"
    done

    check_if_domains_variables_are_identical_in_lists_and_jsons
}

check_unit_tests() {
    local exit_status=0


    bash ./pre-commit-src/tests/pre-commit-tests.sh '--no-verbose' || exit_status=1
    function_exit_code=$?
    if [ $exit_status -ne 1 ]; then
        exit_status=$function_exit_code
    fi

    if [ $exit_status -ne 0 ]; then
        last_error="Unit tests failed with exit code $exit_status"
        last_error+="\nPlease fix the issues before continuing"
        last_error+="To check the details run 'pre-commit-src/tests/pre-commit-tests.sh'"
        return $exit_status
    else
        last_error="Unit tests passed successfully"
    fi
}

check_pre_commit_files() {
    local staged_files
    get_staged_files staged_files

    local pre_commit_path='pre-commit-src/pre-commit'
    check_if_file_exists "$pre_commit_path"

    local matched_pre_commit_in_unstaged=$(grep -x "$pre_commit_path" <<< "$unstaged_files" || echo '')
    if [ -n "$matched_pre_commit_in_unstaged" ]; then
        last_error="Unstaged changes detected in pre-commit file. Stage pre-commit changes before continuing."
        return 1
    fi

    local pre_commit_script_path='pre-commit-src/pre-commit-script.sh'
    check_if_file_exists "$pre_commit_script_path"
    local matched_pre_commit_script_in_unstaged=$(grep -x "$pre_commit_script_path" <<< "$unstaged_files" || echo '')
    if [ -n "$matched_pre_commit_script_in_unstaged" ]; then
        last_error="Unstaged changes detected in pre-commit-script.sh file. Stage pre-commit-script changes before continuing."
        return 1
    fi

    pre_commit_tests_path='pre-commit-src/tests/pre-commit-tests.sh'
    check_if_file_exists "$pre_commit_tests_path"
    local matched_pre_commit_tests_in_unstaged=$(grep -x "$pre_commit_tests_path" <<< "$unstaged_files" || echo '')
    if [ -n "$matched_pre_commit_tests_in_unstaged" ]; then
        last_error="Unstaged changes detected in pre-commit-tests file. Stage pre-commit tests changes before continuing."
        return 1
    fi
    # Only if something changed in pre commit or pre commit tests the unit tests should be run
    if [ -z "$matched_pre_commit_in_unstaged$matched_pre_commit_script_in_unstaged$matched_pre_commit_tests_in_unstaged" ]; then
        check_unit_tests
    fi
}

# For testing purposes only if the script has no arguments or the argument is main the process should run
# thanks to that the script can be tested without running the main function
if [ -z "${1:-}" ]; then
    echo 'Running pre-commit checks...'
    main
    check_pre_commit_files

    echo "Pre-commit checks passed successfully. Double check if there were no error messages above this message before pushing"
    unalias jq
    unalias grep
    unalias git
    exit 0

elif [ "$1" = "--load-only" ]; then
    testing=true
    echo "Script loaded successfully"
else
    echo Wrong option provided "$@"
    unalias jq
    unalias grep
    unalias git
    exit 1
fi