Citation Sync Action
ActionsTags
(2)Automatically synchronize your CITATION.cff file version and date with Git tags. Keep your citation metadata accurate and up-to-date without manual effort!
- 🏷️ Automatic Synchronization: Updates
CITATION.cffwhen you push version tags - 🔀 Two Update Modes:
increment: Creates new incremented tag with updated citation (default)match: Updates citation to match current tag without creating new tag
- 🎯 Flexible Version Formats: Supports custom prefixes (
v,release-, etc.) and pre-release versions - 🌿 Smart Branch Detection: Auto-detects your default branch
- ✅ Validation: Validates
CITATION.cffstructure before and after updates - 🔄 Automatic Rollback: Restores original file if validation fails
- 📝 Clear Error Messages: Provides actionable suggestions when issues occur
- 🎨 Customizable: Configure commit messages, version formats, and more
Add this workflow to .github/workflows/citation-sync.yml:
name: Update CITATION.cff on Tag
on:
push:
tags:
# Match semantic version tags: v1.0.0, v1.2.3, v2.0.0-beta, etc.
# Excludes moving tags: v1, v2
- 'v[0-9]+.[0-9]+.[0-9]+*'
permissions:
contents: write
jobs:
update-citation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: Adamtaranto/citation-sync-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}That's it! Now whenever you push a version tag like v1.0.0, the action will:
- Check if
CITATION.cffneeds updating - Update version and date-released fields
- Create a new incremented tag (e.g.,
v1.0.1) with the changes
Creates a new incremented version tag with updated citation:
- uses: Adamtaranto/citation-sync-action@v1
with:
update-mode: increment # defaultExample Flow:
- You push tag
v1.0.0 CITATION.cffhas version0.9.0- Action updates to version
1.0.1and creates new tagv1.0.1 - Original tag
v1.0.0remains unchanged
Updates citation to match the current tag without incrementing:
- uses: Adamtaranto/citation-sync-action@v1
with:
update-mode: matchExample Flow:
- You push tag
v1.0.0 CITATION.cffhas version0.9.0- Action updates to version
1.0.0 - No new tag is created
- uses: Adamtaranto/citation-sync-action@v1
with:
# GitHub token (default: github.token)
token: ${{ secrets.GITHUB_TOKEN }}
# Path to CITATION.cff (default: 'CITATION.cff')
citation-path: 'CITATION.cff'
# Target branch (default: auto-detect)
target-branch: 'main'
# Create PR instead of pushing to main
# If using `use-pull-request: true`, must also add `pull-requests: write` to 'permissions'
use-pull-request: true
# Update mode: 'increment' or 'match' (default: 'increment')
update-mode: 'increment'
# Version tag prefix (default: 'v')
version-prefix: 'v'
# Version format regex (default: semantic versioning with optional pre-release)
version-format: '^([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-zA-Z0-9.-]+)?$'
# Enable debug output (default: 'false')
enable-debug: 'false'
# Commit message template (default shown, use {version} placeholder)
commit-message: 'chore: Update CITATION.cff to version {version}'
# Git user configuration
git-user-name: 'github-actions[bot]'
git-user-email: 'github-actions[bot]@users.noreply.github.com'
# Add [skip ci] to commit (default: 'true')
skip-ci: 'true'
# Fail if incremented tag exists (default: 'true')
fail-on-conflict: 'true'
# Validate CITATION.cff format (default: 'true')
validate-cff: 'true'Support for different version tag formats:
# Standard 'v' prefix (default)
- uses: Adamtaranto/citation-sync-action@v1
with:
version-prefix: 'v' # Tags: v1.0.0, v2.0.0
# Release prefix
- uses: Adamtaranto/citation-sync-action@v1
with:
version-prefix: 'release-' # Tags: release-1.0.0
# No prefix
- uses: Adamtaranto/citation-sync-action@v1
with:
version-prefix: '' # Tags: 1.0.0, 2.0.0Pre-release versions are fully supported:
on:
push:
tags:
- 'v*'
jobs:
update-citation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: Adamtaranto/citation-sync-action@v1Example:
- Tag:
v1.0.0-beta.1→ CITATION.cff version:1.0.0-beta.1 - Tag:
v2.0.0-rc.2→ CITATION.cff version:2.0.0-rc.2 - When incrementing from
v1.0.0-beta, new version is1.0.1(pre-release suffix dropped)
| Input | Description | Required | Default |
|---|---|---|---|
token |
GitHub token for authentication | No | ${{ github.token }} |
citation-path |
Path to CITATION.cff file | No | CITATION.cff |
target-branch |
Branch to push updates to (empty = auto-detect) | No | '' |
update-mode |
Update mode: increment or match |
No | increment |
version-prefix |
Expected version tag prefix | No | v |
version-format |
Semantic version regex pattern | No | ^([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-zA-Z0-9.-]+)?$ |
enable-debug |
Enable debug output | No | false |
commit-message |
Commit message template (use {version}) |
No | chore: Update CITATION.cff to version {version} |
git-user-name |
Git user name for commits | No | github-actions[bot] |
git-user-email |
Git user email for commits | No | github-actions[bot]@users.noreply.github.com |
skip-ci |
Add [skip ci] to commit message | No | true |
fail-on-conflict |
Fail if incremented tag already exists | No | true |
validate-cff |
Validate CITATION.cff format | No | true |
use-pull-request |
Create PR instead of direct push (for protected branches) | No | false |
pr-branch-prefix |
Prefix for PR branch names | No | citation-sync- |
pr-title |
PR title template (use {version}) |
No | Update CITATION.cff to version {version} |
pr-body |
PR body template (use {version}, {date}, {original_tag}) |
No | This PR updates CITATION.cff based on tag {original_tag}. |
| Output | Description |
|---|---|
needs-update |
Whether CITATION.cff needed updating (true/false) |
skipped |
Whether the update was skipped due to newer tags existing (true/false) |
original-tag |
The tag that triggered this action |
new-tag |
The new tag created (increment mode only) |
new-version |
The new version written to CITATION.cff |
commit-sha |
The commit SHA with updated CITATION.cff |
target-branch |
The branch that was updated |
pull-request-number |
The PR number (if use-pull-request: true) |
pull-request-url |
The PR URL (if use-pull-request: true) |
- uses: Adamtaranto/citation-sync-action@v1
id: citation-sync
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Check if update was needed
if: steps.citation-sync.outputs.needs-update == 'true'
run: |
echo "Updated to version: ${{ steps.citation-sync.outputs.new-version }}"
echo "New commit: ${{ steps.citation-sync.outputs.commit-sha }}"The action requires contents: write permission to:
- Commit changes to CITATION.cff
- Create and push tags (increment mode)
- Push to the default branch
permissions:
contents: writeIf using use-pull-request: true, also add pull-requests: write:
permissions:
contents: write
pull-requests: write # Required for creating pull requestsmain).
If your default branch has protection rules enabled (requires PR reviews, status checks, etc.), you have several options:
Enable PR mode to create a pull request instead of pushing directly:
- uses: Adamtaranto/citation-sync-action@v1
with:
use-pull-request: true
token: ${{ secrets.GITHUB_TOKEN }}The action will:
- Create a new branch with the citation updates
- Open a pull request to your default branch
- You can then review and merge through your normal PR workflow
increment mode with use-pull-request: true, you'll need to manually create the new version tag after merging the PR.
Configure the action to push to a non-protected branch:
- uses: Adamtaranto/citation-sync-action@v1
with:
target-branch: citation-updatesUse a Personal Access Token (PAT) with admin privileges or bypass permissions:
- uses: Adamtaranto/citation-sync-action@v1
with:
token: ${{ secrets.ADMIN_PAT }}In your repository settings, configure branch protection to allow this workflow to bypass restrictions:
- Go to Settings → Branches → Branch protection rules
- Edit your protection rule
- Under "Allow specified actors to bypass required pull requests", add the GitHub Actions app
The action automatically detects if your target branch is protected and will:
- ✅ Proceed normally if
use-pull-request: true - ❌ Fail with a helpful error message if
use-pull-request: false(with solutions) ⚠️ Warn if protection status cannot be determined
You must checkout with sufficient history to access tag information:
- uses: actions/checkout@v5
with:
fetch-depth: 0 # Fetch all history for all tagsYour CITATION.cff must:
- Be valid YAML
- Have exactly one
versionfield - Have exactly one
date-releasedfield - Include required CFF fields:
cff-version,message,title,authors
For a detailed explanation of the action's behavior, see OVERVIEW.md.
Quick Summary:
- Validation: Checks CITATION.cff exists and is valid
- Tag Parsing: Extracts version from tag using configured prefix/format
- Branch Detection: Auto-detects default branch if not specified
- Version Check: Detects if newer version tags exist (prevents downgrades)
- Comparison: Checks if version and date need updating
- Update: Creates backup, updates fields, validates changes
- Commit: Commits changes with configured message
- Tag/Push: Creates new tag (increment mode) or pushes commit (match mode)
- Rollback: Automatically restores backup if validation fails
The action automatically protects against version downgrades when retrospective tags are created:
- Scenario: You tag an old commit as
v1.0.1whenv1.0.6already exists - Behavior: The action detects
v1.0.6is newer and skips the update - Result: CITATION.cff remains at version
1.0.6(no downgrade) - Output: The
skippedoutput will betrueand job summary explains why
This ensures your CITATION.cff always reflects the latest version, even when old commits are tagged later.
name: Sync CITATION.cff
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: Adamtaranto/citation-sync-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}name: Update Citation Metadata
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: Adamtaranto/citation-sync-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
update-mode: match
commit-message: 'docs: Update CITATION.cff for release {version}'name: Update Citation via PR
on:
push:
tags:
- 'v*'
permissions:
contents: write
pull-requests: write # Required for creating PRs
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: Adamtaranto/citation-sync-action@v1
id: sync
with:
token: ${{ secrets.GITHUB_TOKEN }}
use-pull-request: true
pr-title: 'chore: Update CITATION.cff to {version}'
pr-body: |
Automated citation update triggered by tag {original_tag}.
This PR updates the CITATION.cff file with:
- Version: {version}
- Date: {date}
Please review and merge.
- name: Comment on PR
if: steps.sync.outputs.pull-request-number != ''
run: |
echo "Created PR #${{ steps.sync.outputs.pull-request-number }}"
echo "URL: ${{ steps.sync.outputs.pull-request-url }}"name: Citation Sync with Custom Settings
on:
push:
tags:
- 'release-*'
permissions:
contents: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: Adamtaranto/citation-sync-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
version-prefix: 'release-'
target-branch: 'develop'name: Citation Sync with Notifications
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: Adamtaranto/citation-sync-action@v1
id: sync
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Notify on update
if: steps.sync.outputs.needs-update == 'true'
run: |
echo "::notice::Created new version ${{ steps.sync.outputs.new-version }}"
# Add your notification logic here (Slack, email, etc.)Cause: The file doesn't exist at the specified path.
Solution: Ensure CITATION.cff exists in your repository root, or specify the correct path:
with:
citation-path: 'path/to/CITATION.cff'Cause: Your tag doesn't follow the expected version format.
Solution: Check your version-prefix and version-format inputs match your tags:
with:
version-prefix: 'v' # or 'release-', '', etc.
version-format: '^([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-zA-Z0-9.-]+)?$'Cause: In increment mode, the next version tag already exists.
Solution: Either:
- Use
update-mode: matchto update without creating a new tag - Set
fail-on-conflict: falseto proceed anyway - Manually resolve the tag conflict
Cause: Duplicate or missing version field in CITATION.cff.
Solution: Ensure your CITATION.cff has exactly one version: line.
Enable debug output for troubleshooting:
- uses: Adamtaranto/citation-sync-action@v1
with:
enable-debug: 'true'If you encounter issues:
- Check the troubleshooting section above
- Review the action logs with debug mode enabled
- Search existing issues
- Open a new issue with:
- Your workflow file
- Error messages
- Repository details (if public)
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.
If you use this action in your research software, you can cite it as:
@software{citation_sync_action,
author = {Taranto, Adam},
title = {Citation Sync Action},
year = {2025},
url = {https://github.com/Adamtaranto/citation-sync-action}
}This action was developed to simplify citation management for research software projects. Special thanks to the Citation File Format community for creating and maintaining the CFF specification.
Citation Sync Action is not certified by GitHub. It is provided by a third-party and is governed by separate terms of service, privacy policy, and support documentation.