Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8d6ae67
Store Github branch with challenge data
Zahed-Riyaz Jul 9, 2025
9164bf0
Modify backend to store github branch
Zahed-Riyaz Jul 9, 2025
3cf0b12
Merge branch 'master' into starters-versions
Zahed-Riyaz Jul 9, 2025
2f6e1a3
Handle empty branches
Zahed-Riyaz Jul 9, 2025
69b9535
Merge migrations
Zahed-Riyaz Jul 9, 2025
4bff095
Update seed.py
Zahed-Riyaz Jul 9, 2025
75a7868
Add GitHub branch versioning support for multi-version challenges
Zahed-Riyaz Jul 9, 2025
e2d7f53
Revert unnecessary changes
Zahed-Riyaz Jul 9, 2025
f6a06d7
Fix tests
Zahed-Riyaz Jul 9, 2025
3287cc9
Update Github branch var
Zahed-Riyaz Jul 10, 2025
34cbec3
Merge branch 'master' into starters-versions
Zahed-Riyaz Jul 10, 2025
a1bd1ea
Fix failing tests
Zahed-Riyaz Jul 10, 2025
6bbcea7
Pass flake8 and pylint tests
Zahed-Riyaz Jul 10, 2025
4188b9c
Add github_branch field to backend
Zahed-Riyaz Jul 16, 2025
831fec2
Add scripts for populating field
Zahed-Riyaz Jul 16, 2025
1cb4fcf
Merge branch 'master' into starters-versions
Zahed-Riyaz Jul 16, 2025
8af18d5
Allow alphanumeric values for branch name
Zahed-Riyaz Jul 17, 2025
350936f
Remove duplicate params
Zahed-Riyaz Jul 17, 2025
fb379aa
Update migrations and fallback logic
Zahed-Riyaz Jul 17, 2025
8a8e2d8
Update branch validation logic
Zahed-Riyaz Jul 17, 2025
1355c62
Merge branch 'master' into starters-versions
Zahed-Riyaz Jul 17, 2025
d136023
Merge branch 'master' into starters-versions
Zahed-Riyaz Jul 17, 2025
c8bd7c9
Reformat for quality checks
Zahed-Riyaz Jul 17, 2025
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
32 changes: 32 additions & 0 deletions apps/challenges/challenge_config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ def get_value_from_field(data, base_location, field_name):
"challenge_metadata_schema_errors": "ERROR: Unable to serialize the challenge because of the following errors: {}.",
"evaluation_script_not_zip": "ERROR: Please pass in a zip file as evaluation script. If using the `evaluation_script` directory (recommended), it should be `evaluation_script.zip`.",
"docker_based_challenge": "ERROR: New Docker based challenges are not supported starting March 15, 2025.",
"invalid_github_branch_format": "ERROR: GitHub branch name '{branch}' is invalid. It must match the pattern 'challenge-<year>-<version>' (e.g., challenge-2024-1, challenge-2060-v2).",
}


Expand Down Expand Up @@ -364,6 +365,31 @@ def __init__(
self.phase_ids = []
self.leaderboard_ids = []

def validate_github_branch_format(self):
"""
Ensure the github branch name matches challenge-<year>-<version>
For new challenges, enforce strict format. For existing challenges, allow "challenge" fallback.
"""
branch = self.request.data.get("GITHUB_BRANCH_NAME", "challenge")
pattern = r"^challenge-\d{4}-[a-zA-Z0-9]+$"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this also be okay with just "challenge" (default) if passed?


# For new challenge creation (when current_challenge is None), enforce strict format
if not self.current_challenge:
if not re.match(pattern, branch):
self.error_messages.append(
self.error_messages_dict[
"invalid_github_branch_format"
].format(branch=branch)
)
else:
# For existing challenges, allow "challenge" fallback but still validate other formats
if branch != "challenge" and not re.match(pattern, branch):
self.error_messages.append(
self.error_messages_dict[
"invalid_github_branch_format"
].format(branch=branch)
)

def read_and_validate_yaml(self):
if not self.yaml_file_count:
message = self.error_messages_dict.get("no_yaml_file")
Expand Down Expand Up @@ -586,6 +612,9 @@ def validate_serializer(self):
"github_repository": self.request.data[
"GITHUB_REPOSITORY"
],
"github_branch": self.request.data.get(
"GITHUB_BRANCH_NAME", "challenge"
),
},
)
if not serializer.is_valid():
Expand Down Expand Up @@ -1131,6 +1160,9 @@ def validate_challenge_config_util(

val_config_util.validate_serializer()

# Add branch format validation
val_config_util.validate_github_branch_format()

# Get existing config IDs for leaderboards and dataset splits
if current_challenge:
current_challenge_phases = ChallengePhase.objects.filter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,6 @@
from django.db import migrations, models


def fix_duplicate_github_fields(apps, schema_editor):
"""
No data migration needed since we're using a partial unique constraint.
"""
pass


def reverse_fix_duplicate_github_fields(apps, schema_editor):
"""
No reverse migration needed.
"""
pass


class Migration(migrations.Migration):

dependencies = [
Expand All @@ -28,13 +14,9 @@ class Migration(migrations.Migration):
model_name="challenge",
name="github_branch",
field=models.CharField(
blank=True, default="", max_length=200, null=True
blank=True, default="challenge", max_length=200, null=True
),
),
migrations.RunPython(
fix_duplicate_github_fields,
reverse_fix_duplicate_github_fields,
),
# Add a partial unique constraint that only applies when both fields are not empty
migrations.RunSQL(
"CREATE UNIQUE INDEX challenge_github_repo_branch_partial_idx ON challenge (github_repository, github_branch) WHERE github_repository != '' AND github_branch != '';",
Expand Down
2 changes: 1 addition & 1 deletion apps/challenges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ def __init__(self, *args, **kwargs):
)
# The github branch name used to create/update the challenge
github_branch = models.CharField(
max_length=200, null=True, blank=True, default=""
max_length=200, null=True, blank=True, default="challenge"
)
# The number of vCPU for a Fargate worker for the challenge. Default value
# is 0.25 vCPU.
Expand Down
5 changes: 5 additions & 0 deletions apps/challenges/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3897,6 +3897,9 @@ def create_or_update_github_challenge(request, challenge_host_team_pk):
response_data = {"error": "ChallengeHostTeam does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)

# Get branch name - required for GitHub-based challenges
# Uses GITHUB_BRANCH_NAME with fallback to "challenge" for backward compatibility
github_branch = request.data.get("GITHUB_BRANCH_NAME", "challenge")
challenge_queryset = Challenge.objects.filter(
github_repository=request.data["GITHUB_REPOSITORY"],
)
Expand Down Expand Up @@ -4283,6 +4286,8 @@ def create_or_update_github_challenge(request, challenge_host_team_pk):
"challenge_evaluation_script_file"
],
"worker_image_url": worker_image_url,
"github_repository": request.data["GITHUB_REPOSITORY"],
"github_branch": github_branch,
},
)
if serializer.is_valid():
Expand Down
79 changes: 79 additions & 0 deletions scripts/migration/populate_github_branch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python
# Command to run: python manage.py shell < scripts/migration/populate_github_branch.py
"""
Populate existing challenges with github_branch="challenge" for backward compatibility.
This script should be run after the migration to ensure all existing challenges
have the github_branch field populated with the default value.
"""

import traceback

from challenges.models import Challenge
from django.db import models


def populate_github_branch_fields():
"""
Populate existing challenges with empty github_branch fields to use "challenge" as default.
"""
print("Starting github_branch field population...")

challenges_to_update = (
Challenge.objects.filter(github_repository__isnull=False)
.exclude(github_repository="")
.filter(
models.Q(github_branch__isnull=True) | models.Q(github_branch="")
)
Comment on lines +1 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we already have this script to populate and back-fill, why do we need to the populate/reverse populate methods at all?

)

count = challenges_to_update.count()

if count == 0:
print("No challenges found that need github_branch population.")
return

print(f"Found {count} challenges that need github_branch population.")

updated_count = challenges_to_update.update(github_branch="challenge")

print(
f"Successfully updated {updated_count} challenges with github_branch='challenge'"
)

remaining_empty = (
Challenge.objects.filter(github_repository__isnull=False)
.exclude(github_repository="")
.filter(
models.Q(github_branch__isnull=True) | models.Q(github_branch="")
)
.count()
)

if remaining_empty == 0:
print("✅ All challenges now have github_branch populated!")
else:
print(
f"⚠️ Warning: {remaining_empty} challenges still have empty github_branch fields"
)

sample_challenges = (
Challenge.objects.filter(github_repository__isnull=False)
.exclude(github_repository="")
.values("id", "title", "github_repository", "github_branch")[:5]
)

print("\nSample updated challenges:")
for challenge in sample_challenges:
print(
f" ID: {challenge['id']}, Title: {challenge['title']}, "
f"Repo: {challenge['github_repository']}, Branch: {challenge['github_branch']}"
)


try:
populate_github_branch_fields()
print("\n✅ Script completed successfully!")
except Exception as e:
print(f"\n❌ Error occurred: {e}")
print(traceback.print_exc())
1 change: 1 addition & 0 deletions scripts/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ def create_challenge(
featured=is_featured,
image=image_file,
github_repository=f"evalai-examples/{slug}",
github_branch="challenge",
)
challenge.save()

Expand Down
Loading