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.