Skip to content
Talk to our solutions team

Tag a release across many repositories

One version tag applied to every repository in a release, created locally first, verified, and pushed only once every repository has tagged cleanly.

When you finish a release is one command, and a half-applied release is something the flow refuses to produce rather than something you clean up.

Tagging thirty repositories by hand is thirty chances to make the release inconsistent.

Without thisWith this
A typo puts v1.2.O on one repositoryOne variable, used everywhere
The tenth repository fails and ten are already pushedTag all locally, push only when all succeeded
Lightweight tags with no author, date or messageAnnotated tags carrying who cut the release and when
A tag silently moves because it already existedThe flow checks first and stops
”Which repositories are in 1.2.0?” is archaeologyThe list is the answer

The two-phase shape — tag everything locally, then push — is the whole point. Local tags are free to delete; pushed tags are not, and a partially pushed release is the mess you are avoiding.

You needWhy
The fleet on disk and currentTag what you think you are tagging
Push accessThe second phase
A decided versionBump it deliberately, not from a script
name: tag-release
workingdirectory: ~/workspace
vars:
version: "1.2.0"
message: "Release 1.2.0"
tables:
repos:
type: csv
file: ./fleet.csv

The most damaging failure in release tagging is a tag that silently moves. Check before creating.

tasks:
- name: precheck
table: repos
shell: |
cd {{name}} || exit 0
if git rev-parse -q --verify "refs/tags/{{version}}" >/dev/null; then
echo "ERROR {{name}} already has {{version}}"
exit 1
fi
echo "ok {{name}}"

No continueonerror on this flow: if any repository already carries the version, stop and let a human decide.

- name: tag
table: repos
shell: |
set -e
cd {{name}}
git tag -a "{{version}}" -m "{{message}}"
echo "tagged {{name}} $(git rev-parse --short HEAD)"

-a makes it annotated: a real object with an author, a date and a message. A lightweight tag is just a moving pointer and tells you nothing later.

Step 4 — show what you are about to publish

Section titled “Step 4 — show what you are about to publish”

The last moment where undoing costs nothing.

- name: review
table: repos
shell: |
cd {{name}} || exit 0
printf "%-28s %s\n" "{{name}}" "$(git rev-parse --short {{version}})"
- name: push
table: repos
shell: |
set -e
cd {{name}}
git push origin "{{version}}"
echo "pushed {{name}} {{version}}"
name: tag-release
list: true
workingdirectory: ~/workspace
vars:
version: "1.2.0"
message: "Release 1.2.0"
tables:
repos:
type: csv
file: ./fleet.csv
tasks:
- name: precheck
table: repos
shell: |
cd {{name}} || exit 0
if git rev-parse -q --verify "refs/tags/{{version}}" >/dev/null; then
echo "ERROR {{name}} already has {{version}}"
exit 1
fi
echo "ok {{name}}"
- name: tag
table: repos
shell: |
set -e
cd {{name}}
git tag -a "{{version}}" -m "{{message}}"
echo "tagged {{name}} $(git rev-parse --short HEAD)"
- name: review
table: repos
shell: |
cd {{name}} || exit 0
printf "%-28s %s\n" "{{name}}" "$(git rev-parse --short {{version}})"
- name: push
table: repos
shell: |
set -e
cd {{name}}
git push origin "{{version}}"
echo "pushed {{name}} {{version}}"

The flow’s advantage is the record. If the push fails on repository nine of thirty, the run names it — and the tags that were pushed are the eight before it, which is exactly what you need to know to clean up.

Rehearse the whole thing first:

Terminal window
kis flow -f tag-release.yaml --dryrun

Tag and review without publishing:

Terminal window
kis flow -f tag-release.yaml -t precheck,tag,review

Then publish:

Terminal window
kis flow -f tag-release.yaml -t push

Splitting the run with -t is how the two phases stay two phases.

See Audit tags across the fleet (planned) — it exists precisely to answer “did every repository get this version”.

ChangeWhere
Tag a known-good commit, not HEADSee Tag at a specific commit (planned)
Sign the tagsgit tag -s, with the signing key available on the runner
A prefix such as vPut it in version, not in the tasks
  • Audit tags across the fleet (planned)
  • Delete a tag safely (planned)