Skip to content

Synchronize tags - #3

Merged
Paebbels merged 3 commits into
devfrom
claude/synchronize-tags
Sep 10, 2026
Merged

Synchronize tags#3
Paebbels merged 3 commits into
devfrom
claude/synchronize-tags

Conversation

@pytooling-claude

@pytooling-claude pytooling-claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Rebased onto dev after #1 was merged; the base was retargeted automatically. Sibling of #2; the two are
independent.

New Features

  • Tags are synchronized. gh repo sync knows branches only, so a fork drifts behind its upstream in releases
    even while its branches are current. Measured on the repositories this action will run against: ghdl/ghdl has
    46 tags, Paebbels/ghdl has 19 — among the 27 missing are v1.0.0rc1, v0.36-rc1 and v0.35rc2.

  • An optional fourth field carries the tag patterns:

    ghdl/ghdl=ghdl:master:v\d+\.\d+.*
    OSVVM/OSVVM=OSVVM:main,dev:nightly,v\d+\.\d+\.\d+
    antonblanchard/microwatt=microwatt::v\d+\.\d+
    

    <upstream>=<fork>:<branches>[:<tagPatterns>]. A line without it behaves exactly as before, so every existing
    configuration file keeps working untouched
    . As the third line shows, the branch list may be empty when tag
    patterns are given — a fork followed for its releases only.

  • A pattern is a fixed name or a regular expression, matched against the whole tag name. Both forms in one
    list, comma separated.

  • What happens per matching tag of the upstream:

    Situation Result
    The fork doesn't have it Created at the same object — 🏷️ v2.1.0 — created from OSVVM/OSVVM@a1b2c3d
    The fork has it, same object Counted, reported as one line per repository (🟰 12 tag(s) already up to date)
    The fork has it, different object Error, tag left alone, run continues

    The moved case reads:
    ☢️ v1.0.0 — moved in 'OSVVM/OSVVM' (fork 90e6af7, upstream d3d07ba)
    and the annotation carries both SHAs. It is deliberately not rewritten: git push --force on a tag discards
    whatever the fork's tag points at, and that is not a decision an unattended nightly job should make. Deleting the
    tag in the fork lets the next run recreate it.

  • created-tags is a new output parameter and a new line in the summary. Tags are never deleted from a fork, and
    a tag existing only in the fork is left untouched.

Changes

  • The patterns of one line are joined into a single alternation(p1)|(p2) — rather than passed as several
    -e arguments. Not cosmetic: GNU grep refuses the latter.

    $ grep -xP -e 'v\d+' -e 'nightly'
    grep: the -P option only supports a single pattern    (exit 2)
    

    This container's grep is ugrep, which accepts multiple -e happily — so the naive form passes here and fails
    on every GitHub runner. Checked against /usr/bin/grep (GNU grep 3.11) before committing.

  • grep -P is probed once, and only reported when a line actually configures tags — a repository that
    synchronizes branches only needs nothing beyond gh.

  • The parser reads the fourth field, and rejects a line with neither branches nor tags. Splitting is on the first
    colon after the fork name, so a colon inside a pattern survives.

  • Each kind of message has its own symbol (from review): 🏷️ marks a created tag only, 🟰 the tags already pointing
    at the same object, ℹ️ the two nothing-to-do cases, and ☢️ a tag that moved upstream — in the log and in the
    Not synchronized: list, which needed each entry of that list to carry its own symbol instead of a hard-coded ❌.
    The README gains a table of the whole vocabulary.

Documentation

  • README.md gains a Tag Synchronization section: why gh can't do it, the pattern syntax with the PCRE note
    (and the trap that a "fixed name" is still a regex — v1.0 matches v1x0), the three per-tag outcomes, and what
    the action never does. The line-format table gains the fourth element, the worked PLC2 example now carries a tag
    pattern, the sample log shows both new lines, and Dependencies names the grep -P requirement.

Unit Tests

  • check-outputs verifies created-tags, and both verification jobs assert it. The Valid fixture now contains two
    lines with tag patterns — one of them without any branch — which proves in CI that the new field parses and that
    a three-field line is unaffected: the branch counts are unchanged at 4 synchronized, 2 skipped.
  • The workflow can't reach the tag path: dry-run touches no repository. Seven cases in the local harness cover it
    against a stub gh serving git/matching-refs/tags:
    • matching tags created, the non-matching nightly left alone, and the POST carrying ref=refs/tags/v1.0.0 with
      the upstream SHA,
    • a moved tag → one counted error with both SHAs, the other tag still created, the run continuing,
    • tags already up to date → counted, no POST at all,
    • a pattern matching nothing → reported, not an error,
    • an upstream repository without tags,
    • a fixed name beside a regular expression on a line with no branches → three tags created, gh repo sync never
      called,
    • dry-run printing the patterns and issuing no API call.
  • All 17 cases in the harness pass (10 from Add the 'SynchronizeForks' composite action #1, 7 new).

Others

  • Verified read-only against the real repositories while designing this: GET /repos/<owner>/<repo>/git/matching-refs/tags
    returns the ref and the object SHA per tag, and the SHAs of tags present in both ghdl/ghdl and Paebbels/ghdl all
    agree — so the "moved" case is genuinely rare, and comparing the ref's object is the right test. Note that the object
    compared is the annotated tag object where there is one, not the commit it points at.

Related Issues and Pull-Requests

🤖 Generated with Claude Code

@pytooling-claude pytooling-claude Bot added CI: GitHub Actions Continuous Integration issues related to GitHub Actions (Windows, Linux and MacOS) Documentation Improvements or additions to documentation Enhancement New feature or request labels Sep 10, 2026
Base automatically changed from claude/synchronize-forks-action to dev September 10, 2026 21:08
@pytooling-claude
pytooling-claude Bot force-pushed the claude/synchronize-tags branch from cc480f0 to 6d194ff Compare September 10, 2026 21:13
'gh repo sync' knows branches only, so a fork drifts behind its upstream in releases even while its branches are
current - 'ghdl/ghdl' has 46 tags, 'Paebbels/ghdl' has 19.

A configuration line gains an optional fourth field, colon separated, listing tag names or regular expressions:
'<upstream>=<fork>:<branches>[:<tagPatterns>]'. A line without it behaves exactly as before, and a line may carry
tag patterns without any branch. Patterns are matched against the whole tag name with 'grep -P', joined into one
alternation because GNU grep rejects several '-e' patterns together with '-P'.

A matching tag missing from the fork is created pointing at the same object. A tag that moved in the upstream
repository is reported as an error and left alone - rewriting it would discard whatever the fork's tag points at -
and the run continues. Tags are never deleted. 'created-tags' is a new output parameter.

Co-Authored-By: Patrick Lehmann <Paebbels@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnpaXYDwfNwnJrQew78j3f
@pytooling-claude
pytooling-claude Bot force-pushed the claude/synchronize-tags branch from 6d194ff to 519062f Compare September 10, 2026 21:18
Comment thread README.md Outdated
Comment thread README.md Outdated
claude-code and others added 2 commits September 10, 2026 21:43
Review feedback: 🏷️ meant both "a tag was created" and "here is some information about tags", and ❌ meant both
"a branch failed to synchronize" and "a tag moved".

🏷️ now marks a created tag only. 🟰 reports the tags that already point at the same object as upstream, ℹ️ the two
cases with nothing to do (the upstream has no tags, no tag matched), and ☢️ a tag that moved upstream - in the log
and in the 'Not synchronized' list, which required each entry of that list to carry its own symbol instead of a
hard-coded ❌.

The README gains a table of the symbols.

Co-Authored-By: Patrick Lehmann <Paebbels@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnpaXYDwfNwnJrQew78j3f
Review feedback: a moved tag reported two SHAs and nothing else, so a reader couldn't tell which of the two is
newer without looking both up by hand.

Both sides are resolved through 'GET /repos/<owner>/<repo>/commits/<tag>', which dereferences an annotated tag -
the tag ref's object SHA is the tag object there, and that endpoint rejects it. The report names the commit each
tag points at and that commit's date:

  ☢️ v1.0.0 — moved in 'OSVVM/OSVVM'
    ↪ fork:     90e6af7  2024-03-11 14:22:05 UTC
    ↪ upstream: d3d07ba  2025-07-02 09:41:18 UTC

The error annotation carries the full SHAs and both dates. When the two resolve to the same commit, the tag object
itself was recreated, and the report says so rather than leaving two identical lines unexplained. A lookup that
fails falls back to the tag refs without a date, so the moved tag is still reported.

The timestamp is reformatted with parameter expansion rather than 'date', which parses this input only in its GNU
flavour.

Co-Authored-By: Patrick Lehmann <Paebbels@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RnpaXYDwfNwnJrQew78j3f
@Paebbels
Paebbels merged commit 3ffda66 into dev Sep 10, 2026
3 checks passed
@Paebbels
Paebbels deleted the claude/synchronize-tags branch September 10, 2026 21:51
@pytooling-claude pytooling-claude Bot mentioned this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI: GitHub Actions Continuous Integration issues related to GitHub Actions (Windows, Linux and MacOS) Documentation Improvements or additions to documentation Enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants