From 9585de831a5f9b18f3b7e7bc35fbb46557428535 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 14:30:42 +0100 Subject: [PATCH 01/21] Updated naming and changed behavior from making direct commit to creating a PR --- .../ReleaseAutomation/release_config.py | 29 ++++++++- .../run_release_preparation.py | 8 +-- ...sionUpdates.py => createPrAfterRelease.py} | 15 +++-- Tools/scripts/Utils/general_utils.py | 46 ++----------- Tools/scripts/Utils/git_utils.py | 4 +- Tools/scripts/release.py | 65 ++++++++++++++++--- 6 files changed, 104 insertions(+), 63 deletions(-) rename Tools/scripts/Utils/{commitChangelogAndPackageVersionUpdates.py => createPrAfterRelease.py} (81%) diff --git a/Tools/scripts/ReleaseAutomation/release_config.py b/Tools/scripts/ReleaseAutomation/release_config.py index b91f6201d4..141671762e 100644 --- a/Tools/scripts/ReleaseAutomation/release_config.py +++ b/Tools/scripts/ReleaseAutomation/release_config.py @@ -27,6 +27,23 @@ def is_branch_present(self, branch_name): if ghe.status == 404: return False # Branch does not exist raise Exception(f"An error occurred with the GitHub API: {ghe.status}", data=ghe.data) + + def create_pull_request(self, title, body, head, base): + try: + return self.repo.create_pull(title=title, body=body, head=head, base=base) + + except GithubException as ghe: + raise Exception(f"Failed to create pull request: {ghe.status}", ghe.data) from ghe + + def request_reviews(self, pr, reviewers): + if not reviewers: + return + + try: + pr.create_review_request(reviewers=reviewers) + except GithubException as ghe: + raise Exception(f"Failed to request reviews: {ghe.status}", ghe.data) from ghe + class ReleaseConfig: """A simple class to hold all shared configuration.""" @@ -45,7 +62,17 @@ def __init__(self): self.package_version = get_package_version_from_manifest(self.manifest_path) self.release_branch_name = f"release/{self.package_version}" # Branch from which we want to release - self.commit_message = f"Updated changelog and package version for Netcode in anticipation of v{self.package_version} release" + + self.release_commit_message = f"Updated changelog and package version for Netcode in anticipation of v{self.package_version} release" + + self.pr_branch_name = f"netcode-update-after-{self.package_version}-release-branch-creation" # Branch from which we will create PR to default branch with relevant changes after release branch is created + self.pr_commit_message = f"Updated aspects of Netcode package in anticipation of v{self.package_version} release" + self.pr_body = f"This PR was created in sync with branching of {self.release_branch_name}. It includes changes that should land on the default Netcode branch ({self.default_repo_branch}) to reflect the new state of the package after the v{self.package_version} release:\n" \ + f"1) Updated CHANGELOG.md by adding new [Unreleased] section template at the top and cleaning the Changelog for the current release.\n" \ + f"2) Updated package version in package.json by incrementing the patch version to signify the current state of the package.\n" \ + f"3) Updated package version in ValidationExceptions.json to match the new package version.\n\n" \ + f"Please review and merge this PR to keep the default branch up to date with the latest package state after the release. Those changes can land immediately OR after the release was finalized but make sure that the Changelog will be merged correctly as sometimes some discrepancies may be introduced due to new entries being introduced meantime\n" + self.pr_reviewers = ["michal-chrobot"] GITHUB_TOKEN_NAME = "NETCODE_GITHUB_TOKEN" YAMATO_API_KEY_NAME = "NETCODE_YAMATO_API_KEY" diff --git a/Tools/scripts/ReleaseAutomation/run_release_preparation.py b/Tools/scripts/ReleaseAutomation/run_release_preparation.py index 562680f8d3..a043cbe491 100644 --- a/Tools/scripts/ReleaseAutomation/run_release_preparation.py +++ b/Tools/scripts/ReleaseAutomation/run_release_preparation.py @@ -7,9 +7,9 @@ sys.path.insert(0, PARENT_DIR) from ReleaseAutomation.release_config import ReleaseConfig -from Utils.git_utils import create_branch_execute_commands_and_push +from Utils.git_utils import create_release_branch from Utils.verifyReleaseConditions import verifyReleaseConditions -from Utils.commitChangelogAndPackageVersionUpdates import commitChangelogAndPackageVersionUpdates +from Utils.createPrAfterRelease import createPrAfterRelease from Utils.triggerYamatoJobsForReleasePreparation import trigger_release_preparation_jobs def PrepareNetcodePackageForRelease(): @@ -27,13 +27,13 @@ def PrepareNetcodePackageForRelease(): try: print("\nStep 2: Creating release branch...") - create_branch_execute_commands_and_push(config) + create_release_branch(config) print("\nStep 3: Triggering Yamato validation jobs...") trigger_release_preparation_jobs(config) print("\nStep 4: Committing changelog and version updates...") - commitChangelogAndPackageVersionUpdates(config) + createPrAfterRelease(config) except Exception as e: print("\n--- ERROR: Netcode release process failed ---", file=sys.stderr) diff --git a/Tools/scripts/Utils/commitChangelogAndPackageVersionUpdates.py b/Tools/scripts/Utils/createPrAfterRelease.py similarity index 81% rename from Tools/scripts/Utils/commitChangelogAndPackageVersionUpdates.py rename to Tools/scripts/Utils/createPrAfterRelease.py index f58d461216..6e09175729 100644 --- a/Tools/scripts/Utils/commitChangelogAndPackageVersionUpdates.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -19,7 +19,7 @@ from Utils.general_utils import get_package_version_from_manifest, update_changelog, update_package_version_by_patch, update_validation_exceptions from Utils.git_utils import get_local_repo -def commitChangelogAndPackageVersionUpdates(config: ReleaseConfig): +def createPrAfterRelease(config: ReleaseConfig): """ The function updates the changelog and package version of the package in anticipation of a new release. This means that it will @@ -40,6 +40,9 @@ def commitChangelogAndPackageVersionUpdates(config: ReleaseConfig): repo.git.fetch('--prune', '--prune-tags') repo.git.checkout(config.default_repo_branch) repo.git.pull("origin", config.default_repo_branch) + + # Create a new branch for the release changes PR to default branch + repo.git.checkout('-b', config.pr_branch_name) # Update the changelog file with adding new [Unreleased] section update_changelog(config.changelog_path, config.package_version, add_unreleased_template=True) @@ -54,10 +57,14 @@ def commitChangelogAndPackageVersionUpdates(config: ReleaseConfig): author = Actor(config.commiter_name, config.commiter_email) committer = Actor(config.commiter_name, config.commiter_email) - repo.index.commit(config.commit_message, author=author, committer=committer, skip_hooks=True) - repo.git.push("origin", config.default_repo_branch) + repo.index.commit(config.pr_commit_message, author=author, committer=committer, skip_hooks=True) + repo.git.push("origin", config.pr_branch_name) - print(f"Successfully updated and pushed the changelog on branch: {config.default_repo_branch}") + github = config.github_manager + pr = github.create_pull_request(title=config.pr_commit_message, body=config.pr_body, head=config.pr_branch_name, base=config.default_repo_branch) + github.request_reviews(pr, config.pr_reviewers) + + print(f"Successfully updated and created the PR targeting: {config.default_repo_branch}") except GithubException as e: print(f"An error occurred with the GitHub API: {e.status}", file=sys.stderr) diff --git a/Tools/scripts/Utils/general_utils.py b/Tools/scripts/Utils/general_utils.py index 19208ee1b6..9b49f2541e 100644 --- a/Tools/scripts/Utils/general_utils.py +++ b/Tools/scripts/Utils/general_utils.py @@ -6,6 +6,7 @@ import datetime import platform import subprocess +import warnings UNRELEASED_CHANGELOG_SECTION_TEMPLATE = r""" ## [Unreleased] @@ -37,8 +38,7 @@ def get_package_version_from_manifest(package_manifest_path): """ if not os.path.exists(package_manifest_path): - print("get_manifest_json_version function couldn't find a specified manifest_path") - return None + raise FileNotFoundError(f"{package_manifest_path} couldn't be find") with open(package_manifest_path, 'rb') as f: json_text = f.read() @@ -75,42 +75,6 @@ def update_package_version_by_patch(package_manifest_path): json.dump(package_manifest, f, indent=4) return new_package_version - - -def regenerate_wrench(): - """ - It runs Tools/regenerate-ci.cmd OR Tools/regenerate-ci.sh script - to regenerate the CI files. (depending on the OS) - - This is needed because wrench scripts content is created dynamically depending on the available editors - """ - - # --- Regenerate the CI files --- - print("\nRegenerating CI files...") - script_path = "" - if platform.system() == "Windows": - script_path = os.path.join('Tools', 'CI', 'regenerate.bat') - else: # macOS and Linux - script_path = os.path.join('Tools', 'CI', 'regenerate.sh') - - if not os.path.exists(script_path): - print(f"Error: Regeneration script not found at '{script_path}'.") - return - - try: - # Execute the regeneration script - # On non-Windows systems, the script might need execute permissions. - if platform.system() != "Windows": - os.chmod(script_path, 0o755) - - print(f"Running '{script_path}'...") - subprocess.run([script_path], check=True, shell=True) - print("CI regeneration completed successfully.") - - except subprocess.CalledProcessError as e: - print(f"Error: The CI regeneration script failed with exit code {e.returncode}.") - except Exception as e: - print(f"An unexpected error occurred while running the regeneration script: {e}") def update_validation_exceptions(validation_file, package_version): @@ -140,13 +104,11 @@ def update_validation_exceptions(validation_file, package_version): # If no exceptions were updated, we do not need to write the file if not updated: - print(f"No validation exceptions were updated in {validation_file}.") - return + raise FileNotFoundError(f"No validation exceptions were updated in {validation_file}.") with open(validation_file, 'w', encoding='UTF-8', newline='\n') as json_file: json.dump(data, json_file, ensure_ascii=False, indent=2) json_file.write("\n") # Add newline cause Py JSON does not - print(f"updated `{validation_file}`") @@ -178,7 +140,7 @@ def update_changelog(changelog_path, new_version, add_unreleased_template=False) cleaned_content = pattern.sub('', changelog_text) if version_header_to_find_if_exists in changelog_text: - print(f"A changelog entry for version '{new_version}' already exists. The script will just remove Unreleased section and its content.") + warnings.warn(f"A changelog entry for version '{new_version}' already exists. The script will just remove Unreleased section and its content.") changelog_text = re.sub(r'(?s)## \[Unreleased(.*?)(?=## \[)', '', changelog_text) else: # Replace the [Unreleased] section with the new version + cleaned subsections diff --git a/Tools/scripts/Utils/git_utils.py b/Tools/scripts/Utils/git_utils.py index 7ead099710..4776d83e6a 100644 --- a/Tools/scripts/Utils/git_utils.py +++ b/Tools/scripts/Utils/git_utils.py @@ -41,7 +41,7 @@ def get_latest_git_revision(branch_name): except subprocess.CalledProcessError as e: raise Exception(f"Failed to get the latest revision for branch '{branch_name}'.") from e -def create_branch_execute_commands_and_push(config: ReleaseConfig): +def create_release_branch(config: ReleaseConfig): """ Creates a new branch with the specified name, performs specified action, commits the current changes and pushes it to the repo. Note that command_to_run_on_release_branch (within the Config) should be a single command that will be executed using subprocess.run. For multiple commands consider using a Python script file. @@ -67,7 +67,7 @@ def create_branch_execute_commands_and_push(config: ReleaseConfig): author = Actor(config.commiter_name, config.commiter_email) committer = Actor(config.commiter_name, config.commiter_email) - repo.index.commit(config.commit_message, author=author, committer=committer, skip_hooks=True) + repo.index.commit(config.release_commit_message, author=author, committer=committer, skip_hooks=True) repo.git.push("origin", config.release_branch_name) print(f"Successfully created, updated and pushed new branch: {config.release_branch_name}") diff --git a/Tools/scripts/release.py b/Tools/scripts/release.py index c81082e563..2cd9f2cf6d 100644 --- a/Tools/scripts/release.py +++ b/Tools/scripts/release.py @@ -13,7 +13,40 @@ import subprocess import platform -from Utils.general_utils import get_package_version_from_manifest, update_changelog, update_validation_exceptions, regenerate_wrench # nopep8 +from Utils.general_utils import get_package_version_from_manifest, update_changelog, update_validation_exceptions # nopep8 + +def regenerate_wrench(): + """ + It runs Tools/regenerate-ci.cmd OR Tools/regenerate-ci.sh script + to regenerate the CI files. (depending on the OS) + + This is needed because wrench scripts content is created dynamically depending on the available editors + """ + + # --- Regenerate the CI files --- + print("\nRegenerating CI files...") + script_path = "" + if platform.system() == "Windows": + script_path = os.path.join('Tools', 'CI', 'regenerate.bat') + else: # macOS and Linux + script_path = os.path.join('Tools', 'CI', 'regenerate.sh') + + if not os.path.exists(script_path): + raise FileNotFoundError(f"Error: Regeneration script not found at '{script_path}'.") + + try: + # Execute the regeneration script + # On non-Windows systems, the script might need execute permissions. + if platform.system() != "Windows": + os.chmod(script_path, 0o755) + + subprocess.run([script_path], check=True, shell=True) + + except subprocess.CalledProcessError as e: + raise Exception(f"Error: The CI regeneration script failed with exit code {e.returncode}.") + except Exception as e: + raise Exception(f"An unexpected error occurred while running the regeneration script: {e}") + def make_package_release_ready(manifest_path, changelog_path, validation_exceptions_path, package_version): @@ -24,20 +57,32 @@ def make_package_release_ready(manifest_path, changelog_path, validation_excepti if not os.path.exists(changelog_path): print(f" Path does not exist: {changelog_path}") sys.exit(1) + + if not os.path.exists(validation_exceptions_path): + print(f" Path does not exist: {validation_exceptions_path}") + sys.exit(1) if package_version is None: print(f"Package version not found at {manifest_path}") sys.exit(1) - # Update the ValidationExceptions.json file - # with the new package version OR remove it if not a release branch - update_validation_exceptions(validation_exceptions_path, package_version) - # Clean the CHANGELOG and add latest entry - # package version is already know as explained in - # https://github.cds.internal.unity3d.com/unity/dots/pull/14318 - update_changelog(changelog_path, package_version) - # Make sure that the wrench scripts are up to date - regenerate_wrench() + try: + # Update the ValidationExceptions.json file + # with the new package version OR remove it if not a release branch + update_validation_exceptions(validation_exceptions_path, package_version) + # Clean the CHANGELOG and add latest entry + # package version is already know as explained in + # https://github.cds.internal.unity3d.com/unity/dots/pull/14318 + update_changelog(changelog_path, package_version) + # Make sure that the wrench scripts are up to date + regenerate_wrench() + + except Exception as e: + print(f"An unexpected error occurred: {e}", file=sys.stderr) + sys.exit(1) + + + if __name__ == '__main__': From 6f1ea8a3b2bf36588544de58c77800fce359a947 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 14:38:05 +0100 Subject: [PATCH 02/21] re-enabled job trigger --- .yamato/ngo-publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.yamato/ngo-publish.yml b/.yamato/ngo-publish.yml index e3a73a87dc..81f9f57b3b 100644 --- a/.yamato/ngo-publish.yml +++ b/.yamato/ngo-publish.yml @@ -1,10 +1,10 @@ ngo_release_preparation: name: "NGO release preparation" agent: { type: Unity::VM, flavor: b1.small, image: package-ci/ubuntu-22.04:v4 } - #triggers: - # recurring: - # - branch: develop-2.0.0 # We make new releases from this branch - # frequency: "10 ? * 1" # Runs every Sunday at 10:00 AM + triggers: + recurring: + - branch: develop-2.0.0 # We make new releases from this branch + frequency: "10 ? * 1" # Runs every Sunday at 10:00 AM commands: - pip install PyGithub - pip install GitPython From 6e34e825e684ce6ed62c9486400c8b1cd64d99e5 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 14:45:54 +0100 Subject: [PATCH 03/21] test change added --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 06109ea401..9dff48cd14 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- test addition ### Changed From 06cd631f938649551e417ba45f53219ac8d26b45 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 14:55:00 +0100 Subject: [PATCH 04/21] corrected error --- Tools/scripts/Utils/general_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tools/scripts/Utils/general_utils.py b/Tools/scripts/Utils/general_utils.py index 9b49f2541e..0b822ab8d9 100644 --- a/Tools/scripts/Utils/general_utils.py +++ b/Tools/scripts/Utils/general_utils.py @@ -104,7 +104,8 @@ def update_validation_exceptions(validation_file, package_version): # If no exceptions were updated, we do not need to write the file if not updated: - raise FileNotFoundError(f"No validation exceptions were updated in {validation_file}.") + warnings.warn(f"No validation exceptions were updated in {validation_file}.") + return with open(validation_file, 'w', encoding='UTF-8', newline='\n') as json_file: json.dump(data, json_file, ensure_ascii=False, indent=2) From 42ab07bbf4025bd4f942332e84e1bb79744370a8 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 15:10:06 +0100 Subject: [PATCH 05/21] Updated CI regeneration scripts --- Tools/CI/regenerate.bat | 2 -- Tools/CI/regenerate.sh | 2 -- Tools/regenerate-ci.cmd | 4 ++++ Tools/regenerate-ci.sh | 6 ++++++ Tools/scripts/release.py | 6 +++--- 5 files changed, 13 insertions(+), 7 deletions(-) delete mode 100644 Tools/CI/regenerate.bat delete mode 100644 Tools/CI/regenerate.sh create mode 100644 Tools/regenerate-ci.cmd create mode 100644 Tools/regenerate-ci.sh diff --git a/Tools/CI/regenerate.bat b/Tools/CI/regenerate.bat deleted file mode 100644 index 33c9c9fb12..0000000000 --- a/Tools/CI/regenerate.bat +++ /dev/null @@ -1,2 +0,0 @@ -cd %~dp0../../ -dotnet run --project Tools\CI\NGO.Cookbook.csproj \ No newline at end of file diff --git a/Tools/CI/regenerate.sh b/Tools/CI/regenerate.sh deleted file mode 100644 index ef2be6b8d8..0000000000 --- a/Tools/CI/regenerate.sh +++ /dev/null @@ -1,2 +0,0 @@ -cd $(dirname "$0")/../../ -dotnet run --project Tools\CI\NGO.Cookbook.csproj \ No newline at end of file diff --git a/Tools/regenerate-ci.cmd b/Tools/regenerate-ci.cmd new file mode 100644 index 0000000000..0277203f46 --- /dev/null +++ b/Tools/regenerate-ci.cmd @@ -0,0 +1,4 @@ +@echo off + +dotnet run -c Release --project %~dp0\CI\NGO.Cookbook.csproj %* +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/Tools/regenerate-ci.sh b/Tools/regenerate-ci.sh new file mode 100644 index 0000000000..638e6d0d80 --- /dev/null +++ b/Tools/regenerate-ci.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e # fail on first error + +SCRIPT_DIR=$(dirname "$0") +dotnet run -c Release --project $SCRIPT_DIR/CI/NGO.Cookbook.csproj "$@" \ No newline at end of file diff --git a/Tools/scripts/release.py b/Tools/scripts/release.py index 2cd9f2cf6d..67a1ca297f 100644 --- a/Tools/scripts/release.py +++ b/Tools/scripts/release.py @@ -27,9 +27,9 @@ def regenerate_wrench(): print("\nRegenerating CI files...") script_path = "" if platform.system() == "Windows": - script_path = os.path.join('Tools', 'CI', 'regenerate.bat') + script_path = os.path.join('Tools', 'regenerate-ci.cmd') else: # macOS and Linux - script_path = os.path.join('Tools', 'CI', 'regenerate.sh') + script_path = os.path.join('Tools', 'regenerate-ci.sh') if not os.path.exists(script_path): raise FileNotFoundError(f"Error: Regeneration script not found at '{script_path}'.") @@ -40,7 +40,7 @@ def regenerate_wrench(): if platform.system() != "Windows": os.chmod(script_path, 0o755) - subprocess.run([script_path], check=True, shell=True) + subprocess.run(script_path, check=True, shell=True) except subprocess.CalledProcessError as e: raise Exception(f"Error: The CI regeneration script failed with exit code {e.returncode}.") From 8dd9cb4744fa05be272b8f67e776bb49958cf059 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 15:17:02 +0100 Subject: [PATCH 06/21] Updated PR description validation check to exclude automations --- .github/workflows/pr-description-validation.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/pr-description-validation.yml b/.github/workflows/pr-description-validation.yml index c5a727ffdb..277248d2da 100644 --- a/.github/workflows/pr-description-validation.yml +++ b/.github/workflows/pr-description-validation.yml @@ -29,6 +29,21 @@ jobs: script: | const pr = context.payload.pull_request; const body = pr.body || ''; + + // List of users to skip description validation + // This should be automations where we don't care that much about the description format + const skipUsersPrefixes = [ + 'unity-renovate', + 'svc-' + ]; + + // If PR author is in the skip list, exit early + const author = pr.user.login; + console.log(`PR author: ${author}`); + if (skipUsersPrefixes.some(prefix => author.startsWith(prefix))) { + console.log(`Skipping PR description check for user: ${author}`); + return; + } // List of mandatory PR sections const requiredSections = [ From 937cb42427d070cbb24680f847b0054e71e46dd7 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 15:18:42 +0100 Subject: [PATCH 07/21] corrected PR title --- Tools/scripts/ReleaseAutomation/release_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/scripts/ReleaseAutomation/release_config.py b/Tools/scripts/ReleaseAutomation/release_config.py index 141671762e..0aab8d0940 100644 --- a/Tools/scripts/ReleaseAutomation/release_config.py +++ b/Tools/scripts/ReleaseAutomation/release_config.py @@ -66,7 +66,7 @@ def __init__(self): self.release_commit_message = f"Updated changelog and package version for Netcode in anticipation of v{self.package_version} release" self.pr_branch_name = f"netcode-update-after-{self.package_version}-release-branch-creation" # Branch from which we will create PR to default branch with relevant changes after release branch is created - self.pr_commit_message = f"Updated aspects of Netcode package in anticipation of v{self.package_version} release" + self.pr_commit_message = f"chore: Updated aspects of Netcode package in anticipation of v{self.package_version} release" self.pr_body = f"This PR was created in sync with branching of {self.release_branch_name}. It includes changes that should land on the default Netcode branch ({self.default_repo_branch}) to reflect the new state of the package after the v{self.package_version} release:\n" \ f"1) Updated CHANGELOG.md by adding new [Unreleased] section template at the top and cleaning the Changelog for the current release.\n" \ f"2) Updated package version in package.json by incrementing the patch version to signify the current state of the package.\n" \ From ba6e3a76082f03c7499ac808f92ccc5553626974 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 15:22:13 +0100 Subject: [PATCH 08/21] corrected CI typo --- .yamato/project.metafile | 1 + 1 file changed, 1 insertion(+) diff --git a/.yamato/project.metafile b/.yamato/project.metafile index f434d36106..2ddd060521 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -84,6 +84,7 @@ test_platforms: type: Unity::VM::osx image: package-ci/macos-13:v4 flavor: m1.mac + larger_flavor: m1.mac standalone: IOS base: mac architecture: arm64 From 665879beadd04c5aa32d102ff3aac98321590410 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 15:34:32 +0100 Subject: [PATCH 09/21] added temp bypass --- Tools/scripts/ReleaseAutomation/run_release_preparation.py | 2 +- Tools/scripts/Utils/createPrAfterRelease.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Tools/scripts/ReleaseAutomation/run_release_preparation.py b/Tools/scripts/ReleaseAutomation/run_release_preparation.py index a043cbe491..b15e23d4d2 100644 --- a/Tools/scripts/ReleaseAutomation/run_release_preparation.py +++ b/Tools/scripts/ReleaseAutomation/run_release_preparation.py @@ -32,7 +32,7 @@ def PrepareNetcodePackageForRelease(): print("\nStep 3: Triggering Yamato validation jobs...") trigger_release_preparation_jobs(config) - print("\nStep 4: Committing changelog and version updates...") + print("\nStep 4: Creating PR with needed changes to default branch...") createPrAfterRelease(config) except Exception as e: diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index 6e09175729..b6f4da387d 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -53,6 +53,7 @@ def createPrAfterRelease(config: ReleaseConfig): repo.git.add(config.changelog_path) repo.git.add(config.manifest_path) repo.git.add(config.validation_exceptions_path) + repo.git.add("Tools/regenerate-ci.sh") author = Actor(config.commiter_name, config.commiter_email) committer = Actor(config.commiter_name, config.commiter_email) From b8d573e451abf28ef446a133204d9c066dafaf09 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 15:51:37 +0100 Subject: [PATCH 10/21] Updated meta --- Tools/regenerate-ci.sh | 7 +------ Tools/scripts/Utils/createPrAfterRelease.py | 4 +--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Tools/regenerate-ci.sh b/Tools/regenerate-ci.sh index 638e6d0d80..362d04e927 100644 --- a/Tools/regenerate-ci.sh +++ b/Tools/regenerate-ci.sh @@ -1,6 +1 @@ -#!/bin/sh - -set -e # fail on first error - -SCRIPT_DIR=$(dirname "$0") -dotnet run -c Release --project $SCRIPT_DIR/CI/NGO.Cookbook.csproj "$@" \ No newline at end of file +#!/bin/sh set -e # fail on first error SCRIPT_DIR=$(dirname "$0") dotnet run -c Release --project $SCRIPT_DIR/CI/NGO.Cookbook.csproj "$@" \ No newline at end of file diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index b6f4da387d..c3435b9a84 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -33,8 +33,7 @@ def createPrAfterRelease(config: ReleaseConfig): try: if not config.github_manager.is_branch_present(config.default_repo_branch): - print(f"Branch '{config.default_repo_branch}' does not exist. Exiting.") - sys.exit(1) + raise Exception(f"Branch '{config.default_repo_branch}' does not exist. Exiting.") repo = get_local_repo() repo.git.fetch('--prune', '--prune-tags') @@ -53,7 +52,6 @@ def createPrAfterRelease(config: ReleaseConfig): repo.git.add(config.changelog_path) repo.git.add(config.manifest_path) repo.git.add(config.validation_exceptions_path) - repo.git.add("Tools/regenerate-ci.sh") author = Actor(config.commiter_name, config.commiter_email) committer = Actor(config.commiter_name, config.commiter_email) From 8d026407375a7649e8ce78b2271d5b1d7183a669 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 16:55:26 +0100 Subject: [PATCH 11/21] typo --- Tools/regenerate-ci.sh | 7 ++++++- Tools/scripts/release.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Tools/regenerate-ci.sh b/Tools/regenerate-ci.sh index 362d04e927..638e6d0d80 100644 --- a/Tools/regenerate-ci.sh +++ b/Tools/regenerate-ci.sh @@ -1 +1,6 @@ -#!/bin/sh set -e # fail on first error SCRIPT_DIR=$(dirname "$0") dotnet run -c Release --project $SCRIPT_DIR/CI/NGO.Cookbook.csproj "$@" \ No newline at end of file +#!/bin/sh + +set -e # fail on first error + +SCRIPT_DIR=$(dirname "$0") +dotnet run -c Release --project $SCRIPT_DIR/CI/NGO.Cookbook.csproj "$@" \ No newline at end of file diff --git a/Tools/scripts/release.py b/Tools/scripts/release.py index 67a1ca297f..4693d90bc9 100644 --- a/Tools/scripts/release.py +++ b/Tools/scripts/release.py @@ -40,7 +40,7 @@ def regenerate_wrench(): if platform.system() != "Windows": os.chmod(script_path, 0o755) - subprocess.run(script_path, check=True, shell=True) + subprocess.run([script_path], check=True, shell=True) except subprocess.CalledProcessError as e: raise Exception(f"Error: The CI regeneration script failed with exit code {e.returncode}.") From f34eab3f8e6e4c96a222f5f0dc3fcac89376fbdc Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 29 Dec 2025 20:05:32 +0100 Subject: [PATCH 12/21] corrected regenerate scripts --- Tools/regenerate-ci.cmd | 3 ++- Tools/regenerate-ci.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Tools/regenerate-ci.cmd b/Tools/regenerate-ci.cmd index 0277203f46..f31f7122c1 100644 --- a/Tools/regenerate-ci.cmd +++ b/Tools/regenerate-ci.cmd @@ -1,4 +1,5 @@ @echo off -dotnet run -c Release --project %~dp0\CI\NGO.Cookbook.csproj %* +cd /d "%~dp0.." +dotnet run --project "Tools\CI\NGO.Cookbook.csproj" %* if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/Tools/regenerate-ci.sh b/Tools/regenerate-ci.sh index 638e6d0d80..910d547122 100644 --- a/Tools/regenerate-ci.sh +++ b/Tools/regenerate-ci.sh @@ -3,4 +3,4 @@ set -e # fail on first error SCRIPT_DIR=$(dirname "$0") -dotnet run -c Release --project $SCRIPT_DIR/CI/NGO.Cookbook.csproj "$@" \ No newline at end of file +dotnet run --project $SCRIPT_DIR/CI/NGO.Cookbook.csproj "$@" \ No newline at end of file From b41584a8fd37843b6c5568f701fbdd97f42828cf Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Tue, 30 Dec 2025 01:02:21 +0100 Subject: [PATCH 13/21] test --- Tools/scripts/Utils/createPrAfterRelease.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index c3435b9a84..7bf2cbf349 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -35,9 +35,14 @@ def createPrAfterRelease(config: ReleaseConfig): if not config.github_manager.is_branch_present(config.default_repo_branch): raise Exception(f"Branch '{config.default_repo_branch}' does not exist. Exiting.") + author = Actor(config.commiter_name, config.commiter_email) + committer = Actor(config.commiter_name, config.commiter_email) + repo = get_local_repo() repo.git.fetch('--prune', '--prune-tags') repo.git.checkout(config.default_repo_branch) + repo.git.add('Tools/regenerate-ci.sh') + repo.index.commit(config.pr_commit_message, author=author, committer=committer, skip_hooks=True) repo.git.pull("origin", config.default_repo_branch) # Create a new branch for the release changes PR to default branch @@ -53,9 +58,6 @@ def createPrAfterRelease(config: ReleaseConfig): repo.git.add(config.manifest_path) repo.git.add(config.validation_exceptions_path) - author = Actor(config.commiter_name, config.commiter_email) - committer = Actor(config.commiter_name, config.commiter_email) - repo.index.commit(config.pr_commit_message, author=author, committer=committer, skip_hooks=True) repo.git.push("origin", config.pr_branch_name) From f40307ed49efcc86d6ecf64a0bf65c8ae9bb112f Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Sat, 3 Jan 2026 11:35:18 +0100 Subject: [PATCH 14/21] Stashing changes before pulling origin --- Tools/scripts/Utils/createPrAfterRelease.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index 7bf2cbf349..da1ac2eb3a 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -40,9 +40,15 @@ def createPrAfterRelease(config: ReleaseConfig): repo = get_local_repo() repo.git.fetch('--prune', '--prune-tags') + + # Check if there are uncommitted changes that would block checkout + # Stash them if they exist to allow checkout to proceed + has_uncommitted_changes = repo.is_dirty() + if has_uncommitted_changes: + print("Uncommitted changes detected. Stashing before checkout...") + repo.git.stash('push', '-m', 'Auto-stash before checkout for release PR creation') + repo.git.checkout(config.default_repo_branch) - repo.git.add('Tools/regenerate-ci.sh') - repo.index.commit(config.pr_commit_message, author=author, committer=committer, skip_hooks=True) repo.git.pull("origin", config.default_repo_branch) # Create a new branch for the release changes PR to default branch From 6e08459a7623295f35774b1c4adb84b7ed142308 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 5 Jan 2026 10:55:55 +0100 Subject: [PATCH 15/21] Updated flow to use trigger branch as base for releasing --- .../ReleaseAutomation/release_config.py | 4 +- Tools/scripts/Utils/createPrAfterRelease.py | 43 +++++++++++++------ Tools/scripts/Utils/git_utils.py | 14 ++++++ .../scripts/Utils/verifyReleaseConditions.py | 29 +++++++++++++ 4 files changed, 75 insertions(+), 15 deletions(-) diff --git a/Tools/scripts/ReleaseAutomation/release_config.py b/Tools/scripts/ReleaseAutomation/release_config.py index 0aab8d0940..f78465acca 100644 --- a/Tools/scripts/ReleaseAutomation/release_config.py +++ b/Tools/scripts/ReleaseAutomation/release_config.py @@ -65,9 +65,9 @@ def __init__(self): self.release_commit_message = f"Updated changelog and package version for Netcode in anticipation of v{self.package_version} release" - self.pr_branch_name = f"netcode-update-after-{self.package_version}-release-branch-creation" # Branch from which we will create PR to default branch with relevant changes after release branch is created + self.pr_branch_name = f"netcode-update-after-{self.package_version}-release-branch-creation" # Branch from which we will create PR to trigger branch with relevant changes after release branch is created self.pr_commit_message = f"chore: Updated aspects of Netcode package in anticipation of v{self.package_version} release" - self.pr_body = f"This PR was created in sync with branching of {self.release_branch_name}. It includes changes that should land on the default Netcode branch ({self.default_repo_branch}) to reflect the new state of the package after the v{self.package_version} release:\n" \ + self.pr_body = f"This PR was created in sync with branching of {self.release_branch_name}. It includes changes that should land on the branch this job was triggered from which usually is {self.default_repo_branch} (please double check if the target branch is different if this was intended) to reflect the new state of the package after the v{self.package_version} release:\n" \ f"1) Updated CHANGELOG.md by adding new [Unreleased] section template at the top and cleaning the Changelog for the current release.\n" \ f"2) Updated package version in package.json by incrementing the patch version to signify the current state of the package.\n" \ f"3) Updated package version in ValidationExceptions.json to match the new package version.\n\n" \ diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index da1ac2eb3a..0118f0be3a 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -28,31 +28,48 @@ def createPrAfterRelease(config: ReleaseConfig): 3) Update the package version in the package.json file by incrementing the patch version to signify the current state of the package. 4) Update package version in the validation exceptions to match the new package version. - This assumes that at the same time you already branched off for the release. Otherwise it may be confusing + IMPORTANT: The PR is created against the trigger branch (the branch the job was triggered from), + not the default branch. This ensures consistency with the validation and release branch creation. + Please double check if the target branch is different and if so the if this was intended. """ try: + repo = get_local_repo() + trigger_branch = repo.active_branch.name + + if not config.github_manager.is_branch_present(trigger_branch): + raise Exception(f"Trigger branch '{trigger_branch}' does not exist. Exiting.") if not config.github_manager.is_branch_present(config.default_repo_branch): - raise Exception(f"Branch '{config.default_repo_branch}' does not exist. Exiting.") + raise Exception(f"Default branch '{config.default_repo_branch}' does not exist. Exiting.") + + # Check if PR branch already exists (could happen if a previous run failed partway through) + if config.github_manager.is_branch_present(config.pr_branch_name): + raise Exception(f"PR branch '{config.pr_branch_name}' already exists. This might indicate a previous incomplete run.") author = Actor(config.commiter_name, config.commiter_email) committer = Actor(config.commiter_name, config.commiter_email) - repo = get_local_repo() repo.git.fetch('--prune', '--prune-tags') - # Check if there are uncommitted changes that would block checkout - # Stash them if they exist to allow checkout to proceed + # Ensure we're on the trigger branch and have latest changes + # Stash any uncommitted changes that might block operations has_uncommitted_changes = repo.is_dirty() if has_uncommitted_changes: - print("Uncommitted changes detected. Stashing before checkout...") - repo.git.stash('push', '-m', 'Auto-stash before checkout for release PR creation') + print("Uncommitted changes detected. Stashing before operations...") + repo.git.stash('push', '-m', 'Auto-stash before release PR creation') - repo.git.checkout(config.default_repo_branch) - repo.git.pull("origin", config.default_repo_branch) + repo.git.checkout(trigger_branch) + repo.git.pull("origin", trigger_branch) - # Create a new branch for the release changes PR to default branch - repo.git.checkout('-b', config.pr_branch_name) + # Create a new branch for the release changes PR to trigger branch + try: + repo.git.checkout('-b', config.pr_branch_name) + except Exception as e: + # Branch might exist locally, try to checkout existing branch + if 'already exists' in str(e).lower(): + raise Exception(f"Branch '{config.pr_branch_name}' already exists locally which is not expected. Exiting.") + else: + raise # Update the changelog file with adding new [Unreleased] section update_changelog(config.changelog_path, config.package_version, add_unreleased_template=True) @@ -68,10 +85,10 @@ def createPrAfterRelease(config: ReleaseConfig): repo.git.push("origin", config.pr_branch_name) github = config.github_manager - pr = github.create_pull_request(title=config.pr_commit_message, body=config.pr_body, head=config.pr_branch_name, base=config.default_repo_branch) + pr = github.create_pull_request(title=config.pr_commit_message, body=config.pr_body, head=config.pr_branch_name, base=trigger_branch) github.request_reviews(pr, config.pr_reviewers) - print(f"Successfully updated and created the PR targeting: {config.default_repo_branch}") + print(f"Successfully updated and created the PR targeting trigger branch: {trigger_branch}") except GithubException as e: print(f"An error occurred with the GitHub API: {e.status}", file=sys.stderr) diff --git a/Tools/scripts/Utils/git_utils.py b/Tools/scripts/Utils/git_utils.py index 4776d83e6a..a034f316ed 100644 --- a/Tools/scripts/Utils/git_utils.py +++ b/Tools/scripts/Utils/git_utils.py @@ -45,6 +45,10 @@ def create_release_branch(config: ReleaseConfig): """ Creates a new branch with the specified name, performs specified action, commits the current changes and pushes it to the repo. Note that command_to_run_on_release_branch (within the Config) should be a single command that will be executed using subprocess.run. For multiple commands consider using a Python script file. + + IMPORTANT: The release branch is created from the trigger branch (the branch the job was triggered from). + This ensures the release branch is created from the branch that was validated and will be used for the PR. + Please double check if the target branch is different and if so the if this was intended. """ try: @@ -52,6 +56,16 @@ def create_release_branch(config: ReleaseConfig): raise Exception(f"Branch '{config.release_branch_name}' already exists.") repo = get_local_repo() + trigger_branch = repo.active_branch.name + + # Stash any uncommitted changes to allow pull + has_uncommitted_changes = repo.is_dirty() + if has_uncommitted_changes: + print("Uncommitted changes detected. Stashing before pull...") + repo.git.stash('push', '-m', 'Auto-stash before pull for release branch creation') + + repo.git.fetch('--prune', '--prune-tags') + repo.git.pull("origin", trigger_branch) new_branch = repo.create_head(config.release_branch_name, repo.head.commit) new_branch.checkout() diff --git a/Tools/scripts/Utils/verifyReleaseConditions.py b/Tools/scripts/Utils/verifyReleaseConditions.py index 7267c36db1..0ed5bf4886 100644 --- a/Tools/scripts/Utils/verifyReleaseConditions.py +++ b/Tools/scripts/Utils/verifyReleaseConditions.py @@ -7,6 +7,9 @@ - Note that if the job is triggered manually, this condition will be bypassed. 2. **Is the [Unreleased] section of the CHANGELOG.md not empty?** - The script checks if the [Unreleased] section in the CHANGELOG.md contains meaningful entries. + - IMPORTANT: This check is performed on the branch the job was triggered from (after pulling latest). + The release branch will be created from this trigger branch, and the PR will target this trigger branch. + Please double check if the target branch is different and if so the if this was intended. 3. **Does the release branch already exist?** - If the release branch for the target release already exists, the script will not run. """ @@ -21,6 +24,7 @@ import datetime import re from release_config import ReleaseConfig +from Utils.git_utils import get_local_repo def get_yamato_trigger_type(): """ @@ -80,6 +84,9 @@ def verifyReleaseConditions(config: ReleaseConfig): This function checks the following conditions: 1. If today is a scheduled release day (based on release cycle, weekday and anchor date). 2. If the [Unreleased] section of the CHANGELOG.md is not empty. + IMPORTANT: This check is performed on the branch the job was triggered from (after pulling latest). + The release branch will be created from this trigger branch, and the PR will target this trigger branch. + Please double check if the target branch is different and if so the if this was intended. 3. If the release branch does not already exist. """ @@ -92,12 +99,34 @@ def verifyReleaseConditions(config: ReleaseConfig): if not is_manual and not is_release_date(config.release_weekday, config.release_week_cycle, config.anchor_date): error_messages.append(f"Condition not met: Today is not the scheduled release day. It should be weekday: {config.release_weekday}, every {config.release_week_cycle} weeks starting from {config.anchor_date}.") + # Pull latest changes from the trigger branch to ensure we're checking the latest state + # The release branch will be created from this trigger branch, and the PR will target this trigger branch + repo = get_local_repo() + trigger_branch = repo.active_branch.name + print(f"\nTrigger branch: {trigger_branch}") + print(f"Pulling latest changes from '{trigger_branch}' to verify changelog state...") + + # Stash any uncommitted changes to allow pull + has_uncommitted_changes = repo.is_dirty() + if has_uncommitted_changes: + print("Uncommitted changes detected. Stashing before pull...") + repo.git.stash('push', '-m', 'Auto-stash before pull for release verification') + + repo.git.fetch('--prune', '--prune-tags') + repo.git.pull("origin", trigger_branch) + print(f"Now on branch '{trigger_branch}' with latest changes pulled.") + if is_changelog_empty(config.changelog_path): error_messages.append("Condition not met: The [Unreleased] section of the changelog has no meaningful entries.") if config.github_manager.is_branch_present(config.release_branch_name): error_messages.append("Condition not met: The release branch already exists.") + # Restore stashed changes if any + if has_uncommitted_changes: + print("Restoring stashed changes...") + repo.git.stash('pop') + if error_messages: print("\n--- Release conditions not met: ---") for i, msg in enumerate(error_messages, 1): From 921587ba33cde5e15f001f1632b133513800ebef Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 5 Jan 2026 11:05:50 +0100 Subject: [PATCH 16/21] Handling trigger branch detached head --- Tools/scripts/Utils/createPrAfterRelease.py | 24 +++++- Tools/scripts/Utils/git_utils.py | 85 ++++++++++++++++++- .../scripts/Utils/verifyReleaseConditions.py | 13 ++- 3 files changed, 116 insertions(+), 6 deletions(-) diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index 0118f0be3a..8f96ce06b4 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -17,7 +17,7 @@ from ReleaseAutomation.release_config import ReleaseConfig from Utils.general_utils import get_package_version_from_manifest, update_changelog, update_package_version_by_patch, update_validation_exceptions -from Utils.git_utils import get_local_repo +from Utils.git_utils import get_local_repo, get_trigger_branch def createPrAfterRelease(config: ReleaseConfig): """ @@ -35,7 +35,8 @@ def createPrAfterRelease(config: ReleaseConfig): try: repo = get_local_repo() - trigger_branch = repo.active_branch.name + trigger_branch = get_trigger_branch(repo, config.default_repo_branch) + print(f"\nTrigger branch: {trigger_branch}") if not config.github_manager.is_branch_present(trigger_branch): raise Exception(f"Trigger branch '{trigger_branch}' does not exist. Exiting.") @@ -51,6 +52,14 @@ def createPrAfterRelease(config: ReleaseConfig): repo.git.fetch('--prune', '--prune-tags') + # If we're in detached HEAD state, checkout the trigger branch first + try: + repo.active_branch.name + except (TypeError, ValueError): + # HEAD is detached, checkout the trigger branch + print(f"HEAD is detached, checking out trigger branch '{trigger_branch}'...") + repo.git.checkout(trigger_branch) + # Ensure we're on the trigger branch and have latest changes # Stash any uncommitted changes that might block operations has_uncommitted_changes = repo.is_dirty() @@ -58,7 +67,16 @@ def createPrAfterRelease(config: ReleaseConfig): print("Uncommitted changes detected. Stashing before operations...") repo.git.stash('push', '-m', 'Auto-stash before release PR creation') - repo.git.checkout(trigger_branch) + # Make sure we're on the trigger branch (in case we were on a different branch) + current_branch = None + try: + current_branch = repo.active_branch.name + except (TypeError, ValueError): + pass + + if current_branch != trigger_branch: + repo.git.checkout(trigger_branch) + repo.git.pull("origin", trigger_branch) # Create a new branch for the release changes PR to trigger branch diff --git a/Tools/scripts/Utils/git_utils.py b/Tools/scripts/Utils/git_utils.py index a034f316ed..3af9ff23fe 100644 --- a/Tools/scripts/Utils/git_utils.py +++ b/Tools/scripts/Utils/git_utils.py @@ -41,6 +41,80 @@ def get_latest_git_revision(branch_name): except subprocess.CalledProcessError as e: raise Exception(f"Failed to get the latest revision for branch '{branch_name}'.") from e + +def get_trigger_branch(repo, default_branch): + """ + Gets the trigger branch name, handling detached HEAD state in CI environments. + + In CI environments, the repository might be checked out at a specific commit (detached HEAD). + This function tries multiple methods to determine the branch: + 1. Check if HEAD is attached to a branch + 2. Check environment variables (YAMATO_BRANCH, CI_COMMIT_REF_NAME, etc.) + 3. Use git commands to find which remote branch contains the current commit + 4. Fall back to the default branch if nothing else works + + Args: + repo: GitPython Repo object + default_branch: Default branch name to fall back to + + Returns: + str: The branch name + """ + try: + # Try to get the active branch name (works when HEAD is attached) + return repo.active_branch.name + except (TypeError, ValueError): + # HEAD is detached, try other methods + pass + + # Method 1: Check environment variables + # Yamato might set branch info in environment variables + trigger_branch = os.environ.get('YAMATO_BRANCH') or \ + os.environ.get('CI_COMMIT_REF_NAME') or \ + os.environ.get('GITHUB_REF_NAME') or \ + os.environ.get('BRANCH_NAME') + + if trigger_branch: + # Remove 'refs/heads/' prefix if present + trigger_branch = trigger_branch.replace('refs/heads/', '') + print(f"Found trigger branch from environment variable: {trigger_branch}") + return trigger_branch + + # Method 2: Try to find which remote branch contains the current commit + try: + current_commit = repo.head.commit.hexsha + # Fetch all remote branches + repo.git.fetch('origin', '--prune', '--prune-tags') + + # Try to find which remote branch points to this commit + result = subprocess.run( + ['git', 'branch', '-r', '--contains', current_commit], + capture_output=True, + text=True, + check=True + ) + + branches = [b.strip() for b in result.stdout.strip().split('\n') if b.strip()] + # Filter to find the most likely branch (prefer default branch, then develop, then others) + for branch_line in branches: + branch = branch_line.replace('origin/', '').strip() + if branch and branch == default_branch: + print(f"Found trigger branch from remote branches: {branch}") + return branch + + # If default branch not found, use the first one + if branches: + branch = branches[0].replace('origin/', '').strip() + if branch: + print(f"Found trigger branch from remote branches: {branch}") + return branch + except Exception as e: + print(f"Warning: Could not determine branch from remote branches: {e}") + + # Method 3: Fall back to default branch + print(f"Warning: Could not determine trigger branch, falling back to default branch: {default_branch}") + return default_branch + def create_release_branch(config: ReleaseConfig): """ Creates a new branch with the specified name, performs specified action, commits the current changes and pushes it to the repo. @@ -56,7 +130,16 @@ def create_release_branch(config: ReleaseConfig): raise Exception(f"Branch '{config.release_branch_name}' already exists.") repo = get_local_repo() - trigger_branch = repo.active_branch.name + trigger_branch = get_trigger_branch(repo, config.default_repo_branch) + print(f"\nTrigger branch: {trigger_branch}") + + # If we're in detached HEAD state, checkout the trigger branch first + try: + repo.active_branch.name + except (TypeError, ValueError): + # HEAD is detached, checkout the trigger branch + print(f"HEAD is detached, checking out trigger branch '{trigger_branch}'...") + repo.git.checkout(trigger_branch) # Stash any uncommitted changes to allow pull has_uncommitted_changes = repo.is_dirty() diff --git a/Tools/scripts/Utils/verifyReleaseConditions.py b/Tools/scripts/Utils/verifyReleaseConditions.py index 0ed5bf4886..d38a547c8e 100644 --- a/Tools/scripts/Utils/verifyReleaseConditions.py +++ b/Tools/scripts/Utils/verifyReleaseConditions.py @@ -24,7 +24,7 @@ import datetime import re from release_config import ReleaseConfig -from Utils.git_utils import get_local_repo +from Utils.git_utils import get_local_repo, get_trigger_branch def get_yamato_trigger_type(): """ @@ -102,8 +102,17 @@ def verifyReleaseConditions(config: ReleaseConfig): # Pull latest changes from the trigger branch to ensure we're checking the latest state # The release branch will be created from this trigger branch, and the PR will target this trigger branch repo = get_local_repo() - trigger_branch = repo.active_branch.name + trigger_branch = get_trigger_branch(repo, config.default_repo_branch) print(f"\nTrigger branch: {trigger_branch}") + + # If we're in detached HEAD state, checkout the trigger branch first + try: + repo.active_branch.name + except (TypeError, ValueError): + # HEAD is detached, checkout the trigger branch + print(f"HEAD is detached, checking out trigger branch '{trigger_branch}'...") + repo.git.checkout(trigger_branch) + print(f"Pulling latest changes from '{trigger_branch}' to verify changelog state...") # Stash any uncommitted changes to allow pull From ce462ce42085e079d4786914867d8a2e4845b10f Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 5 Jan 2026 11:15:33 +0100 Subject: [PATCH 17/21] excluding release branch from target branches --- Tools/scripts/Utils/createPrAfterRelease.py | 3 +- Tools/scripts/Utils/git_utils.py | 37 +++++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index 8f96ce06b4..48a12e5fc0 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -35,7 +35,8 @@ def createPrAfterRelease(config: ReleaseConfig): try: repo = get_local_repo() - trigger_branch = get_trigger_branch(repo, config.default_repo_branch) + # Exclude the release branch when determining trigger branch (we might be on it) + trigger_branch = get_trigger_branch(repo, config.default_repo_branch, exclude_branches=[config.release_branch_name]) print(f"\nTrigger branch: {trigger_branch}") if not config.github_manager.is_branch_present(trigger_branch): diff --git a/Tools/scripts/Utils/git_utils.py b/Tools/scripts/Utils/git_utils.py index 3af9ff23fe..9734f3c84c 100644 --- a/Tools/scripts/Utils/git_utils.py +++ b/Tools/scripts/Utils/git_utils.py @@ -42,13 +42,13 @@ def get_latest_git_revision(branch_name): raise Exception(f"Failed to get the latest revision for branch '{branch_name}'.") from e -def get_trigger_branch(repo, default_branch): +def get_trigger_branch(repo, default_branch, exclude_branches=None): """ Gets the trigger branch name, handling detached HEAD state in CI environments. In CI environments, the repository might be checked out at a specific commit (detached HEAD). This function tries multiple methods to determine the branch: - 1. Check if HEAD is attached to a branch + 1. Check if HEAD is attached to a branch (but skip if it's a release branch or excluded branch) 2. Check environment variables (YAMATO_BRANCH, CI_COMMIT_REF_NAME, etc.) 3. Use git commands to find which remote branch contains the current commit 4. Fall back to the default branch if nothing else works @@ -56,13 +56,23 @@ def get_trigger_branch(repo, default_branch): Args: repo: GitPython Repo object default_branch: Default branch name to fall back to + exclude_branches: Optional list of branch names to exclude (e.g., release branches) Returns: str: The branch name """ + exclude_branches = exclude_branches or [] + current_branch = None + try: # Try to get the active branch name (works when HEAD is attached) - return repo.active_branch.name + current_branch = repo.active_branch.name + # If we're on a release branch or excluded branch, don't use it - use other methods + if current_branch.startswith('release/') or current_branch in exclude_branches: + print(f"Current branch '{current_branch}' is a release/excluded branch, using other methods to find trigger branch...") + current_branch = None + else: + return current_branch except (TypeError, ValueError): # HEAD is detached, try other methods pass @@ -95,19 +105,24 @@ def get_trigger_branch(repo, default_branch): ) branches = [b.strip() for b in result.stdout.strip().split('\n') if b.strip()] - # Filter to find the most likely branch (prefer default branch, then develop, then others) + # Filter out release branches and excluded branches + valid_branches = [] for branch_line in branches: branch = branch_line.replace('origin/', '').strip() - if branch and branch == default_branch: - print(f"Found trigger branch from remote branches: {branch}") - return branch + if branch and not branch.startswith('release/') and branch not in exclude_branches: + valid_branches.append(branch) - # If default branch not found, use the first one - if branches: - branch = branches[0].replace('origin/', '').strip() - if branch: + # Prefer default branch, then other valid branches + for branch in valid_branches: + if branch == default_branch: print(f"Found trigger branch from remote branches: {branch}") return branch + + # If default branch not found, use the first valid branch + if valid_branches: + branch = valid_branches[0] + print(f"Found trigger branch from remote branches: {branch}") + return branch except Exception as e: print(f"Warning: Could not determine branch from remote branches: {e}") From c965c4704d47831156f56cf5bececd29477bb67b Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 5 Jan 2026 11:24:31 +0100 Subject: [PATCH 18/21] Revert "excluding release branch from target branches" This reverts commit ce462ce42085e079d4786914867d8a2e4845b10f. --- Tools/scripts/Utils/createPrAfterRelease.py | 3 +- Tools/scripts/Utils/git_utils.py | 37 ++++++--------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index 48a12e5fc0..8f96ce06b4 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -35,8 +35,7 @@ def createPrAfterRelease(config: ReleaseConfig): try: repo = get_local_repo() - # Exclude the release branch when determining trigger branch (we might be on it) - trigger_branch = get_trigger_branch(repo, config.default_repo_branch, exclude_branches=[config.release_branch_name]) + trigger_branch = get_trigger_branch(repo, config.default_repo_branch) print(f"\nTrigger branch: {trigger_branch}") if not config.github_manager.is_branch_present(trigger_branch): diff --git a/Tools/scripts/Utils/git_utils.py b/Tools/scripts/Utils/git_utils.py index 9734f3c84c..3af9ff23fe 100644 --- a/Tools/scripts/Utils/git_utils.py +++ b/Tools/scripts/Utils/git_utils.py @@ -42,13 +42,13 @@ def get_latest_git_revision(branch_name): raise Exception(f"Failed to get the latest revision for branch '{branch_name}'.") from e -def get_trigger_branch(repo, default_branch, exclude_branches=None): +def get_trigger_branch(repo, default_branch): """ Gets the trigger branch name, handling detached HEAD state in CI environments. In CI environments, the repository might be checked out at a specific commit (detached HEAD). This function tries multiple methods to determine the branch: - 1. Check if HEAD is attached to a branch (but skip if it's a release branch or excluded branch) + 1. Check if HEAD is attached to a branch 2. Check environment variables (YAMATO_BRANCH, CI_COMMIT_REF_NAME, etc.) 3. Use git commands to find which remote branch contains the current commit 4. Fall back to the default branch if nothing else works @@ -56,23 +56,13 @@ def get_trigger_branch(repo, default_branch, exclude_branches=None): Args: repo: GitPython Repo object default_branch: Default branch name to fall back to - exclude_branches: Optional list of branch names to exclude (e.g., release branches) Returns: str: The branch name """ - exclude_branches = exclude_branches or [] - current_branch = None - try: # Try to get the active branch name (works when HEAD is attached) - current_branch = repo.active_branch.name - # If we're on a release branch or excluded branch, don't use it - use other methods - if current_branch.startswith('release/') or current_branch in exclude_branches: - print(f"Current branch '{current_branch}' is a release/excluded branch, using other methods to find trigger branch...") - current_branch = None - else: - return current_branch + return repo.active_branch.name except (TypeError, ValueError): # HEAD is detached, try other methods pass @@ -105,24 +95,19 @@ def get_trigger_branch(repo, default_branch, exclude_branches=None): ) branches = [b.strip() for b in result.stdout.strip().split('\n') if b.strip()] - # Filter out release branches and excluded branches - valid_branches = [] + # Filter to find the most likely branch (prefer default branch, then develop, then others) for branch_line in branches: branch = branch_line.replace('origin/', '').strip() - if branch and not branch.startswith('release/') and branch not in exclude_branches: - valid_branches.append(branch) - - # Prefer default branch, then other valid branches - for branch in valid_branches: - if branch == default_branch: + if branch and branch == default_branch: print(f"Found trigger branch from remote branches: {branch}") return branch - # If default branch not found, use the first valid branch - if valid_branches: - branch = valid_branches[0] - print(f"Found trigger branch from remote branches: {branch}") - return branch + # If default branch not found, use the first one + if branches: + branch = branches[0].replace('origin/', '').strip() + if branch: + print(f"Found trigger branch from remote branches: {branch}") + return branch except Exception as e: print(f"Warning: Could not determine branch from remote branches: {e}") From b34414e035304a6ca83527d69ba561078da028cd Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 5 Jan 2026 11:24:35 +0100 Subject: [PATCH 19/21] Revert "Handling trigger branch detached head" This reverts commit 921587ba33cde5e15f001f1632b133513800ebef. --- Tools/scripts/Utils/createPrAfterRelease.py | 24 +----- Tools/scripts/Utils/git_utils.py | 85 +------------------ .../scripts/Utils/verifyReleaseConditions.py | 13 +-- 3 files changed, 6 insertions(+), 116 deletions(-) diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index 8f96ce06b4..0118f0be3a 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -17,7 +17,7 @@ from ReleaseAutomation.release_config import ReleaseConfig from Utils.general_utils import get_package_version_from_manifest, update_changelog, update_package_version_by_patch, update_validation_exceptions -from Utils.git_utils import get_local_repo, get_trigger_branch +from Utils.git_utils import get_local_repo def createPrAfterRelease(config: ReleaseConfig): """ @@ -35,8 +35,7 @@ def createPrAfterRelease(config: ReleaseConfig): try: repo = get_local_repo() - trigger_branch = get_trigger_branch(repo, config.default_repo_branch) - print(f"\nTrigger branch: {trigger_branch}") + trigger_branch = repo.active_branch.name if not config.github_manager.is_branch_present(trigger_branch): raise Exception(f"Trigger branch '{trigger_branch}' does not exist. Exiting.") @@ -52,14 +51,6 @@ def createPrAfterRelease(config: ReleaseConfig): repo.git.fetch('--prune', '--prune-tags') - # If we're in detached HEAD state, checkout the trigger branch first - try: - repo.active_branch.name - except (TypeError, ValueError): - # HEAD is detached, checkout the trigger branch - print(f"HEAD is detached, checking out trigger branch '{trigger_branch}'...") - repo.git.checkout(trigger_branch) - # Ensure we're on the trigger branch and have latest changes # Stash any uncommitted changes that might block operations has_uncommitted_changes = repo.is_dirty() @@ -67,16 +58,7 @@ def createPrAfterRelease(config: ReleaseConfig): print("Uncommitted changes detected. Stashing before operations...") repo.git.stash('push', '-m', 'Auto-stash before release PR creation') - # Make sure we're on the trigger branch (in case we were on a different branch) - current_branch = None - try: - current_branch = repo.active_branch.name - except (TypeError, ValueError): - pass - - if current_branch != trigger_branch: - repo.git.checkout(trigger_branch) - + repo.git.checkout(trigger_branch) repo.git.pull("origin", trigger_branch) # Create a new branch for the release changes PR to trigger branch diff --git a/Tools/scripts/Utils/git_utils.py b/Tools/scripts/Utils/git_utils.py index 3af9ff23fe..a034f316ed 100644 --- a/Tools/scripts/Utils/git_utils.py +++ b/Tools/scripts/Utils/git_utils.py @@ -41,80 +41,6 @@ def get_latest_git_revision(branch_name): except subprocess.CalledProcessError as e: raise Exception(f"Failed to get the latest revision for branch '{branch_name}'.") from e - -def get_trigger_branch(repo, default_branch): - """ - Gets the trigger branch name, handling detached HEAD state in CI environments. - - In CI environments, the repository might be checked out at a specific commit (detached HEAD). - This function tries multiple methods to determine the branch: - 1. Check if HEAD is attached to a branch - 2. Check environment variables (YAMATO_BRANCH, CI_COMMIT_REF_NAME, etc.) - 3. Use git commands to find which remote branch contains the current commit - 4. Fall back to the default branch if nothing else works - - Args: - repo: GitPython Repo object - default_branch: Default branch name to fall back to - - Returns: - str: The branch name - """ - try: - # Try to get the active branch name (works when HEAD is attached) - return repo.active_branch.name - except (TypeError, ValueError): - # HEAD is detached, try other methods - pass - - # Method 1: Check environment variables - # Yamato might set branch info in environment variables - trigger_branch = os.environ.get('YAMATO_BRANCH') or \ - os.environ.get('CI_COMMIT_REF_NAME') or \ - os.environ.get('GITHUB_REF_NAME') or \ - os.environ.get('BRANCH_NAME') - - if trigger_branch: - # Remove 'refs/heads/' prefix if present - trigger_branch = trigger_branch.replace('refs/heads/', '') - print(f"Found trigger branch from environment variable: {trigger_branch}") - return trigger_branch - - # Method 2: Try to find which remote branch contains the current commit - try: - current_commit = repo.head.commit.hexsha - # Fetch all remote branches - repo.git.fetch('origin', '--prune', '--prune-tags') - - # Try to find which remote branch points to this commit - result = subprocess.run( - ['git', 'branch', '-r', '--contains', current_commit], - capture_output=True, - text=True, - check=True - ) - - branches = [b.strip() for b in result.stdout.strip().split('\n') if b.strip()] - # Filter to find the most likely branch (prefer default branch, then develop, then others) - for branch_line in branches: - branch = branch_line.replace('origin/', '').strip() - if branch and branch == default_branch: - print(f"Found trigger branch from remote branches: {branch}") - return branch - - # If default branch not found, use the first one - if branches: - branch = branches[0].replace('origin/', '').strip() - if branch: - print(f"Found trigger branch from remote branches: {branch}") - return branch - except Exception as e: - print(f"Warning: Could not determine branch from remote branches: {e}") - - # Method 3: Fall back to default branch - print(f"Warning: Could not determine trigger branch, falling back to default branch: {default_branch}") - return default_branch - def create_release_branch(config: ReleaseConfig): """ Creates a new branch with the specified name, performs specified action, commits the current changes and pushes it to the repo. @@ -130,16 +56,7 @@ def create_release_branch(config: ReleaseConfig): raise Exception(f"Branch '{config.release_branch_name}' already exists.") repo = get_local_repo() - trigger_branch = get_trigger_branch(repo, config.default_repo_branch) - print(f"\nTrigger branch: {trigger_branch}") - - # If we're in detached HEAD state, checkout the trigger branch first - try: - repo.active_branch.name - except (TypeError, ValueError): - # HEAD is detached, checkout the trigger branch - print(f"HEAD is detached, checking out trigger branch '{trigger_branch}'...") - repo.git.checkout(trigger_branch) + trigger_branch = repo.active_branch.name # Stash any uncommitted changes to allow pull has_uncommitted_changes = repo.is_dirty() diff --git a/Tools/scripts/Utils/verifyReleaseConditions.py b/Tools/scripts/Utils/verifyReleaseConditions.py index d38a547c8e..0ed5bf4886 100644 --- a/Tools/scripts/Utils/verifyReleaseConditions.py +++ b/Tools/scripts/Utils/verifyReleaseConditions.py @@ -24,7 +24,7 @@ import datetime import re from release_config import ReleaseConfig -from Utils.git_utils import get_local_repo, get_trigger_branch +from Utils.git_utils import get_local_repo def get_yamato_trigger_type(): """ @@ -102,17 +102,8 @@ def verifyReleaseConditions(config: ReleaseConfig): # Pull latest changes from the trigger branch to ensure we're checking the latest state # The release branch will be created from this trigger branch, and the PR will target this trigger branch repo = get_local_repo() - trigger_branch = get_trigger_branch(repo, config.default_repo_branch) + trigger_branch = repo.active_branch.name print(f"\nTrigger branch: {trigger_branch}") - - # If we're in detached HEAD state, checkout the trigger branch first - try: - repo.active_branch.name - except (TypeError, ValueError): - # HEAD is detached, checkout the trigger branch - print(f"HEAD is detached, checking out trigger branch '{trigger_branch}'...") - repo.git.checkout(trigger_branch) - print(f"Pulling latest changes from '{trigger_branch}' to verify changelog state...") # Stash any uncommitted changes to allow pull From 1073a4f825cde19fbdfcda79587b6346a372b648 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 5 Jan 2026 11:24:40 +0100 Subject: [PATCH 20/21] Revert "Updated flow to use trigger branch as base for releasing" This reverts commit 6e08459a7623295f35774b1c4adb84b7ed142308. --- .../ReleaseAutomation/release_config.py | 4 +- Tools/scripts/Utils/createPrAfterRelease.py | 43 ++++++------------- Tools/scripts/Utils/git_utils.py | 14 ------ .../scripts/Utils/verifyReleaseConditions.py | 29 ------------- 4 files changed, 15 insertions(+), 75 deletions(-) diff --git a/Tools/scripts/ReleaseAutomation/release_config.py b/Tools/scripts/ReleaseAutomation/release_config.py index f78465acca..0aab8d0940 100644 --- a/Tools/scripts/ReleaseAutomation/release_config.py +++ b/Tools/scripts/ReleaseAutomation/release_config.py @@ -65,9 +65,9 @@ def __init__(self): self.release_commit_message = f"Updated changelog and package version for Netcode in anticipation of v{self.package_version} release" - self.pr_branch_name = f"netcode-update-after-{self.package_version}-release-branch-creation" # Branch from which we will create PR to trigger branch with relevant changes after release branch is created + self.pr_branch_name = f"netcode-update-after-{self.package_version}-release-branch-creation" # Branch from which we will create PR to default branch with relevant changes after release branch is created self.pr_commit_message = f"chore: Updated aspects of Netcode package in anticipation of v{self.package_version} release" - self.pr_body = f"This PR was created in sync with branching of {self.release_branch_name}. It includes changes that should land on the branch this job was triggered from which usually is {self.default_repo_branch} (please double check if the target branch is different if this was intended) to reflect the new state of the package after the v{self.package_version} release:\n" \ + self.pr_body = f"This PR was created in sync with branching of {self.release_branch_name}. It includes changes that should land on the default Netcode branch ({self.default_repo_branch}) to reflect the new state of the package after the v{self.package_version} release:\n" \ f"1) Updated CHANGELOG.md by adding new [Unreleased] section template at the top and cleaning the Changelog for the current release.\n" \ f"2) Updated package version in package.json by incrementing the patch version to signify the current state of the package.\n" \ f"3) Updated package version in ValidationExceptions.json to match the new package version.\n\n" \ diff --git a/Tools/scripts/Utils/createPrAfterRelease.py b/Tools/scripts/Utils/createPrAfterRelease.py index 0118f0be3a..da1ac2eb3a 100644 --- a/Tools/scripts/Utils/createPrAfterRelease.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -28,48 +28,31 @@ def createPrAfterRelease(config: ReleaseConfig): 3) Update the package version in the package.json file by incrementing the patch version to signify the current state of the package. 4) Update package version in the validation exceptions to match the new package version. - IMPORTANT: The PR is created against the trigger branch (the branch the job was triggered from), - not the default branch. This ensures consistency with the validation and release branch creation. - Please double check if the target branch is different and if so the if this was intended. + This assumes that at the same time you already branched off for the release. Otherwise it may be confusing """ try: - repo = get_local_repo() - trigger_branch = repo.active_branch.name - - if not config.github_manager.is_branch_present(trigger_branch): - raise Exception(f"Trigger branch '{trigger_branch}' does not exist. Exiting.") if not config.github_manager.is_branch_present(config.default_repo_branch): - raise Exception(f"Default branch '{config.default_repo_branch}' does not exist. Exiting.") - - # Check if PR branch already exists (could happen if a previous run failed partway through) - if config.github_manager.is_branch_present(config.pr_branch_name): - raise Exception(f"PR branch '{config.pr_branch_name}' already exists. This might indicate a previous incomplete run.") + raise Exception(f"Branch '{config.default_repo_branch}' does not exist. Exiting.") author = Actor(config.commiter_name, config.commiter_email) committer = Actor(config.commiter_name, config.commiter_email) + repo = get_local_repo() repo.git.fetch('--prune', '--prune-tags') - # Ensure we're on the trigger branch and have latest changes - # Stash any uncommitted changes that might block operations + # Check if there are uncommitted changes that would block checkout + # Stash them if they exist to allow checkout to proceed has_uncommitted_changes = repo.is_dirty() if has_uncommitted_changes: - print("Uncommitted changes detected. Stashing before operations...") - repo.git.stash('push', '-m', 'Auto-stash before release PR creation') + print("Uncommitted changes detected. Stashing before checkout...") + repo.git.stash('push', '-m', 'Auto-stash before checkout for release PR creation') - repo.git.checkout(trigger_branch) - repo.git.pull("origin", trigger_branch) + repo.git.checkout(config.default_repo_branch) + repo.git.pull("origin", config.default_repo_branch) - # Create a new branch for the release changes PR to trigger branch - try: - repo.git.checkout('-b', config.pr_branch_name) - except Exception as e: - # Branch might exist locally, try to checkout existing branch - if 'already exists' in str(e).lower(): - raise Exception(f"Branch '{config.pr_branch_name}' already exists locally which is not expected. Exiting.") - else: - raise + # Create a new branch for the release changes PR to default branch + repo.git.checkout('-b', config.pr_branch_name) # Update the changelog file with adding new [Unreleased] section update_changelog(config.changelog_path, config.package_version, add_unreleased_template=True) @@ -85,10 +68,10 @@ def createPrAfterRelease(config: ReleaseConfig): repo.git.push("origin", config.pr_branch_name) github = config.github_manager - pr = github.create_pull_request(title=config.pr_commit_message, body=config.pr_body, head=config.pr_branch_name, base=trigger_branch) + pr = github.create_pull_request(title=config.pr_commit_message, body=config.pr_body, head=config.pr_branch_name, base=config.default_repo_branch) github.request_reviews(pr, config.pr_reviewers) - print(f"Successfully updated and created the PR targeting trigger branch: {trigger_branch}") + print(f"Successfully updated and created the PR targeting: {config.default_repo_branch}") except GithubException as e: print(f"An error occurred with the GitHub API: {e.status}", file=sys.stderr) diff --git a/Tools/scripts/Utils/git_utils.py b/Tools/scripts/Utils/git_utils.py index a034f316ed..4776d83e6a 100644 --- a/Tools/scripts/Utils/git_utils.py +++ b/Tools/scripts/Utils/git_utils.py @@ -45,10 +45,6 @@ def create_release_branch(config: ReleaseConfig): """ Creates a new branch with the specified name, performs specified action, commits the current changes and pushes it to the repo. Note that command_to_run_on_release_branch (within the Config) should be a single command that will be executed using subprocess.run. For multiple commands consider using a Python script file. - - IMPORTANT: The release branch is created from the trigger branch (the branch the job was triggered from). - This ensures the release branch is created from the branch that was validated and will be used for the PR. - Please double check if the target branch is different and if so the if this was intended. """ try: @@ -56,16 +52,6 @@ def create_release_branch(config: ReleaseConfig): raise Exception(f"Branch '{config.release_branch_name}' already exists.") repo = get_local_repo() - trigger_branch = repo.active_branch.name - - # Stash any uncommitted changes to allow pull - has_uncommitted_changes = repo.is_dirty() - if has_uncommitted_changes: - print("Uncommitted changes detected. Stashing before pull...") - repo.git.stash('push', '-m', 'Auto-stash before pull for release branch creation') - - repo.git.fetch('--prune', '--prune-tags') - repo.git.pull("origin", trigger_branch) new_branch = repo.create_head(config.release_branch_name, repo.head.commit) new_branch.checkout() diff --git a/Tools/scripts/Utils/verifyReleaseConditions.py b/Tools/scripts/Utils/verifyReleaseConditions.py index 0ed5bf4886..7267c36db1 100644 --- a/Tools/scripts/Utils/verifyReleaseConditions.py +++ b/Tools/scripts/Utils/verifyReleaseConditions.py @@ -7,9 +7,6 @@ - Note that if the job is triggered manually, this condition will be bypassed. 2. **Is the [Unreleased] section of the CHANGELOG.md not empty?** - The script checks if the [Unreleased] section in the CHANGELOG.md contains meaningful entries. - - IMPORTANT: This check is performed on the branch the job was triggered from (after pulling latest). - The release branch will be created from this trigger branch, and the PR will target this trigger branch. - Please double check if the target branch is different and if so the if this was intended. 3. **Does the release branch already exist?** - If the release branch for the target release already exists, the script will not run. """ @@ -24,7 +21,6 @@ import datetime import re from release_config import ReleaseConfig -from Utils.git_utils import get_local_repo def get_yamato_trigger_type(): """ @@ -84,9 +80,6 @@ def verifyReleaseConditions(config: ReleaseConfig): This function checks the following conditions: 1. If today is a scheduled release day (based on release cycle, weekday and anchor date). 2. If the [Unreleased] section of the CHANGELOG.md is not empty. - IMPORTANT: This check is performed on the branch the job was triggered from (after pulling latest). - The release branch will be created from this trigger branch, and the PR will target this trigger branch. - Please double check if the target branch is different and if so the if this was intended. 3. If the release branch does not already exist. """ @@ -99,34 +92,12 @@ def verifyReleaseConditions(config: ReleaseConfig): if not is_manual and not is_release_date(config.release_weekday, config.release_week_cycle, config.anchor_date): error_messages.append(f"Condition not met: Today is not the scheduled release day. It should be weekday: {config.release_weekday}, every {config.release_week_cycle} weeks starting from {config.anchor_date}.") - # Pull latest changes from the trigger branch to ensure we're checking the latest state - # The release branch will be created from this trigger branch, and the PR will target this trigger branch - repo = get_local_repo() - trigger_branch = repo.active_branch.name - print(f"\nTrigger branch: {trigger_branch}") - print(f"Pulling latest changes from '{trigger_branch}' to verify changelog state...") - - # Stash any uncommitted changes to allow pull - has_uncommitted_changes = repo.is_dirty() - if has_uncommitted_changes: - print("Uncommitted changes detected. Stashing before pull...") - repo.git.stash('push', '-m', 'Auto-stash before pull for release verification') - - repo.git.fetch('--prune', '--prune-tags') - repo.git.pull("origin", trigger_branch) - print(f"Now on branch '{trigger_branch}' with latest changes pulled.") - if is_changelog_empty(config.changelog_path): error_messages.append("Condition not met: The [Unreleased] section of the changelog has no meaningful entries.") if config.github_manager.is_branch_present(config.release_branch_name): error_messages.append("Condition not met: The release branch already exists.") - # Restore stashed changes if any - if has_uncommitted_changes: - print("Restoring stashed changes...") - repo.git.stash('pop') - if error_messages: print("\n--- Release conditions not met: ---") for i, msg in enumerate(error_messages, 1): From 077c1bb453710c45432e10c81aa81086474f2cf6 Mon Sep 17 00:00:00 2001 From: michal-chrobot Date: Mon, 5 Jan 2026 11:26:37 +0100 Subject: [PATCH 21/21] reverted changelog addition --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9dff48cd14..06109ea401 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,7 +10,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- test addition ### Changed