Skip to content

Repository files navigation

labelsync

labelsync

Documentation: labelsync.specs.dev

Synchronise GitHub issue/PR labels across a set of repositories from a local YAML file.

labelsync is a reconciler, not a script: for each target repository it reads the current labels, resolves the desired set, computes an ordered plan, and then applies it — or prints it, under --dry-run. One labels.yml describes the labels you want; groups describe which repositories should have them. Running it twice changes nothing the second time.

labelsync selecting three repositories and printing the plan for them

The command surface, then a real dry run against three public repositories — two renames each, one drifted description, and everything else already in sync. It writes nothing, and the exit code carries the 2 bit because it found drift.

# labels.yml
version: 1

groups:
  ours:
    org: yourorg
    exclude: ["*-archive"]

defaults:
  groups: [ours]

renames:
  - from: "bug"
    to: "type: bug"

labels:
  - name: "type: bug"
    color: "d73a4a"
    description: "Something isn't working"
  - name: "type: feature"
    color: "0e8a16"
    description: "New functionality"
labelsync groups            # which repositories that selects, and why the rest were filtered out
labelsync sync --dry-run    # the plan; writes nothing; exits 2 if anything has drifted
labelsync sync              # apply it

What it does, in one list:

  • Creates, updates, and converges names, colours, descriptions, and casing across every selected repository. Nothing is deleted unless you ask for --mode=prune, which reports first and then asks which labels to remove.
  • Renames without losing anything. A renames: entry becomes a PATCH, so every issue and pull request that carried the old label still carries it under the new name.
  • Never touches a repository no group selects. That is the safety property the rest is built on.
  • Runs in CI. --dry-run sets the 2 bit on drift, so a pull-request check fails when the committed config and the live labels disagree — test the bit, because a run that also skipped a repository exits 6. --output=json emits NDJSON with a stable error_kind.

Install

brew install specsnl/tap/labelsync

Or go install github.com/specsnl/labelsync@latest, or download a tar.gz for your platform from the releases page — Linux and macOS, amd64 and arm64.

In a container, docker run --rm ghcr.io/specsnl/labelsync:0.1 --help — also published as ghcr.io/specsnl/labelsync/debian for when a step needs a shell.

Release candidates are a separate, opt-in cask, so brew upgrade never moves a stable install onto one — while the rc cask itself tracks the leading edge and upgrades onto a stable release once it ships:

brew install specsnl/tap/labelsync@rc

The two casks both provide a labelsync command and cannot be installed side by side — see Install channels.

Building from a checkout needs nothing but Docker and Task:

task build

Getting started

Export before you write a config. Descriptions in the config file are authoritative, so a config written from scratch clears every description your repositories already have:

labelsync export yourorg/yourrepo --out labels.yml

The rest — describing the repositories, the dry run, the first apply — is in Getting started, and everything else — the configuration file, every command and flag, running in CI, and how it is built — is on the same site: labelsync.specs.dev.


Contributing

Every command runs through Task, which wraps the Docker Compose services that pin the Go, golangci-lint, Node, and Hugo versions — so a check runs the same way locally as it does in CI. Run task --list for the full set.

task checkall   # tidy:check, lint, test, md:check — run this before opening a pull request

Conventions, workflow, and the house rules that reviews are held to: AGENTS.md.


License

MIT — see LICENSE.

About

A Github label synchronization tool

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages