Skip to content
Open
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
269 changes: 269 additions & 0 deletions .github/workflows/dependabot-submodule-tag-alignment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
name: Dependabot Submodule Tag Alignment

on:
pull_request:
types:
- opened
- synchronize
- reopened

permissions:
contents: write
pull-requests: write

jobs:
align-submodule-tags:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest

steps:
######################################################################
# Checkout the PR branch itself.
#
# We intentionally checkout the PR HEAD branch because we may:
# - modify .gitmodules
# - update submodule pointers
# - force-push changes back into the Dependabot branch
######################################################################
- name: Checkout PR branch
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
submodules: recursive
ref: ${{ github.event.pull_request.head.ref }}

######################################################################
# Configure git identity for commits made by the workflow.
######################################################################
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Install GitHub CLI
run: |
sudo apt-get update
sudo apt-get install -y gh jq

######################################################################
# Main logic.
#
# This script:
# 1. Detects modified submodules in the PR
# 2. Reads branch/tag/url from .gitmodules
# 3. Finds the newest tag reachable from the configured branch
# 4. Compares commits
# 5. Either:
# - closes the PR
# - updates only .gitmodules
# - or rewrites the submodule commit to the latest tag commit
######################################################################
- name: Align submodule tags
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |

set -euo pipefail

####################################################################
# Cleanup safety: always remove temp dir even on failure/exit
####################################################################
TEMP_DIR=""
trap '[[ -n "$TEMP_DIR" ]] && rm -rf "$TEMP_DIR"' EXIT

echo "========================================================"
echo "Detecting changed submodules in PR"
echo "========================================================"

####################################################################
# Detect submodule changes in this PR
####################################################################
CHANGED_SUBMODULES=$(
git diff --name-only "$BASE_SHA" "$HEAD_SHA" \
| while read -r path; do
git ls-tree "$HEAD_SHA" "$path" \
| awk '$1 == "160000" {print $4}'
done
)

if [ -z "$CHANGED_SUBMODULES" ]; then
echo "No changed submodules detected."
exit 0
fi

echo "Changed submodules:"
echo "$CHANGED_SUBMODULES"

####################################################################
# Enforce strict single-submodule rule
####################################################################
SUBMODULE_COUNT=$(echo "$CHANGED_SUBMODULES" | wc -w)

if [ "$SUBMODULE_COUNT" -gt 1 ]; then
echo "ERROR: Multiple submodules detected."

gh pr comment "$PR_NUMBER" \
--repo "$REPO" \
--body "$(printf '%s\n' \
"Workflow \"$GITHUB_WORKFLOW\" expected exactly one submodule update in this Dependabot PR, but found multiple:" \
"" \
'```' \
"$CHANGED_SUBMODULES" \
'```' \
"" \
"This PR was left unchanged for manual review."
)"

exit 1
fi

####################################################################
# Single submodule assumption
####################################################################
SUBMODULE_PATH=$(echo "$CHANGED_SUBMODULES" | head -n1)

echo "Processing submodule: $SUBMODULE_PATH"

####################################################################
# Read submodule metadata
####################################################################
SUBMODULE_URL=$(git config -f .gitmodules --get "submodule.${SUBMODULE_PATH}.url" || true)
SUBMODULE_BRANCH=$(git config -f .gitmodules --get "submodule.${SUBMODULE_PATH}.branch" || true)
SUBMODULE_TAG=$(git config -f .gitmodules --get "submodule.${SUBMODULE_PATH}.tag" || true)

echo "URL: $SUBMODULE_URL"
echo "Branch: $SUBMODULE_BRANCH"
echo "Tag: ${SUBMODULE_TAG:-<none>}"

if [ -z "$SUBMODULE_URL" ]; then
echo "ERROR: Missing submodule URL"
exit 1
fi

####################################################################
# Fetch submodule repository metadata
####################################################################
TEMP_DIR=$(mktemp -d)

git clone --quiet --no-checkout "$SUBMODULE_URL" "$TEMP_DIR"
pushd "$TEMP_DIR" > /dev/null

git fetch --all --tags --force

REACHABLE_TAGS=$(git tag --merged "origin/${SUBMODULE_BRANCH}" --sort=-v:refname)

if [ -z "$REACHABLE_TAGS" ]; then
echo "No reachable tags for branch ${SUBMODULE_BRANCH}"
popd > /dev/null
exit 0
fi

LATEST_TAG=$(echo "$REACHABLE_TAGS" | head -n1)
LATEST_TAG_COMMIT=$(git rev-list -n 1 "$LATEST_TAG")

popd > /dev/null

echo "Latest reachable tag: $LATEST_TAG"
echo "Latest tag commit: $LATEST_TAG_COMMIT"

####################################################################
# Current PR submodule commit
####################################################################
CURRENT_SUBMODULE_COMMIT=$(git ls-tree HEAD "$SUBMODULE_PATH" | awk '{print $3}')

echo "Dependabot commit: $CURRENT_SUBMODULE_COMMIT"

####################################################################
# CASE 1: Submodule already pinned to latest release tag
####################################################################
if [ "$SUBMODULE_TAG" = "$LATEST_TAG" ]; then

echo "Submodule already at latest release tag. Closing PR."

gh pr comment "$PR_NUMBER" \
--repo "$REPO" \
--body "$(printf '%s\n' \
"Workflow \"$GITHUB_WORKFLOW\" closed this PR because the submodule is already pinned to the latest release tag:" \
"" \
"- Tag: \`$LATEST_TAG\`" \
"" \
"@dependabot ignore this"
)"

gh pr close "$PR_NUMBER" --repo "$REPO"

exit 0
fi

####################################################################
# CASE 2: Newer tag exists → PR will be rewritten
####################################################################
NEW_PR_TITLE="Bump ${SUBMODULE_PATH} from ${SUBMODULE_TAG:-unknown} to ${LATEST_TAG}"

gh pr edit "$PR_NUMBER" \
--repo "$REPO" \
--title "$NEW_PR_TITLE"

echo "Updating .gitmodules to latest tag: $LATEST_TAG"

git config -f .gitmodules \
"submodule.${SUBMODULE_PATH}.tag" \
"$LATEST_TAG"

git add .gitmodules

####################################################################
# CASE 2A: Dependabot already at tag commit → no rewrite needed
####################################################################
if [ "$CURRENT_SUBMODULE_COMMIT" = "$LATEST_TAG_COMMIT" ]; then

echo "Dependabot already at tag commit. Only .gitmodules updated."

####################################################################
# CASE 2B: Need to rewrite submodule to tag commit
####################################################################
else

echo "Rewriting submodule to tag commit..."

gh pr comment "$PR_NUMBER" \
--repo "$REPO" \
--body "$(printf '%s\n' \
"Workflow \"$GITHUB_WORKFLOW\" rewrote this submodule update to use the latest release tag." \
"" \
"- Previous tag: \`${SUBMODULE_TAG}\`" \
"- New tag: \`${LATEST_TAG}\`" \
"- Dependabot commit: \`${CURRENT_SUBMODULE_COMMIT}\`" \
"- Tag commit: \`${LATEST_TAG_COMMIT}\`"
)"

git submodule update --init --recursive "$SUBMODULE_PATH"
pushd "$SUBMODULE_PATH" > /dev/null

git checkout "$LATEST_TAG"

popd > /dev/null

git add "$SUBMODULE_PATH"
fi

####################################################################
# Commit result (always single commit if changes exist)
####################################################################
if git diff --cached --quiet; then
echo "No changes to commit."
exit 0
fi

git commit -m "$NEW_PR_TITLE"

####################################################################
# Push result back to Dependabot PR branch
####################################################################
git push origin HEAD:"$PR_BRANCH"
8 changes: 7 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
path = public/mod/questionnaire
url = https://github.com/PoetOS/moodle-mod_questionnaire
branch = MOODLE_500_STABLE
tag = v5.0.1
tag = v5.0.2
[submodule "public/mod/quiz/report/archive"]
path = public/mod/quiz/report/archive
url = https://github.com/bfh/moodle-quiz_archive
Expand Down Expand Up @@ -198,3 +198,9 @@
url = https://github.com/ucsf-education/moodle-theme-ucsf
branch = MOODLE_501_STABLE
tag = v5.1.2
[submodule "sample-repository"]
path = sample-repository
url = https://github.com/ctam/sample-repository
branch = main
tag = v1.0

2 changes: 1 addition & 1 deletion public/local/oauthredirect
Submodule oauthredirect updated 1 files
+1 −1 README.md
2 changes: 1 addition & 1 deletion public/mod/questionnaire
Submodule questionnaire updated 73 files
+27 −10 CHANGES.md
+10 −8 README.md
+9 −0 backup/moodle2/backup_questionnaire_stepslib.php
+88 −1 backup/moodle2/restore_questionnaire_stepslib.php
+172 −0 classes/courseformat/overview.php
+68 −0 classes/dates.php
+1 −0 classes/feedback_section_form.php
+1 −1 classes/file_storage.php
+179 −0 classes/manager.php
+1 −0 classes/output/renderer.php
+1 −1 classes/privacy/provider.php
+227 −0 classes/question/file.php
+1 −1 classes/question/numerical.php
+18 −15 classes/question/question.php
+115 −12 classes/questions_form.php
+450 −0 classes/responsetype/file.php
+2 −2 classes/responsetype/rank.php
+2 −1 classes/responsetype/response/response.php
+3 −2 classes/search/question.php
+54 −0 classes/task/cron_task.php
+7 −0 db/install.php
+17 −2 db/install.xml
+10 −1 db/tasks.php
+61 −0 db/upgrade.php
+5 −198 index.php
+23 −2 lang/en/questionnaire.php
+30 −9 lib.php
+148 −14 locallib.php
+68 −38 questionnaire.class.php
+92 −21 questions.php
+34 −18 report.php
+9 −0 settings.php
+22 −0 styles.css
+5 −5 templates/dataformat_selector.mustache
+4 −4 templates/extrafields.mustache
+1 −1 templates/navbaralpha.mustache
+3 −3 templates/question_check.mustache
+3 −3 templates/question_date.mustache
+2 −2 templates/question_numeric.mustache
+3 −3 templates/question_radio.mustache
+1 −1 templates/question_rate.mustache
+1 −1 templates/question_text.mustache
+1 −1 templates/question_yesno.mustache
+1 −1 templates/questionpdf_container.mustache
+1 −1 templates/reportpage.mustache
+1 −1 templates/reportpagepdf.mustache
+3 −3 templates/response_check.mustache
+3 −3 templates/response_radio.mustache
+3 −3 templates/response_rate.mustache
+4 −4 templates/response_yesno.mustache
+3 −3 templates/responselist.mustache
+3 −3 templates/results_choice.mustache
+1 −1 templates/results_date.mustache
+3 −3 templates/results_rate.mustache
+1 −1 templates/results_text.mustache
+3 −3 templates/resultspdf_choice.mustache
+1 −1 templates/resultspdf_date.mustache
+3 −3 templates/resultspdf_rate.mustache
+1 −1 templates/resultspdf_text.mustache
+4 −0 tests/behat/add_questions.feature
+221 −16 tests/behat/behat_mod_questionnaire.php
+86 −0 tests/behat/deletion_questionnaire.feature
+80 −0 tests/behat/file_question.feature
+94 −0 tests/behat/file_question_multiple.feature
+174 −0 tests/behat/overview_report.feature
+3 −3 tests/custom_completion_test.php
+109 −0 tests/deletion_question_test.php
+ tests/fixtures/backup-activity-questionnaire.mbz
+ tests/fixtures/testfilequestion.pdf
+ tests/fixtures/testfilequestion2.pdf
+45 −0 tests/lib_test.php
+2 −2 version.php
+1 −1 view.php
1 change: 1 addition & 0 deletions sample-repository
Submodule sample-repository added at aa6a25