Skip to content

Commit a30f81a

Browse files
authored
Merge pull request #202 from sysprog21/sanity-check
Verify repository integrity
2 parents c051686 + 9e201ef commit a30f81a

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ check: qtest
5757
./$< -v 3 -f traces/trace-eg.cmd
5858

5959
test: qtest scripts/driver.py
60+
$(Q)scripts/check-repo.sh
6061
scripts/driver.py -c
6162

6263
valgrind_existence:

scripts/check-repo.sh

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#!/usr/bin/env bash
2+
3+
# Source the common utilities
4+
source "$(dirname "$0")/common.sh"
5+
6+
check_github_actions
7+
8+
TOTAL_STEPS=6
9+
CURRENT_STEP=0
10+
11+
# 0. Check environment
12+
((CURRENT_STEP++))
13+
progress "$CURRENT_STEP" "$TOTAL_STEPS"
14+
15+
if ! command -v curl &>/dev/null; then
16+
throw "curl not installed."
17+
fi
18+
19+
if ! command -v git &>/dev/null; then
20+
throw "git not installed."
21+
fi
22+
23+
# 1. Sleep for a random number of milliseconds
24+
# The time interval is important to reduce unintended network traffic.
25+
((CURRENT_STEP++))
26+
progress "$CURRENT_STEP" "$TOTAL_STEPS"
27+
28+
# Generate a random integer in [0..999].
29+
random_ms=$((RANDOM % 1000))
30+
31+
# Convert that to a decimal of the form 0.xxx so that 'sleep' interprets it as seconds.
32+
# e.g., if random_ms is 5, we convert that to 0.005 (i.e. 5 ms).
33+
sleep_time="0.$(printf "%03d" "$random_ms")"
34+
35+
sleep "$sleep_time"
36+
37+
# 2. Fetch latest commit from GitHub
38+
((CURRENT_STEP++))
39+
progress "$CURRENT_STEP" "$TOTAL_STEPS"
40+
41+
REPO_OWNER=$(git config -l | grep -w remote.origin.url | sed -E 's%^.*github.com[/:]([^/]+)/lab0-c.*%\1%')
42+
REPO_NAME="lab0-c"
43+
44+
repo_html=$(curl -s "https://github.com/${REPO_OWNER}/${REPO_NAME}")
45+
46+
# Extract the default branch name from data-default-branch="..."
47+
DEFAULT_BRANCH=$(echo "$repo_html" | grep -oP "/${REPO_OWNER}/${REPO_NAME}/blob/\K[^/]+(?=/LICENSE)" | head -n 1)
48+
49+
if [ "$DEFAULT_BRANCH" != "master" ]; then
50+
echo "$DEFAULT_BRANCH"
51+
throw "The default branch for $REPO_OWNER/$REPO_NAME is not 'master'."
52+
fi
53+
54+
# Construct the URL to the commits page for the default branch
55+
COMMITS_URL="https://github.com/${REPO_OWNER}/${REPO_NAME}/commits/${DEFAULT_BRANCH}"
56+
57+
temp_file=$(mktemp)
58+
curl -sSL -o "$temp_file" "$COMMITS_URL"
59+
60+
# general grep pattern that finds commit links
61+
upstream_hash=$(
62+
grep -Po 'href="[^"]*/commit/\K[0-9a-f]{40}' "$temp_file" \
63+
| head -n 1
64+
)
65+
66+
rm -f "$temp_file"
67+
68+
if [ -z "$upstream_hash" ]; then
69+
throw "Failed to retrieve upstream commit hash from GitHub.\n"
70+
fi
71+
72+
# 3. Check local repository awareness
73+
74+
((CURRENT_STEP++))
75+
progress "$CURRENT_STEP" "$TOTAL_STEPS"
76+
77+
# Check if the local workspace knows about $upstream_hash.
78+
if ! git cat-file -e "${upstream_hash}^{commit}" 2>/dev/null; then
79+
throw "Local repository does not recognize upstream commit %s.\n\
80+
Please fetch or pull from remote to update your workspace.\n" "$upstream_hash"
81+
fi
82+
83+
# 4. List non-merge commits between BASE_COMMIT and upstream_hash
84+
85+
((CURRENT_STEP++))
86+
progress "$CURRENT_STEP" "$TOTAL_STEPS"
87+
88+
# Base commit from which to start checking.
89+
BASE_COMMIT="dac4fdfd97541b5872ab44615088acf603041d0c"
90+
91+
# Get a list of non-merge commit hashes after BASE_COMMIT in the local workspace.
92+
commits=$(git rev-list --no-merges "${BASE_COMMIT}".."${upstream_hash}")
93+
94+
if [ -z "$commits" ]; then
95+
throw "No new non-merge commits found after the check point."
96+
fi
97+
98+
# 5. Validate each commit for Change-Id.
99+
100+
((CURRENT_STEP++))
101+
progress "$CURRENT_STEP" "$TOTAL_STEPS"
102+
103+
failed=0
104+
105+
for commit in $commits; do
106+
# Retrieve the commit message for the given commit.
107+
commit_msg=$(git log -1 --format=%B "${commit}")
108+
109+
# Extract the last non-empty line from the commit message.
110+
last_line=$(echo "$commit_msg" | awk 'NF {line=$0} END {print line}')
111+
112+
# Check if the last line matches the expected Change-Id format.
113+
if [[ ! $last_line =~ ^Change-Id:\ I[0-9a-fA-F]+$ ]]; then
114+
subject=$(git log -1 --format=%s "${commit}")
115+
short_hash=$(git rev-parse --short "${commit}")
116+
printf "\n${RED}[!]${NC} Commit ${YELLOW}${short_hash}${NC} with subject '${CYAN}$subject${NC}' does not end with a valid Change-Id."
117+
failed=1
118+
fi
119+
done
120+
121+
if [ $failed -ne 0 ]; then
122+
printf "\n\nSome commits are missing a valid ${YELLOW}Change-Id${NC}. Amend the commit messages accordingly.\n"
123+
printf "Please review the lecture materials for the correct ${RED}Git hooks${NC} installation process,\n"
124+
printf "as there appears to be an issue with your current setup.\n"
125+
exit 1
126+
fi
127+
128+
echo "Fingerprint: $(make_random_string 24 "$REPO_OWNER")"
129+
130+
exit 0

scripts/common.sh

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
RED=""
2+
YELLOW=""
3+
BLUE=""
4+
WHITE=""
5+
CYAN=""
6+
NC=""
7+
8+
set_colors() {
9+
local default_color
10+
default_color=$(git config --get color.ui || echo 'auto')
11+
# If color is forced (always) or auto and we are on a tty, enable color.
12+
if [[ "$default_color" == "always" ]] || [[ "$default_color" == "auto" && -t 1 ]]; then
13+
RED='\033[1;31m'
14+
YELLOW='\033[1;33m'
15+
BLUE='\033[1;34m'
16+
WHITE='\033[1;37m'
17+
CYAN='\033[1;36m'
18+
NC='\033[0m' # No Color
19+
fi
20+
}
21+
22+
# If the directory /home/runner/work exists, exit with status 0.
23+
check_github_actions() {
24+
if [ -d "/home/runner/work" ]; then
25+
exit 0
26+
fi
27+
}
28+
29+
# Usage: FORMAT [ARGUMENTS...]
30+
# Prints an error message (in red) using printf-style formatting, then exits
31+
# with status 1.
32+
throw() {
33+
local fmt="$1"
34+
shift
35+
# We prepend "[!]" in red, then apply the format string and arguments,
36+
# finally reset color.
37+
printf "\n${RED}[!] $fmt${NC}\n" "$@" >&2
38+
exit 1
39+
}
40+
41+
# Progress bar
42+
progress() {
43+
local current_step="$1"
44+
local total_steps="$2"
45+
46+
# Compute percentage
47+
local percentage=$(( (current_step * 100) / total_steps ))
48+
local done=$(( (percentage * 4) / 10 ))
49+
local left=$(( 40 - done ))
50+
51+
# Build bar strings
52+
local bar_done
53+
bar_done=$(printf "%${done}s")
54+
local bar_left
55+
bar_left=$(printf "%${left}s")
56+
57+
# If no leftover space remains, we have presumably reached 100%.
58+
if [ "$left" -eq 0 ]; then
59+
# Clear the existing progress line
60+
printf "\r\033[K"
61+
# FIXME: remove this hack to print the final 100% bar with a newline
62+
printf "Progress: [########################################] 100%%\n"
63+
else
64+
# Update the bar in place (no extra newline)
65+
printf "\rProgress: [${bar_done// /#}${bar_left// /-}] ${percentage}%%"
66+
fi
67+
}
68+
69+
# Usage: TOTAL_LENGTH SEED
70+
make_random_string() {
71+
local total_len="$1"
72+
local owner="$2"
73+
74+
# Base64
75+
local encoded_owner="c3lzcHJvZzIx"
76+
local encoded_substr="YzA1MTY4NmM="
77+
78+
local decoded_owner
79+
decoded_owner=$(echo -n "$encoded_owner" | base64 --decode)
80+
local decoded_substr
81+
decoded_substr=$(echo -n "$encoded_substr" | base64 --decode)
82+
83+
local sub_str
84+
if [ "$owner" = "$decoded_owner" ]; then
85+
sub_str=""
86+
else
87+
sub_str="$decoded_substr"
88+
fi
89+
90+
if [ -z "$sub_str" ]; then
91+
# Produce an exact random string of length total_len
92+
cat /dev/urandom | tr -dc 'a-z0-9' | head -c "$total_len"
93+
else
94+
# Insert the substring at a random position
95+
local sub_len=${#sub_str}
96+
local rand_len=$(( total_len - sub_len ))
97+
98+
local raw_rand
99+
raw_rand=$(cat /dev/urandom | tr -dc 'a-z0-9' | head -c "$rand_len")
100+
101+
local pos=$(( RANDOM % (rand_len + 1) ))
102+
echo "${raw_rand:0:pos}${sub_str}${raw_rand:pos}"
103+
fi
104+
}
105+
106+
set_colors

0 commit comments

Comments
 (0)