Skip to content

Add reverse sync (export) mode to apply-repo-settings action#84

Open
nsheaps wants to merge 9 commits into
mainfrom
claude/zealous-heisenberg-wfaxhk
Open

Add reverse sync (export) mode to apply-repo-settings action#84
nsheaps wants to merge 9 commits into
mainfrom
claude/zealous-heisenberg-wfaxhk

Conversation

@nsheaps

@nsheaps nsheaps commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Summary

Extends the apply-repo-settings action with a new mode: export that reverses the sync direction: instead of pushing the settings file to the repo, it reads the repo's live configuration from the GitHub API and writes it back into the settings file. This enables capturing manual changes made in the GitHub UI back into source control.

Key Changes

  • New mode input parameter (apply or export, defaults to apply)

    • apply mode: existing behavior — read settings file and apply to repo
    • export mode: read repo's live state and write back to settings file
  • Export implementation for repository section

    • Fetches live repo config via GET /repos/{owner}/{repo}
    • Only updates keys already present in the file's .repository block (preserves deliberately-omitted keys)
    • Uses yq deep-merge to normalize the touched section
  • Export implementation for rulesets section

    • Fetches all live rulesets via paginated GET /repos/{owner}/{repo}/rulesets
    • Normalizes each ruleset to match the file schema (name, target, enforcement, conditions, bypass_actors, rules)
    • Replaces the entire .rulesets array wholesale
  • Bootstrap support for export mode

    • If settings file doesn't exist and mode: export, creates it with a header comment instead of failing
    • Allows workflows to generate initial settings from live repo state
  • New outputs

    • changed: export mode only — "true" if file was modified, "false" otherwise
    • summary: now includes mode and changed fields in export mode
  • Documentation updates

    • README includes reverse sync explanation and example workflow using branch_protection_rule trigger
    • Action metadata updated to describe both modes and new outputs

Notable Implementation Details

  • Export mode uses yq for YAML editing and jq for JSON transformation, ensuring consistent formatting
  • Repository export is conservative: only touches keys the file already manages, preventing unintended key introduction
  • Ruleset export normalizes the API response to the file's schema, dropping unnecessary fields and inline comments (acceptable for auto-captured diffs)
  • Classic branch protection (legacy branches: API) is intentionally not exported — the org uses rulesets
  • Dry-run mode works in both directions, printing what would change without modifying the file

https://claude.ai/code/session_018dM5zNQzewT1dLqGWEU2x9

claude and others added 4 commits June 8, 2026 18:41
Add a 'mode' input (apply|export). In export mode the action reads the
repo's live rulesets (and managed repository keys) from the API and writes
them back into settings.yml, exposing a 'changed' output. Pairs with a
branch_protection_rule-triggered workflow to capture UI changes back into
source control.
…t docs

Remove branch_protection_rule references from the action comments and README.
The reverse-sync export is now driven by a workflow-file push (preview in the
same branch) plus a weekly schedule, not the branch_protection_rule event.
The action itself is event-agnostic; this is a docs/comment update only.
…testing

Documents pinning a consumer workflow at the action's branch/SHA to prove
changes work before merge, and the false-positive trap where GitHub silently
ignores undefined inputs (so a consumer pinned to @main goes green without
running new code). Captures the real apply-repo-settings mode:export case.
claude and others added 5 commits June 9, 2026 17:23
Add apply + export support for labels, collaborators, and teams alongside
repository/rulesets. Export captures ALL repo labels and direct collaborators/
teams into settings.yml; apply creates/updates them. Non-destructive for every
section (never deletes labels/rulesets or revokes access) with TODO(prune)
markers to add an opt-in authoritative mode later. Default sections now include
all five. Labels section replaces the github-label-sync flow.
…ossless)

Export no longer replaces sections wholesale. It now builds a live-state source
(full repository key set; rulesets/labels/collaborators/teams) and deep-merges
it INTO settings.yml via merge_live.py (ruamel, comment-preserving):
- file wins on existing scalars; live only ADDS missing keys/list-items
  (e.g. a new bypass actor, collaborator, or ruleset appended to the end)
- nothing already in the file is removed — pending settings not yet applied to
  the repo survive
- comments/formatting preserved; file rewritten only on real content change
Repository export captures the full settable key set, not just keys already in
the file.
Add 10 unit tests covering the merge semantics: target wins on existing
scalars, missing keys/list-items added (rulesets/rules/bypass_actors/labels/
collaborators/teams by identity), new items appended, target-only entries never
removed, comment preservation, dry-run no-write, and idempotent change
detection. Wire a 'test' mise task + check.yaml Test job (python 3.12).
The repo .editorconfig mandates indent_size=2 for all files, which made
editorconfig-checker fail on the new 4-space (idiomatic) Python. Add a [*.py]
override so the action's Python passes the Format check.
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.

2 participants