Skip to content

Add @since update tool and release prep integration#1798

Merged
pattonwebz merged 4 commits into
developfrom
william/no-issue/add-since-update-tool
Jul 8, 2026
Merged

Add @since update tool and release prep integration#1798
pattonwebz merged 4 commits into
developfrom
william/no-issue/add-since-update-tool

Conversation

@pattonwebz

@pattonwebz pattonwebz commented Jun 27, 2026

Copy link
Copy Markdown
Member

What this PR does

  • Adds a new PHP CLI tool: tools/update-since-tags.php to replace placeholder @since x.x.x values with a release version.
  • Supports advanced flags: --version, --dry-run, --changed-since-last-tag, --changed-since-tag, --root, and --placeholder.
  • Updates scripts/prep_release.sh to:
    • run the @SInCE updater in a dedicated post-version-bump commit,
    • safely stash/restore local uncommitted work around the since-update step,
    • stage only tracked changed PHP files for the since commit,
    • verify local tags are in sync with origin before release prep continues.

Commits

  • Add @SInCE placeholder update tool
  • Update release prep flow for @SInCE sync and tag checks

Notes

  • Existing unrelated untracked files were intentionally left untouched.

Summary by CodeRabbit

  • New Features
    • Added stricter release preparation validation by requiring local git tags to exactly match remote tags before creating the release branch.
    • Introduced a CLI tool to update PHP docblock @since placeholders to the target release version.
  • Bug Fixes
    • Hardened the WordPress “version requirements” updater with stricter parsing and explicit failure when no versions are detected; keeps stopping behavior when “Tested up to” can’t be resolved, and enforces a minimum baseline of WordPress 5.0 in readme.txt.
    • Made @since updates safer by stashing/restoring local work, updating only relevant tracked PHP changes, and skipping commits when nothing changes.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

scripts/prep_release.sh now verifies local tags against origin, tightens WordPress version handling, and runs tools/update-since-tags.php through a stash-backed release flow. The new PHP CLI scans PHP files and replaces placeholder @since tokens with the release version.

Release Prep Hardening

Layer / File(s) Summary
Git tag sync verification
scripts/prep_release.sh
Adds verify_local_tags_match_remote that compares sorted local and remote tag refs after a pruning fetch.
WordPress version requirements update
scripts/prep_release.sh
Adds empty-version validation, a warning for missing tested versions, and a 5.0 minimum baseline before updating readme.txt.
PHP @since tool entrypoint and scan selection
tools/update-since-tags.php
Adds CLI parsing, exit codes, scan-mode selection, and file enumeration helpers for full or git-scoped PHP scans.
PHP @since tool process helpers
tools/update-since-tags.php
Adds the git execution helper and output writer used by the CLI tool.
PHP @since replacement loop
tools/update-since-tags.php
Replaces placeholder @since tokens, supports dry-run output, and prints scan/update totals.
Stash-backed @since commit workflow
scripts/prep_release.sh
Stashes local work, runs the tool, stages tracked PHP updates, commits when needed, and restores the stash on exit.

Changes

Release Prep Hardening

Layer / File(s) Summary
Git tag sync verification
scripts/prep_release.sh
Adds verify_local_tags_match_remote that compares sorted local and remote tag refs after a pruning fetch.
WordPress version requirements update
scripts/prep_release.sh
Adds empty-version validation, a warning for missing tested versions, and a 5.0 minimum baseline before updating readme.txt.
PHP @since tool entrypoint and scan selection
tools/update-since-tags.php
Adds CLI parsing, exit codes, scan-mode selection, and file enumeration helpers for full or git-scoped PHP scans.
PHP @since tool process helpers
tools/update-since-tags.php
Adds the git execution helper and output writer used by the CLI tool.
PHP @since replacement loop
tools/update-since-tags.php
Replaces placeholder @since tokens, supports dry-run output, and prints scan/update totals.
Stash-backed @since commit workflow
scripts/prep_release.sh
Stashes local work, runs the tool, stages tracked PHP updates, commits when needed, and restores the stash on exit.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hop through tags and check them twice,
Then stash the clutter neat and nice.
@since signs bloom from placeholder snow,
And release prep runs smooth as I go.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: a new @since update tool and its integration into release prep.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch william/no-issue/add-since-update-tool

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a release preparation script and a PHP tool to automate tag verification and update placeholder @SInCE tags in PHP docblocks. The review feedback highlights several critical issues and improvement opportunities: a bug in the stash restoration logic that could pop unrelated stashes when no local changes exist, a recommendation to use process substitution instead of temporary files for tag comparison, and type-handling issues in the PHP script where getopt returning false for optional parameters could bypass null coalescing checks and cause runtime errors or silent failures.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread scripts/prep_release.sh
Comment on lines +207 to +222
# Stash any existing uncommitted work (tracked modifications + untracked files)
# so that the subsequent git diff picks up only what the tool changes.
STASH_MESSAGE="prep_release: pre-since-tag stash"
git stash push --include-untracked -m "${STASH_MESSAGE}"
STASH_CREATED=$?

# Ensure the stash is always restored, even if the script exits early.
restore_stash() {
if [[ ${STASH_CREATED} -eq 0 ]]; then
echo
echo "Restoring stashed changes"
echo
git stash pop
fi
}
trap restore_stash EXIT

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

Unrelated Stash Pop Bug

If there are no local changes to stash, git stash push will print No local changes to save and exit with status 0. This means STASH_CREATED will be set to 0.

When restore_stash is subsequently called, it checks if STASH_CREATED is 0 and executes git stash pop. Since no stash was actually created by this script, git stash pop will pop and apply the topmost unrelated stash from the user's stash stack (or fail if the stack is empty). This can lead to silent data corruption or unexpected merge conflicts.

To fix this, we should check if there are any uncommitted changes (tracked or untracked) before attempting to stash.

Suggested change
# Stash any existing uncommitted work (tracked modifications + untracked files)
# so that the subsequent git diff picks up only what the tool changes.
STASH_MESSAGE="prep_release: pre-since-tag stash"
git stash push --include-untracked -m "${STASH_MESSAGE}"
STASH_CREATED=$?
# Ensure the stash is always restored, even if the script exits early.
restore_stash() {
if [[ ${STASH_CREATED} -eq 0 ]]; then
echo
echo "Restoring stashed changes"
echo
git stash pop
fi
}
trap restore_stash EXIT
# Check if there are any uncommitted changes (tracked or untracked) to stash.
HAS_STASHED=false
if [[ -n "$(git status --porcelain)" ]]; then
echo "Stashing local changes before updating @since tags..."
STASH_MESSAGE="prep_release: pre-since-tag stash"
git stash push --include-untracked -m "${STASH_MESSAGE}"
HAS_STASHED=true
fi
# Ensure the stash is always restored, even if the script exits early.
restore_stash() {
if [[ "${HAS_STASHED}" == "true" ]]; then
echo
echo "Restoring stashed changes"
echo
git stash pop
fi
}
trap restore_stash EXIT

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in commit 9eb48e01. The stash logic now compares refs/stash before and after the push — if the ref is unchanged (nothing was stashed), STASH_CREATED stays false and restore_stash is skipped entirely, preventing any pop of an unrelated stash.

Comment thread scripts/prep_release.sh
Comment on lines +37 to +62
verify_local_tags_match_remote() {
local local_tags_file
local remote_tags_file

local_tags_file=$(mktemp)
remote_tags_file=$(mktemp)

# Always clean up temporary files before returning.
trap 'rm -f "${local_tags_file}" "${remote_tags_file}"' RETURN

# --refs avoids peeled annotated-tag entries (^{}) for clean one-line refs.
git show-ref --tags | awk '{print $1" "$2}' | sort > "${local_tags_file}"
git ls-remote --tags --refs origin | awk '{print $1" "$2}' | sort > "${remote_tags_file}"

if ! diff -u "${remote_tags_file}" "${local_tags_file}" >/dev/null; then
echo
echo "Error: local tags do not match tags on origin."
echo "Please sync tags before running release prep."
echo
diff -u "${remote_tags_file}" "${local_tags_file}" || true
return 1
fi

echo "Tag sync check passed: local tags match origin."
return 0
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Process Substitution Refactoring

Instead of creating temporary files with mktemp and setting up a RETURN trap to clean them up, you can use Bash's process substitution (<(...)). This is much cleaner, more idiomatic, and completely avoids the risk of leaving orphaned temporary files if the script exits prematurely (e.g., due to set -e before the trap is triggered).

Suggested change
verify_local_tags_match_remote() {
local local_tags_file
local remote_tags_file
local_tags_file=$(mktemp)
remote_tags_file=$(mktemp)
# Always clean up temporary files before returning.
trap 'rm -f "${local_tags_file}" "${remote_tags_file}"' RETURN
# --refs avoids peeled annotated-tag entries (^{}) for clean one-line refs.
git show-ref --tags | awk '{print $1" "$2}' | sort > "${local_tags_file}"
git ls-remote --tags --refs origin | awk '{print $1" "$2}' | sort > "${remote_tags_file}"
if ! diff -u "${remote_tags_file}" "${local_tags_file}" >/dev/null; then
echo
echo "Error: local tags do not match tags on origin."
echo "Please sync tags before running release prep."
echo
diff -u "${remote_tags_file}" "${local_tags_file}" || true
return 1
fi
echo "Tag sync check passed: local tags match origin."
return 0
}
verify_local_tags_match_remote() {
# Use process substitution to compare remote and local tags without creating temporary files.
# 2>/dev/null ensures we don't trigger set -e if no tags exist.
if ! diff -u <(git ls-remote --tags --refs origin 2>/dev/null | awk '{print $1" "$2}' | sort) <(git show-ref --tags 2>/dev/null | awk '{print $1" "$2}' | sort) >/dev/null; then
echo
echo "Error: local tags do not match tags on origin."
echo "Please sync tags before running release prep."
echo
diff -u <(git ls-remote --tags --refs origin 2>/dev/null | awk '{print $1" "$2}' | sort) <(git show-ref --tags 2>/dev/null | awk '{print $1" "$2}' | sort) || true
return 1
fi
echo "Tag sync check passed: local tags match origin."
return 0
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in commit 9eb48e01. Replaced the mktemp + RETURN-trap approach with process substitution (<(...)) in verify_local_tags_match_remote(). No temp files are created so there is nothing to orphan on early exit.

Comment thread tools/update-since-tags.php Outdated
Comment on lines +53 to +64
$root = $opts['root'] ?? dirname( __DIR__ );
if ( ! is_string( $root ) || ! is_dir( $root ) ) {
fwrite( STDERR, 'Error: root directory does not exist: ' . (string) $root . "\n" );
exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}
$root = rtrim( (string) $root, DIRECTORY_SEPARATOR );

$placeholder = $opts['placeholder'] ?? 'x.x.x';
if ( ! is_string( $placeholder ) || '' === trim( $placeholder ) ) {
fwrite( STDERR, "Error: --placeholder must be a non-empty string.\n" );
exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Handle false from getopt for Optional Parameters

In PHP, getopt returns false for optional parameters (defined with ::) if they are specified on the command line without a value (e.g., --root or --placeholder).

Because the null coalescing operator (??) only checks for null, a value of false will bypass it and be assigned directly to $root or $placeholder. This leads to type errors or unexpected behavior (e.g., is_dir(false) is false, resulting in a cryptic error message).

We should explicitly check for false and handle it gracefully.

$root = $opts['root'] ?? dirname( __DIR__ );
if ( false === $root ) {
	fwrite( STDERR, "Error: --root requires a value.\n" );
	exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}
if ( ! is_string( $root ) || ! is_dir( $root ) ) {
	fwrite( STDERR, 'Error: root directory does not exist: ' . (string) $root . "\n" );
	exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}
$root = rtrim( (string) $root, DIRECTORY_SEPARATOR );

$placeholder = $opts['placeholder'] ?? 'x.x.x';
if ( false === $placeholder ) {
	fwrite( STDERR, "Error: --placeholder requires a value.\n" );
	exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}
if ( ! is_string( $placeholder ) || '' === trim( $placeholder ) ) {
	fwrite( STDERR, "Error: --placeholder must be a non-empty string.\n" );
	exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in commit 9eb48e01. Added explicit false === $root_opt and false === $placeholder_opt guards before the existing is_string/is_dir checks, both exit with a clear error message when an optional flag is supplied without a value.

Comment thread tools/update-since-tags.php Outdated
Comment on lines +66 to +73
$changed_since_tag = $opts['changed-since-tag'] ?? null;
$changed_since_last_tag = isset( $opts['changed-since-last-tag'] );
$dry_run = isset( $opts['dry-run'] );

if ( null !== $changed_since_tag && $changed_since_last_tag ) {
fwrite( STDERR, "Error: use either --changed-since-tag or --changed-since-last-tag, not both.\n" );
exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Handle false from getopt and Validate Git Reference

Similar to --root, if --changed-since-tag is passed without a value, getopt returns false. This causes null !== $changed_since_tag to evaluate to true, leading to an invalid Git command (git diff '..HEAD') and a silent failure where the tool reports success but scans 0 files.

Additionally, we should validate that the provided Git reference actually exists in the repository using git rev-parse --verify to prevent silent failures when a typo is made in the tag name.

$changed_since_tag = $opts['changed-since-tag'] ?? null;
if ( false === $changed_since_tag ) {
	fwrite( STDERR, "Error: --changed-since-tag requires a value.\n" );
	exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}

$changed_since_last_tag = isset( $opts['changed-since-last-tag'] );
$dry_run = isset( $opts['dry-run'] );

if ( null !== $changed_since_tag && $changed_since_last_tag ) {
	fwrite( STDERR, "Error: use either --changed-since-tag or --changed-since-last-tag, not both.\n" );
	exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS );
}

if ( null !== $changed_since_tag ) {
	$ref_check = edac_run_git( $root, 'rev-parse --verify ' . escapeshellarg( $changed_since_tag ) );
	if ( '' === trim( $ref_check ) ) {
		fwrite( STDERR, "Error: invalid Git reference or tag: {$changed_since_tag}\n" );
		exit( EDAC_SINCE_TOOL_EXIT_RUNTIME_ERROR );
	}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in commit 9eb48e01. Added a false === $changed_since_tag_opt guard to catch --changed-since-tag given without a value, and a git rev-parse --verify check to validate the ref before use — exits with EDAC_SINCE_TOOL_EXIT_RUNTIME_ERROR if the ref does not exist.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/prep_release.sh`:
- Around line 210-220: The restore_stash logic in prep_release.sh should track
the exact stash entry created by git stash push, not just rely on STASH_CREATED,
because an empty push can still return success and a later pop may hit an
unrelated stash. Update restore_stash to record and verify the specific stash
reference created by the push, and make sure the EXIT trap is cleared before
attempting git stash pop so a failed pop cannot be retried by the trap; apply
the same fix wherever the stash restore flow is duplicated.
- Around line 229-235: The staging step in prep_release.sh currently uses echo
piped into xargs, which will break PHP paths containing spaces or other special
characters. Update the changed-files collection and staging flow around
SINCE_CHANGES so it uses NUL-delimited output and a matching xargs invocation,
and keep the git add logic in the same release-commit block so paths are staged
safely before the git commit.

In `@tools/update-since-tags.php`:
- Around line 186-218: The Git command path in edac_run_git() is swallowing
failures by redirecting stderr and converting non-string shell_exec output to an
empty string, which makes edac_get_changed_php_files_since_ref() treat command
errors as “no files changed.” Update edac_run_git() to detect a non-zero git
exit status for the diff call and surface that failure to the caller, then have
edac_get_changed_php_files_since_ref() stop and trigger
EDAC_SINCE_TOOL_EXIT_RUNTIME_ERROR when the git command fails instead of
returning an empty file list.
- Line 37: The help text for the CLI option is misleading because `--root` is
documented as defaulting to the current working directory, while the option
parsing in `update-since-tags.php` actually uses `dirname(__DIR__)` as the
default. Update the usage string near the `--root` help entry to match the real
default used by the option handling logic, and make sure the default shown there
stays consistent with the `--root` parsing code.
- Around line 34-43: The CLI script has PHPCS/WPCS violations from unescaped raw
output, the `$mode` assignment pattern, and trailing blank lines at the end of
the file. Clean up the help/usage output in the main option-handling block of
`update-since-tags.php` so it matches WordPress CLI coding standards, refactor
the `$mode` logic to avoid the flagged assignment style, and remove any extra
EOF whitespace so the file passes linting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ffc37435-1d08-4fdd-9dfb-bd649958878e

📥 Commits

Reviewing files that changed from the base of the PR and between 40a0b82 and 1fb7859.

📒 Files selected for processing (2)
  • scripts/prep_release.sh
  • tools/update-since-tags.php

Comment thread scripts/prep_release.sh
Comment thread scripts/prep_release.sh Outdated
Comment thread tools/update-since-tags.php Outdated
Comment thread tools/update-since-tags.php Outdated
Comment thread tools/update-since-tags.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tools/update-since-tags.php (1)

149-167: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Prune excluded directories before recursion.

This still walks through .git, vendor, node_modules, build, and dist; the prefix check only drops files after the iterator has already descended into those trees. On large repos that makes the “full scan” path much slower than intended.

Suggested refactor
-	$iterator = new RecursiveIteratorIterator(
-		new RecursiveDirectoryIterator( $root, RecursiveDirectoryIterator::SKIP_DOTS )
-	);
+	$directory_iterator = new RecursiveDirectoryIterator( $root, RecursiveDirectoryIterator::SKIP_DOTS );
+	$filter             = new RecursiveCallbackFilterIterator(
+		$directory_iterator,
+		static function ( SplFileInfo $item, $key, RecursiveCallbackFilterIterator $iterator ) use ( $root, $excluded_dirs ): bool {
+			$path          = $item->getPathname();
+			$relative_path = ltrim( substr( $path, strlen( $root ) ), DIRECTORY_SEPARATOR );
+
+			if ( $item->isDir() ) {
+				foreach ( $excluded_dirs as $excluded ) {
+					if ( $relative_path === $excluded || 0 === strpos( $relative_path, $excluded . DIRECTORY_SEPARATOR ) ) {
+						return false;
+					}
+				}
+			}
+
+			return true;
+		}
+	);
+	$iterator = new RecursiveIteratorIterator( $filter );
@@
-		foreach ( $excluded_dirs as $excluded ) {
-			$needle = $excluded . DIRECTORY_SEPARATOR;
-			if ( 0 === strpos( $relative_path, $needle ) || false !== strpos( $relative_path, DIRECTORY_SEPARATOR . $needle ) ) {
-				continue 2;
-			}
-		}
-
 		if ( 'php' === strtolower( (string) $item->getExtension() ) ) {
 			$files[] = $path;
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/update-since-tags.php` around lines 149 - 167, The
edac_get_all_php_files() scan still recurses into excluded trees before
filtering, so update the RecursiveDirectoryIterator/RecursiveIteratorIterator
flow to prune directories up front rather than only skipping files after
descent. Add directory-level exclusion handling in edac_get_all_php_files()
using the existing $excluded_dirs list so .git, vendor, node_modules, build, and
dist are never traversed, and keep the current file-collection logic intact for
non-excluded paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/update-since-tags.php`:
- Around line 54-59: The `--root` normalization in `update-since-tags.php` is
stripping valid filesystem root paths by using `rtrim` on `$root`, which turns
`/` into empty and `C:\` into `C:`. Update the root handling logic around
`$opts['root']`, `dirname(__DIR__)`, and the `rtrim` step so absolute filesystem
roots are preserved while still removing only non-root trailing separators.
Ensure later directory traversal continues to use the normalized `$root` value
correctly.

---

Nitpick comments:
In `@tools/update-since-tags.php`:
- Around line 149-167: The edac_get_all_php_files() scan still recurses into
excluded trees before filtering, so update the
RecursiveDirectoryIterator/RecursiveIteratorIterator flow to prune directories
up front rather than only skipping files after descent. Add directory-level
exclusion handling in edac_get_all_php_files() using the existing $excluded_dirs
list so .git, vendor, node_modules, build, and dist are never traversed, and
keep the current file-collection logic intact for non-excluded paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ca476be2-836a-47a8-8411-d71a699bb849

📥 Commits

Reviewing files that changed from the base of the PR and between 1fb7859 and 4c1dfeb.

📒 Files selected for processing (1)
  • tools/update-since-tags.php

Comment thread tools/update-since-tags.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/update-since-tags.php`:
- Around line 83-88: The option parsing in update-since-tags.php only rejects
`false`, so repeated `--changed-since-tag` inputs from `getopt()` can slip
through as an array and later become `"Array"` instead of failing as invalid
args. Update the `changed_since_tag_opt` handling to explicitly detect
non-string values/arrays before assigning `changed_since_tag`, and emit the same
bad-args error path used for missing values. Keep the fix localized around the
`changed_since_tag_opt` branch and the `changed_since_tag` assignment.
- Around line 97-103: The ref check in update-since-tags.php is treating every
empty rev-parse result as an invalid tag, even when the real problem is that the
supplied --root is not a valid Git repository. Update the validation flow around
edac_run_git() and the changed_since_tag check so it first verifies the work
tree/repository at $root before blaming the ref, and emit a separate
repository-not-found/error message when Git cannot inspect the repo. Keep the
existing invalid-ref handling only for cases where the repository is valid and
rev-parse --verify fails for the tag/ref itself.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7224da26-1287-4ba6-a2c0-f94bfb6550a4

📥 Commits

Reviewing files that changed from the base of the PR and between 4c1dfeb and 9eb48e0.

📒 Files selected for processing (2)
  • scripts/prep_release.sh
  • tools/update-since-tags.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/prep_release.sh

Comment on lines +83 to +88
$changed_since_tag_opt = $opts['changed-since-tag'] ?? null;
if ( false === $changed_since_tag_opt ) {
edac_write_line( 'Error: --changed-since-tag requires a value.', STDERR );
exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$changed_since_tag = $changed_since_tag_opt;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject repeated --changed-since-tag values.

getopt() returns an array when the same option is passed more than once. This branch only guards false, so repeated --changed-since-tag values can fall through and later get string-cast into "Array" instead of failing cleanly as bad input.

Suggested fix
 $changed_since_tag_opt = $opts['changed-since-tag'] ?? null;
 if ( false === $changed_since_tag_opt ) {
 	edac_write_line( 'Error: --changed-since-tag requires a value.', STDERR );
 	exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
 }
+if ( null !== $changed_since_tag_opt && ! is_string( $changed_since_tag_opt ) ) {
+	edac_write_line( 'Error: --changed-since-tag may only be provided once.', STDERR );
+	exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+}
 $changed_since_tag      = $changed_since_tag_opt;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$changed_since_tag_opt = $opts['changed-since-tag'] ?? null;
if ( false === $changed_since_tag_opt ) {
edac_write_line( 'Error: --changed-since-tag requires a value.', STDERR );
exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$changed_since_tag = $changed_since_tag_opt;
$changed_since_tag_opt = $opts['changed-since-tag'] ?? null;
if ( false === $changed_since_tag_opt ) {
edac_write_line( 'Error: --changed-since-tag requires a value.', STDERR );
exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ( null !== $changed_since_tag_opt && ! is_string( $changed_since_tag_opt ) ) {
edac_write_line( 'Error: --changed-since-tag may only be provided once.', STDERR );
exit( EDAC_SINCE_TOOL_EXIT_BAD_ARGS ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$changed_since_tag = $changed_since_tag_opt;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/update-since-tags.php` around lines 83 - 88, The option parsing in
update-since-tags.php only rejects `false`, so repeated `--changed-since-tag`
inputs from `getopt()` can slip through as an array and later become `"Array"`
instead of failing as invalid args. Update the `changed_since_tag_opt` handling
to explicitly detect non-string values/arrays before assigning
`changed_since_tag`, and emit the same bad-args error path used for missing
values. Keep the fix localized around the `changed_since_tag_opt` branch and the
`changed_since_tag` assignment.

Comment on lines +97 to +103
// Validate the supplied ref actually exists before proceeding.
if ( null !== $changed_since_tag ) {
$ref_check = trim( edac_run_git( $root, 'rev-parse --verify ' . escapeshellarg( (string) $changed_since_tag ) ) );
if ( '' === $ref_check ) {
edac_write_line( 'Error: invalid Git reference or tag: ' . (string) $changed_since_tag, STDERR );
exit( EDAC_SINCE_TOOL_EXIT_RUNTIME_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate the Git work tree before blaming the ref.

This path treats any empty rev-parse --verify output as an invalid ref. If --root points at a non-repo directory, the tool emits the wrong error message even though the real failure is that Git cannot inspect the repository at all.

Suggested fix
 // Validate the supplied ref actually exists before proceeding.
 if ( null !== $changed_since_tag ) {
+	$repo_check = trim( edac_run_git( $root, 'rev-parse --is-inside-work-tree', $repo_exit_code ) );
+	if ( 0 !== $repo_exit_code || 'true' !== $repo_check ) {
+		edac_write_line( 'Error: --root is not a Git working tree: ' . $root, STDERR );
+		exit( EDAC_SINCE_TOOL_EXIT_RUNTIME_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+	}
+
 	$ref_check = trim( edac_run_git( $root, 'rev-parse --verify ' . escapeshellarg( (string) $changed_since_tag ) ) );
 	if ( '' === $ref_check ) {
 		edac_write_line( 'Error: invalid Git reference or tag: ' . (string) $changed_since_tag, STDERR );
 		exit( EDAC_SINCE_TOOL_EXIT_RUNTIME_ERROR ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/update-since-tags.php` around lines 97 - 103, The ref check in
update-since-tags.php is treating every empty rev-parse result as an invalid
tag, even when the real problem is that the supplied --root is not a valid Git
repository. Update the validation flow around edac_run_git() and the
changed_since_tag check so it first verifies the work tree/repository at $root
before blaming the ref, and emit a separate repository-not-found/error message
when Git cannot inspect the repo. Keep the existing invalid-ref handling only
for cases where the repository is valid and rev-parse --verify fails for the
tag/ref itself.

@pattonwebz pattonwebz merged commit 881b1ab into develop Jul 8, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant