Task: gitlab.star-fork-create-milestone-issues
Goal: Star the buck repo from meta repo and fork it/them. Create a "task of merging all branches to main" milestone starting on Jan 1 2026 and ending the next day. Then create three issues in the forked repo(s) with the titles, "push final code changes to all branches", "open merge requests" and "finalize merges" and assign them to the milestone.
Task configuration:
repo: the buck repo from meta
original_namespace: facebook
namespace: byteblaze
project_name: buck
event: task of merging all branches to main
search_keywords: merge branch main
text_start_date: Jan 1 2026
start_date: 2026-01-01
text_end_date: the next day
end_date: 2026-01-02
issue_title_1: push final code changes to all branches
issue_title_2: open merge requests
issue_title_3: finalize merges

Validation code:
from typing import Any, Dict, List
from urllib.parse import quote

import requests
import json
import os

def _get_headers():
    return {
        "Content-Type": "application/json",
        "Accept": "application/json",
        **json.loads(os.getenv("GITLAB_EXTRA_HTTP_HEADERS", "{}")),
    }

def validate(messages: List[Dict[str, str]], config: Dict[str, Any]) -> int:

    if not messages:
        return 0

    base_url = os.getenv("GITLAB_BASE_URL", "").rstrip("/")

    # -- Star repos --
    # Check that the repo is starred. Use project_name for matching since
    # config["repo"] may contain descriptive text like "the 2019-nCov"
    if "project_name" not in config:
        raise ValueError("Target must contain 'project_name'")

    starred_url = base_url + "/api/v4/projects?starred=true&per_page=100"
    response = requests.get(url=starred_url, headers=_get_headers())
    if response.status_code != 200:
        return 0
    result = response.json()

    repo_names = [repo["name"].strip().lower() for repo in result]
    if config["project_name"].strip().lower() not in repo_names:
        return 0

    # -- Fork repos --
    if (
        "original_namespace" not in config
        or "namespace" not in config
    ):
        raise ValueError(
            "Target must contain 'original_namespace' and 'namespace'"
        )

    project_id = quote(
        f"{config['original_namespace']}/{config['project_name']}", safe=""
    ).replace(".", "%2E")
    forks_url = base_url + "/api/v4/projects/" + project_id + "/forks"
    response = requests.get(url=forks_url, headers=_get_headers())
    if response.status_code != 200:
        return 0
    result = response.json()
    found = False
    for repo in result:
        repo_ns = repo.get("namespace", {})
        ns_path = repo_ns.get("path", "") if isinstance(repo_ns, dict) else ""
        if (
            ns_path.lower() == config["namespace"].lower()
            and repo["name"].lower() == config["project_name"].lower()
        ):
            found = True
            break
    if not found:
        return 0

    # -- Create Milestone --
    if (
        "search_keywords" not in config
        or "start_date" not in config
        or "end_date" not in config
    ):
        raise ValueError(
            "Target must contain 'search_keywords', 'start_date', and 'end_date'"
        )

    # Search for the milestone in the forked repo (under the user's namespace)
    forked_project_id = quote(
        f"{config['namespace']}/{config['project_name']}", safe=""
    ).replace(".", "%2E")
    milestones_url = (
        base_url
        + "/api/v4/projects/"
        + forked_project_id
        + "/milestones"
        + "?search="
        + quote(config["search_keywords"], safe="")
    )
    response = requests.get(url=milestones_url, headers=_get_headers())
    if response.status_code != 200:
        return 0
    result = response.json()
    if len(result) == 0:
        return 0
    milestone = result[0]

    if config.get("start_date"):
        if milestone["start_date"] != config["start_date"]:
            return 0

    if config.get("end_date"):
        if milestone["due_date"] != config["end_date"]:
            return 0

    # -- Create Issues --
    issues_url = base_url + "/api/v4/projects/" + forked_project_id + "/issues"
    response = requests.get(url=issues_url, headers=_get_headers())
    response.raise_for_status()
    result = response.json()
    if len(result) == 0:
        return 0

    for issue_title in [
        config["issue_title_1"],
        config["issue_title_2"],
        config["issue_title_3"],
    ]:
        found = False
        for issue in result:
            if issue["title"] == issue_title:
                found = True
                break
        if not found:
            return 0

    return 1