diff --git a/hooks/validate.sh b/hooks/validate.sh index 6a41397..b429fa3 100755 --- a/hooks/validate.sh +++ b/hooks/validate.sh @@ -44,6 +44,8 @@ if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi declare -i MINVERSION=$TIMESTAMPING_VERSION declare -i MAX_COMMITS_TO_CHECK=0 declare -A PROCESSED_COMMIT +declare -A COMMITS +declare -A COMMIT_TIMES while [[ $# -gt 0 ]]; do KEY="$1" @@ -295,7 +297,7 @@ validate_commit() { if [ $NUM_VALID -gt 0 ]; then if [ $NUM_INVALID -gt 0 ]; then - echo_warning "Warning: While commit $COMMIT_HASH contains $NUM_VALID valid timestamp tokens and thus is considered proppely timestamped, it also contains $NUM_INVALID invalid timestamp tokens." + echo_warning "Warning: While commit $COMMIT_HASH contains $NUM_VALID valid timestamp tokens and thus is considered properly timestamped, it also contains $NUM_INVALID invalid timestamp tokens." fi DATE_STRING=$(date -d @"$EARLIEST_VALID_UNIX_TIME") echo_info "Commit $COMMIT_HASH, which timestamps commit $PARENT_HASH at $DATE_STRING, contains $NUM_VALID valid timestamp tokens." @@ -341,6 +343,64 @@ validate_commit_and_parents() { return 1 } +# Recursive function to find all ancestors of commit +# param1: commit hash +# creates an array COMMITS, key is the commit hash, value is the commit time (Unix epoch seconds) +# the array contains all commits found in all paths from the passed-in commit hash back to the root commit of the repo +# the array is global so it can be accessed after the function returns +find_all_commits() { + local COMMIT_HASH="$1" + log "find_all_commits for $COMMIT_HASH" + # git show "ct" format returns the commit time as Unix epoch seconds + COMMIT_TIME=$(git show --no-patch --format=%ct "$COMMIT_HASH") + COMMITS[$COMMIT_HASH]="${COMMIT_TIME}" + + local PARENTS=$(git cat-file -p "$COMMIT_HASH" | awk '/^$/{exit} /parent/ {print}' | sed 's/parent //') + # iterate over all parents of commit + if [ ! -z "$PARENTS" ]; then + while read PARENT_HASH; do + if [[ ${COMMITS[$PARENT_HASH]} ]]; then + log "commit $PARENT_HASH has already been processed" + else + find_all_commits "$PARENT_HASH" + fi + done <<< $(printf "%s" "$PARENTS") + fi +} + +# Validate the commits in the COMMITS array, up to MAX_COMMITS_TO_CHECK +# returns: 0 if the validation of the commits succeeded +validate_commits() { + ALL_PASSED=true + # create an associative array with keys using the Unix epoch commit time and value the commit hash + # this array can be easily used to sort in (forward or reverse) order of time + for HASH in "${!COMMITS[@]}"; do + UNIX_EPOCH_TIME="${COMMITS[$HASH]}" + # two commits could have the exact same Unix epoch in seconds + # so make that unique by appending an "x" and the hash + UNIQUE_KEY="${UNIX_EPOCH_TIME}x${HASH}" + COMMIT_TIMES[$UNIQUE_KEY]="${HASH}" + done + # sort into reverse order + SORTED_KEYS=($(printf "%s\n" "${!COMMIT_TIMES[@]}" | sort -r)) + # process the commits from latest time to oldest time + ALL_PASSED=true + for ENTRY in "${SORTED_KEYS[@]}"; do + COMMIT_HASH=${COMMIT_TIMES[${ENTRY}]} + log "${ENTRY} has value ${COMMIT_HASH}" + NUM_COMMITS_CHECKED=${#PROCESSED_COMMIT[@]} + if [[ ${NUM_COMMITS_CHECKED} -lt ${MAX_COMMITS_TO_CHECK} ]]; then + if ! validate_commit "$COMMIT_HASH"; then + ALL_PASSED=false + fi + fi + done + if [ "$ALL_PASSED" = true ]; then + return 0 + fi + return 1 +} + echo_info "Checking repository integrity..." #check git repository integrity if ! git fsck --full --strict --no-progress --no-dangling "$COMMIT_HASH"; then @@ -352,10 +412,21 @@ echo "" echo_info "Validating timestamps. This may take a while..." echo "" -if validate_commit_and_parents "$COMMIT_HASH"; then - echo_success "Validation OK: All timestamped commits in the commit history of $COMMIT_HASH contain at least one valid timestamp." - exit 0 +if [[ ${MAX_COMMITS_TO_CHECK} -ge 1 ]]; then + find_all_commits "$COMMIT_HASH" + if validate_commits; then + echo_success "Validation OK: ${NUM_COMMITS_CHECKED} timestamped commits in the commit history of $COMMIT_HASH contain at least one valid timestamp." + exit 0 + else + echo_error "Validation Failed: There are timestamped commits in the commit history of $COMMIT_HASH which do not contain any valid timestamps." + exit 1 + fi else - echo_error "Validation Failed: There are timestamped commits in the commit history of $COMMIT_HASH which do not contain any valid timestamps." - exit 1 + if validate_commit_and_parents "$COMMIT_HASH"; then + echo_success "Validation OK: All timestamped commits in the commit history of $COMMIT_HASH contain at least one valid timestamp." + exit 0 + else + echo_error "Validation Failed: There are timestamped commits in the commit history of $COMMIT_HASH which do not contain any valid timestamps." + exit 1 + fi fi