diff --git a/.github/workflows/cli-tests.yml b/.github/workflows/cli-tests.yml index 07b68111..6ec2aced 100644 --- a/.github/workflows/cli-tests.yml +++ b/.github/workflows/cli-tests.yml @@ -70,3 +70,6 @@ jobs: - name: CLI tests run: pytest -s ./tests/*.py + + - name: Repo setup-remotes tests + run: ./tests/test_repo.sh diff --git a/Makefile b/Makefile index 66a9c3d5..a8e2ccf1 100644 --- a/Makefile +++ b/Makefile @@ -143,13 +143,18 @@ dev.status: ## Prints the status of all git repositories. dev.checkout: ## Check out "openedx-release/$OPENEDX_RELEASE" in each repo if set, use default branch otherwise. ./repo.sh checkout +dev.setup-remotes: ## Set up edx and openedx remotes for all forked repositories. + ./repo.sh setup-remotes + dev.clone: dev.clone.ssh ## Clone service repos to the parent directory. dev.clone.https: ## Clone service repos using HTTPS method to the parent directory. ./repo.sh clone + make dev.setup-remotes dev.clone.ssh: ## Clone service repos using SSH method to the parent directory. ./repo.sh clone_ssh + make dev.setup-remotes ######################################################################################## # Developer interface: Docker image management. diff --git a/repo.sh b/repo.sh index e551aafd..3fccc107 100755 --- a/repo.sh +++ b/repo.sh @@ -26,15 +26,14 @@ repos=( "https://github.com/openedx/cs_comments_service.git" "https://github.com/edx/ecommerce.git" "https://github.com/openedx/edx-notes-api.git" - "https://github.com/openedx/edx-platform.git" + "https://github.com/edx/edx-platform.git" "https://github.com/openedx/xqueue.git" "https://github.com/edx/edx-analytics-dashboard.git" - "https://github.com/openedx/frontend-app-gradebook.git" - "https://github.com/openedx/frontend-app-learner-dashboard.git" - "https://github.com/openedx/frontend-app-learner-record.git" + "https://github.com/edx/frontend-app-gradebook.git" + "https://github.com/edx/frontend-app-learner-dashboard.git" + "https://github.com/edx/frontend-app-learner-record.git" "https://github.com/edx/frontend-app-payment.git" - "https://github.com/openedx/frontend-app-publisher.git" - "https://github.com/edx/edx-analytics-dashboard.git" + "https://github.com/edx/frontend-app-publisher.git" "https://github.com/edx/edx-analytics-data-api.git" "https://github.com/openedx/enterprise-catalog.git" "https://github.com/edx/portal-designer.git" @@ -46,17 +45,29 @@ repos=( non_release_repos=( "https://github.com/openedx/frontend-app-authn.git" "https://github.com/openedx/frontend-app-course-authoring.git" - "https://github.com/openedx/frontend-app-learning.git" + "https://github.com/edx/frontend-app-learning.git" "https://github.com/edx/registrar.git" "https://github.com/edx/frontend-app-program-console.git" "https://github.com/openedx/frontend-app-account.git" - "https://github.com/openedx/frontend-app-profile.git" - "https://github.com/openedx/frontend-app-ora-grading.git" + "https://github.com/edx/frontend-app-profile.git" + "https://github.com/edx/frontend-app-ora-grading.git" "https://github.com/openedx/enterprise-subsidy.git" - "https://github.com/openedx/frontend-app-admin-portal.git" - "https://github.com/openedx/frontend-app-learner-portal-enterprise.git" + "https://github.com/edx/frontend-app-admin-portal.git" + "https://github.com/edx/frontend-app-learner-portal-enterprise.git" "https://github.com/edx/frontend-app-enterprise-checkout.git" "https://github.com/edx/edx-exams.git" + "https://github.com/edx/frontend-app-skills.git" + "https://github.com/edx/frontend-app-ora.git" + "https://github.com/edx/frontend-app-exams-dashboard.git" + "https://github.com/edx/frontend-app-learner-portal-programs.git" + "https://github.com/edx/frontend-app-communications.git" + "https://github.com/edx/frontend-app-discussions.git" + "https://github.com/edx/frontend-app-enterprise-public-catalog.git" + "https://github.com/edx/frontend-app-support-tools.git" + "https://github.com/edx/frontend-app-authoring.git" + "https://github.com/edx/frontend-app-instruct.git" + "https://github.com/edx/frontend-app-catalog.git" + "https://github.com/edx/openedx-translations.git" ) ssh_repos=( @@ -66,15 +77,14 @@ ssh_repos=( "git@github.com:edx/ecommerce.git" "git@github.com:openedx/edx-notes-api.git" "git@github.com:openedx/enterprise-catalog.git" - "git@github.com:openedx/edx-platform.git" + "git@github.com:edx/edx-platform.git" "git@github.com:openedx/xqueue.git" "git@github.com:edx/edx-analytics-dashboard.git" - "git@github.com:openedx/frontend-app-gradebook.git" - "git@github.com:openedx/frontend-app-learner-dashboard.git" - "git@github.com:openedx/frontend-app-learner-record.git" + "git@github.com:edx/frontend-app-gradebook.git" + "git@github.com:edx/frontend-app-learner-dashboard.git" + "git@github.com:edx/frontend-app-learner-record.git" "git@github.com:edx/frontend-app-payment.git" - "git@github.com:openedx/frontend-app-publisher.git" - "git@github.com:edx/edx-analytics-dashboard.git" + "git@github.com:edx/frontend-app-publisher.git" "git@github.com:edx/edx-analytics-data-api.git" "git@github.com:edx/portal-designer.git" "git@github.com:openedx/license-manager.git" @@ -85,17 +95,29 @@ ssh_repos=( non_release_ssh_repos=( "git@github.com:openedx/frontend-app-authn.git" "git@github.com:openedx/frontend-app-course-authoring.git" - "git@github.com:openedx/frontend-app-learning.git" + "git@github.com:edx/frontend-app-learning.git" "git@github.com:edx/registrar.git" "git@github.com:edx/frontend-app-program-console.git" "git@github.com:openedx/frontend-app-account.git" - "git@github.com:openedx/frontend-app-profile.git" - "git@github.com:openedx/frontend-app-ora-grading.git" + "git@github.com:edx/frontend-app-profile.git" + "git@github.com:edx/frontend-app-ora-grading.git" "git@github.com:openedx/enterprise-subsidy.git" - "git@github.com:openedx/frontend-app-admin-portal.git" - "git@github.com:openedx/frontend-app-learner-portal-enterprise.git" + "git@github.com:edx/frontend-app-admin-portal.git" + "git@github.com:edx/frontend-app-learner-portal-enterprise.git" "git@github.com:edx/frontend-app-enterprise-checkout.git" "git@github.com:edx/edx-exams.git" + "git@github.com:edx/frontend-app-skills.git" + "git@github.com:edx/frontend-app-ora.git" + "git@github.com:edx/frontend-app-exams-dashboard.git" + "git@github.com:edx/frontend-app-learner-portal-programs.git" + "git@github.com:edx/frontend-app-communications.git" + "git@github.com:edx/frontend-app-discussions.git" + "git@github.com:edx/frontend-app-enterprise-public-catalog.git" + "git@github.com:edx/frontend-app-support-tools.git" + "git@github.com:edx/frontend-app-authoring.git" + "git@github.com:edx/frontend-app-instruct.git" + "git@github.com:edx/frontend-app-catalog.git" + "git@github.com:edx/openedx-translations.git" ) if [ -n "${OPENEDX_RELEASE}" ]; then @@ -287,6 +309,244 @@ status () cd - &> /dev/null } +# Define repositories that exist in both edx and openedx organizations +# These are the ones that need remote setup for forked repositories +FORKED_REPOS=( + "course-discovery" + "credentials" + "cs_comments_service" + "ecommerce" + "edx-notes-api" + "edx-platform" + "xqueue" + "edx-analytics-dashboard" + "frontend-app-gradebook" + "frontend-app-learner-dashboard" + "frontend-app-learner-record" + "frontend-app-skills" + "frontend-app-learning" + "frontend-app-ora" + "frontend-app-ora-grading" + "frontend-app-exams-dashboard" + "frontend-app-learner-portal-programs" + "frontend-app-program-console" + "frontend-app-communications" + "frontend-app-discussions" + "frontend-app-profile" + "frontend-app-enterprise-public-catalog" + "frontend-app-publisher" + "frontend-app-support-tools" + "frontend-app-admin-portal" + "frontend-app-learner-portal-enterprise" + "frontend-app-enterprise-checkout" + "frontend-app-authoring" + "frontend-app-instruct" + "frontend-app-catalog" + "openedx-translations" + "frontend-app-payment" + "edx-analytics-data-api" + "enterprise-catalog" + "portal-designer" + "license-manager" + "codejail-service" + "enterprise-access" + "frontend-app-authn" + "frontend-app-course-authoring" + "registrar" + "frontend-app-account" + "enterprise-subsidy" + "edx-exams" +) + +setup_forked_repo_remotes () +{ + local repo_name=$1 + local edx_remote_exists + local openedx_remote_exists + local origin_exists + local existing_url="" + local existing_org="" + local other_org + local other_remote_exists + local other_url + + # Check if we're in a git repository + if [ ! -d ".git" ]; then + echo "ERROR: $repo_name is not a git repository" + return 1 + fi + + # Check if both remotes already exist (idempotency check) + edx_remote_exists=$(git remote | grep "^edx$" || true) + openedx_remote_exists=$(git remote | grep "^openedx$" || true) + origin_exists=$(git remote | grep "^origin$" || true) + + if [ -n "$edx_remote_exists" ] && [ -n "$openedx_remote_exists" ] && [ -z "$origin_exists" ]; then + echo "Both edx and openedx remotes already exist in $repo_name. No changes needed." + return 0 + fi + + echo "Setting up remotes for forked repository: $repo_name" + + # First, try to find an existing remote and its URL + if [ -n "$origin_exists" ]; then + # We have an 'origin' remote - determine its organization + existing_url=$(git remote get-url origin 2>/dev/null || true) + + if [[ $existing_url =~ github\.com[:/]edx/ ]]; then + existing_org="edx" + elif [[ $existing_url =~ github\.com[:/]openedx/ ]]; then + existing_org="openedx" + else + echo "ERROR: Unexpected origin URL in $repo_name: $existing_url" + echo "Expected URL to be from either edx or openedx organization" + return 1 + fi + + # Rename origin to the correct organization name if not already done + if ! git remote | grep -q "^${existing_org}$"; then + echo "Renaming origin to '$existing_org' in $repo_name" + if ! git remote rename origin "$existing_org"; then + echo "ERROR: Failed to rename origin to $existing_org in $repo_name" + return 1 + fi + else + echo "Remote '$existing_org' already exists, removing origin" + git remote remove origin 2>/dev/null || true + fi + elif [ -n "$edx_remote_exists" ]; then + # No origin, but we have an 'edx' remote - use it as reference + existing_url=$(git remote get-url edx) + existing_org="edx" + elif [ -n "$openedx_remote_exists" ]; then + # No origin or edx, but we have an 'openedx' remote - use it as reference + existing_url=$(git remote get-url openedx) + existing_org="openedx" + else + echo "ERROR: No remotes found in $repo_name" + return 1 + fi + + # Determine the other organization and add its remote if missing + if [ "$existing_org" = "edx" ]; then + other_org="openedx" + else + other_org="edx" + fi + + # Check if the other remote exists + other_remote_exists=$(git remote | grep "^${other_org}$" || true) + + if [ -z "$other_remote_exists" ]; then + # Construct the URL for the other organization + if [[ $existing_url =~ ^git@ ]]; then + # SSH URL format + other_url="git@github.com:${other_org}/${repo_name}.git" + else + # HTTPS URL format + other_url="https://github.com/${other_org}/${repo_name}.git" + fi + + echo "Adding $other_org remote: $other_url" + if ! git remote add "$other_org" "$other_url"; then + echo "ERROR: Failed to add $other_org remote in $repo_name" + return 1 + fi + else + echo "Remote '$other_org' already exists in $repo_name" + fi + + echo "Successfully configured remotes for $repo_name" + return 0 +} + +setup_all_forked_repo_remotes () +{ + local successful_repos=() + local failed_repos=() + local skipped_repos=() + local repo + local name + local is_forked + local forked_repo + + echo "Setting up remotes for all forked repositories..." + echo "========================================" + + for repo in "${repos[@]}" "${non_release_repos[@]}" + do + # Extract repo name from URL + if [[ ! $repo =~ $name_pattern ]]; then + echo "Cannot setup remotes for repo; URL did not match expected pattern: $repo" + continue + fi + name="${BASH_REMATCH[1]}" + + # Check if directory exists + if [ ! -d "$name" ]; then + echo "Repository $name is not cloned. Skipping." + skipped_repos+=("$name") + continue + fi + + # Check if this repo is configured as a forked repo + is_forked=false + for forked_repo in "${FORKED_REPOS[@]}"; do + if [[ "$forked_repo" == "$name" ]]; then + is_forked=true + break + fi + done + + if [[ "$is_forked" == false ]]; then + echo "Repository $name is not configured as a forked repo. Skipping." + skipped_repos+=("$name") + continue + fi + + # Change to repo directory and setup remotes + cd "$name" + if setup_forked_repo_remotes "$name"; then + successful_repos+=("$name") + else + failed_repos+=("$name") + fi + cd "$DEVSTACK_WORKSPACE" + echo "" + done + + # Print summary report + echo "========================================" + echo "Remote Setup Summary:" + echo "========================================" + + if [ ${#successful_repos[@]} -gt 0 ]; then + echo "✓ Successfully configured remotes for ${#successful_repos[@]} repositories:" + printf " - %s\n" "${successful_repos[@]}" + echo "" + fi + + if [ ${#failed_repos[@]} -gt 0 ]; then + echo "✗ Failed to configure remotes for ${#failed_repos[@]} repositories:" + printf " - %s\n" "${failed_repos[@]}" + echo "" + fi + + if [ ${#skipped_repos[@]} -gt 0 ]; then + echo "◦ Skipped ${#skipped_repos[@]} repositories (not cloned or not forked):" + printf " - %s\n" "${skipped_repos[@]}" + echo "" + fi + + echo "Total repositories processed: $((${#successful_repos[@]} + ${#failed_repos[@]} + ${#skipped_repos[@]}))" + + if [ ${#failed_repos[@]} -gt 0 ]; then + return 1 + else + return 0 + fi +} + if [ "$1" == "checkout" ]; then checkout elif [ "$1" == "clone" ]; then @@ -297,4 +557,6 @@ elif [ "$1" == "reset" ]; then reset elif [ "$1" == "status" ]; then status +elif [ "$1" == "setup-remotes" ]; then + setup_all_forked_repo_remotes fi diff --git a/tests/test_repo.sh b/tests/test_repo.sh new file mode 100755 index 00000000..1956e805 --- /dev/null +++ b/tests/test_repo.sh @@ -0,0 +1,208 @@ +#!/bin/bash + +# Determine the script's directory and devstack root +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DEVSTACK_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )" + +TEST_WORKSPACE="/tmp/devstack_test_$$" +mkdir -p "$TEST_WORKSPACE" + +GREEN='\033[0;32m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Test tracking variables +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 +TEST_RESULTS=() + +print_pass() { + echo -e "${GREEN}PASS${NC}: $1" + PASSED_TESTS=$((PASSED_TESTS + 1)) + TEST_RESULTS+=("PASS: $1") +} + +print_fail() { + echo -e "${RED}FAIL${NC}: $1" + FAILED_TESTS=$((FAILED_TESTS + 1)) + TEST_RESULTS+=("FAIL: $1") +} + +show_remotes() { + local repo_name=$1 + local label=$2 + echo " $label - $repo_name:" + if [ -d "$repo_name" ]; then + cd "$repo_name" + git remote -v | sed 's/^/ /' + cd .. + fi +} + +create_test_repo() { + local repo_name=$1 + local origin_url=$2 + mkdir -p "$repo_name" + cd "$repo_name" + git init -q + git remote add origin "$origin_url" + git config user.email "test@example.com" + git config user.name "Test User" + echo "# $repo_name" > README.md + git add README.md + git commit -q -m "Initial commit" + cd .. +} + +echo "TEST 1: EDX Origin Repository" +TOTAL_TESTS=$((TOTAL_TESTS + 1)) +cd "$TEST_WORKSPACE" +create_test_repo "frontend-app-gradebook" "https://github.com/edx/frontend-app-gradebook.git" +show_remotes "frontend-app-gradebook" "BEFORE" +cd "$DEVSTACK_ROOT" +echo " Output:" +DEVSTACK_WORKSPACE="$TEST_WORKSPACE" make dev.setup-remotes +cd "$TEST_WORKSPACE" +show_remotes "frontend-app-gradebook" "AFTER" +cd frontend-app-gradebook +if git remote | grep -q "edx" && git remote | grep -q "openedx" && ! git remote | grep -q "origin"; then + print_pass "EDX origin handling" +else + print_fail "EDX origin handling" +fi +cd .. + +echo +echo "TEST 2: EDX Origin Repository (edx-platform)" +TOTAL_TESTS=$((TOTAL_TESTS + 1)) +create_test_repo "edx-platform" "https://github.com/edx/edx-platform.git" +show_remotes "edx-platform" "BEFORE" +cd "$DEVSTACK_ROOT" +echo " Output:" +DEVSTACK_WORKSPACE="$TEST_WORKSPACE" make dev.setup-remotes +cd "$TEST_WORKSPACE" +show_remotes "edx-platform" "AFTER" +cd edx-platform +if git remote | grep -q "openedx" && git remote | grep -q "edx" && ! git remote | grep -q "origin"; then + print_pass "EDX origin handling (edx-platform)" +else + print_fail "EDX origin handling (edx-platform)" +fi +cd .. + +echo +echo "TEST 3: OpenEDX Origin Repository (course-discovery)" +TOTAL_TESTS=$((TOTAL_TESTS + 1)) +create_test_repo "course-discovery" "https://github.com/openedx/course-discovery.git" +show_remotes "course-discovery" "BEFORE" +cd "$DEVSTACK_ROOT" +echo " Output:" +DEVSTACK_WORKSPACE="$TEST_WORKSPACE" make dev.setup-remotes +cd "$TEST_WORKSPACE" +show_remotes "course-discovery" "AFTER" +cd course-discovery +# course-discovery should be renamed from origin to openedx, and get an edx remote +if git remote get-url openedx | grep -q "openedx/course-discovery" && git remote get-url edx | grep -q "edx/course-discovery" && [ $(git remote | wc -l) -eq 2 ]; then + print_pass "OpenEDX origin handling (course-discovery)" +else + print_fail "OpenEDX origin handling (course-discovery)" +fi +cd .. + +echo +echo "TEST 4: Personal Fork Error" +TOTAL_TESTS=$((TOTAL_TESTS + 1)) +create_test_repo "credentials" "https://github.com/personaluser/credentials.git" +show_remotes "credentials" "BEFORE" +cd "$DEVSTACK_ROOT" +echo " Output:" +DEVSTACK_WORKSPACE="$TEST_WORKSPACE" make dev.setup-remotes +cd "$TEST_WORKSPACE" +show_remotes "credentials" "AFTER" +cd credentials +if git remote get-url origin | grep -q "personaluser"; then + print_pass "Personal fork error detection" +else + print_fail "Personal fork error detection" +fi +cd .. + +echo +echo "TEST 5: Non-Forked Repository" +TOTAL_TESTS=$((TOTAL_TESTS + 1)) +create_test_repo "unknown-repo" "https://github.com/edx/unknown-repo.git" +show_remotes "unknown-repo" "BEFORE" +cd "$DEVSTACK_ROOT" +echo " Output:" +DEVSTACK_WORKSPACE="$TEST_WORKSPACE" make dev.setup-remotes +cd "$TEST_WORKSPACE" +show_remotes "unknown-repo" "AFTER" +cd unknown-repo +if git remote | grep -q "^origin$" && [ $(git remote | wc -l) -eq 1 ]; then + print_pass "Non-forked repository handling" +else + print_fail "Non-forked repository handling" +fi +cd .. + +echo +echo "TEST 6: Idempotent Operations" +TOTAL_TESTS=$((TOTAL_TESTS + 1)) +create_test_repo "frontend-app-gradebook" "https://github.com/edx/frontend-app-gradebook.git" +show_remotes "frontend-app-gradebook" "BEFORE (fresh repo)" +cd "$DEVSTACK_ROOT" +echo " First run output:" +DEVSTACK_WORKSPACE="$TEST_WORKSPACE" make dev.setup-remotes +cd "$TEST_WORKSPACE" +show_remotes "frontend-app-gradebook" "AFTER FIRST RUN" +cd "$DEVSTACK_ROOT" +echo " Second run output (should be idempotent):" +DEVSTACK_WORKSPACE="$TEST_WORKSPACE" make dev.setup-remotes +cd "$TEST_WORKSPACE" +show_remotes "frontend-app-gradebook" "AFTER SECOND RUN (should be identical)" +cd frontend-app-gradebook +if [ $(git remote | wc -l) -eq 2 ] && git remote | grep -q "edx" && git remote | grep -q "openedx"; then + print_pass "Idempotent operations" +else + print_fail "Idempotent operations" +fi +cd .. + +echo +echo "TEST 7: Make Integration" +TOTAL_TESTS=$((TOTAL_TESTS + 1)) +cd "$DEVSTACK_ROOT" +if make -n dev.setup-remotes >/dev/null 2>&1 && make help | grep -q "dev.setup-remotes"; then + print_pass "Make command integration" +else + print_fail "Make command integration" +fi + +rm -rf "$TEST_WORKSPACE" + +echo +echo "======================================================================" +echo -e "${BLUE}TEST SUMMARY${NC}" +echo "======================================================================" +echo "Total Test Cases: $TOTAL_TESTS" +echo -e "Passed: ${GREEN}$PASSED_TESTS${NC}" +echo -e "Failed: ${RED}$FAILED_TESTS${NC}" +echo +echo "Test Results:" +for result in "${TEST_RESULTS[@]}"; do + if [[ $result == PASS:* ]]; then + echo -e " ${GREEN}${result}${NC}" + else + echo -e " ${RED}${result}${NC}" + fi +done +echo +if [ $FAILED_TESTS -eq 0 ]; then + echo -e "${GREEN}All tests passed successfully!${NC}" + exit 0 +else + echo -e "${RED}$FAILED_TESTS test(s) failed.${NC}" + exit 1 +fi