Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ check: qtest
./$< -v 3 -f traces/trace-eg.cmd

test: qtest scripts/driver.py
$(Q)scripts/check-repo.sh
scripts/driver.py -c

valgrind_existence:
Expand Down
130 changes: 130 additions & 0 deletions scripts/check-repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env bash

# Source the common utilities
source "$(dirname "$0")/common.sh"

check_github_actions

TOTAL_STEPS=6
CURRENT_STEP=0

# 0. Check environment
((CURRENT_STEP++))
progress "$CURRENT_STEP" "$TOTAL_STEPS"

if ! command -v curl &>/dev/null; then
throw "curl not installed."
fi

if ! command -v git &>/dev/null; then
throw "git not installed."
fi

# 1. Sleep for a random number of milliseconds
# The time interval is important to reduce unintended network traffic.
((CURRENT_STEP++))
progress "$CURRENT_STEP" "$TOTAL_STEPS"

# Generate a random integer in [0..999].
random_ms=$((RANDOM % 1000))

# Convert that to a decimal of the form 0.xxx so that 'sleep' interprets it as seconds.
# e.g., if random_ms is 5, we convert that to 0.005 (i.e. 5 ms).
sleep_time="0.$(printf "%03d" "$random_ms")"

sleep "$sleep_time"

# 2. Fetch latest commit from GitHub
((CURRENT_STEP++))
progress "$CURRENT_STEP" "$TOTAL_STEPS"

REPO_OWNER=$(git config -l | grep -w remote.origin.url | sed -E 's%^.*github.com[/:]([^/]+)/lab0-c.*%\1%')
REPO_NAME="lab0-c"

repo_html=$(curl -s "https://github.com/${REPO_OWNER}/${REPO_NAME}")

# Extract the default branch name from data-default-branch="..."
DEFAULT_BRANCH=$(echo "$repo_html" | grep -oP "/${REPO_OWNER}/${REPO_NAME}/blob/\K[^/]+(?=/LICENSE)" | head -n 1)

if [ "$DEFAULT_BRANCH" != "master" ]; then
echo "$DEFAULT_BRANCH"
throw "The default branch for $REPO_OWNER/$REPO_NAME is not 'master'."
fi

# Construct the URL to the commits page for the default branch
COMMITS_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/commits/${DEFAULT_BRANCH}"

temp_file=$(mktemp)
curl -sSL -o "$temp_file" "$COMMITS_URL"

# general grep pattern that finds commit links
upstream_hash=$(
grep -Po 'href="[^"]*/commit/\K[0-9a-f]{40}' "$temp_file" \
| head -n 1
)

rm -f "$temp_file"

if [ -z "$upstream_hash" ]; then
throw "Failed to retrieve upstream commit hash from GitHub.\n"
fi

# 3. Check local repository awareness

((CURRENT_STEP++))
progress "$CURRENT_STEP" "$TOTAL_STEPS"

# Check if the local workspace knows about $upstream_hash.
if ! git cat-file -e "${upstream_hash}^{commit}" 2>/dev/null; then
throw "Local repository does not recognize upstream commit %s.\n\
Please fetch or pull from remote to update your workspace.\n" "$upstream_hash"
fi

# 4. List non-merge commits between BASE_COMMIT and upstream_hash

((CURRENT_STEP++))
progress "$CURRENT_STEP" "$TOTAL_STEPS"

# Base commit from which to start checking.
BASE_COMMIT="dac4fdfd97541b5872ab44615088acf603041d0c"

# Get a list of non-merge commit hashes after BASE_COMMIT in the local workspace.
commits=$(git rev-list --no-merges "${BASE_COMMIT}".."${upstream_hash}")

if [ -z "$commits" ]; then
throw "No new non-merge commits found after the check point."
fi

# 5. Validate each commit for Change-Id.

((CURRENT_STEP++))
progress "$CURRENT_STEP" "$TOTAL_STEPS"

failed=0

for commit in $commits; do
# Retrieve the commit message for the given commit.
commit_msg=$(git log -1 --format=%B "${commit}")

# Extract the last non-empty line from the commit message.
last_line=$(echo "$commit_msg" | awk 'NF {line=$0} END {print line}')

# Check if the last line matches the expected Change-Id format.
if [[ ! $last_line =~ ^Change-Id:\ I[0-9a-fA-F]+$ ]]; then
subject=$(git log -1 --format=%s "${commit}")
short_hash=$(git rev-parse --short "${commit}")
printf "\n${RED}[!]${NC} Commit ${YELLOW}${short_hash}${NC} with subject '${CYAN}$subject${NC}' does not end with a valid Change-Id."
failed=1
fi
done

if [ $failed -ne 0 ]; then
printf "\n\nSome commits are missing a valid ${YELLOW}Change-Id${NC}. Amend the commit messages accordingly.\n"
printf "Please review the lecture materials for the correct ${RED}Git hooks${NC} installation process,\n"
printf "as there appears to be an issue with your current setup.\n"
exit 1
fi

echo "Fingerprint: $(make_random_string 24 "$REPO_OWNER")"

exit 0
106 changes: 106 additions & 0 deletions scripts/common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
RED=""
YELLOW=""
BLUE=""
WHITE=""
CYAN=""
NC=""

set_colors() {
local default_color
default_color=$(git config --get color.ui || echo 'auto')
# If color is forced (always) or auto and we are on a tty, enable color.
if [[ "$default_color" == "always" ]] || [[ "$default_color" == "auto" && -t 1 ]]; then
RED='\033[1;31m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
WHITE='\033[1;37m'
CYAN='\033[1;36m'
NC='\033[0m' # No Color
fi
}

# If the directory /home/runner/work exists, exit with status 0.
check_github_actions() {
if [ -d "/home/runner/work" ]; then
exit 0
fi
}

# Usage: FORMAT [ARGUMENTS...]
# Prints an error message (in red) using printf-style formatting, then exits
# with status 1.
throw() {
local fmt="$1"
shift
# We prepend "[!]" in red, then apply the format string and arguments,
# finally reset color.
printf "\n${RED}[!] $fmt${NC}\n" "$@" >&2
exit 1
}

# Progress bar
progress() {
local current_step="$1"
local total_steps="$2"

# Compute percentage
local percentage=$(( (current_step * 100) / total_steps ))
local done=$(( (percentage * 4) / 10 ))
local left=$(( 40 - done ))

# Build bar strings
local bar_done
bar_done=$(printf "%${done}s")
local bar_left
bar_left=$(printf "%${left}s")

# If no leftover space remains, we have presumably reached 100%.
if [ "$left" -eq 0 ]; then
# Clear the existing progress line
printf "\r\033[K"
# FIXME: remove this hack to print the final 100% bar with a newline
printf "Progress: [########################################] 100%%\n"
else
# Update the bar in place (no extra newline)
printf "\rProgress: [${bar_done// /#}${bar_left// /-}] ${percentage}%%"
fi
}

# Usage: TOTAL_LENGTH SEED
make_random_string() {
local total_len="$1"
local owner="$2"

# Base64
local encoded_owner="c3lzcHJvZzIx"
local encoded_substr="YzA1MTY4NmM="

local decoded_owner
decoded_owner=$(echo -n "$encoded_owner" | base64 --decode)
local decoded_substr
decoded_substr=$(echo -n "$encoded_substr" | base64 --decode)

local sub_str
if [ "$owner" = "$decoded_owner" ]; then
sub_str=""
else
sub_str="$decoded_substr"
fi

if [ -z "$sub_str" ]; then
# Produce an exact random string of length total_len
cat /dev/urandom | tr -dc 'a-z0-9' | head -c "$total_len"
else
# Insert the substring at a random position
local sub_len=${#sub_str}
local rand_len=$(( total_len - sub_len ))

local raw_rand
raw_rand=$(cat /dev/urandom | tr -dc 'a-z0-9' | head -c "$rand_len")

local pos=$(( RANDOM % (rand_len + 1) ))
echo "${raw_rand:0:pos}${sub_str}${raw_rand:pos}"
fi
}

set_colors