Note
Before updating your copyright with the current year, please assert that there is a value for you in doing so. I'm not trained, nor certified, in any legal matter, and based on empiric data it seems that specifying the year, or range of years, in a license or copyright header isn't a requirement.
The enormously popular microsoft/vscode repository has not updated their license since initial publication, nor is the year specified in source file copyright headers. Another repository that deliberately removed years from their copyright notice in 2023 is curl (commit).
So this seems to have happened. Instead of manually updating the license copyright years in my GitHub repositories I created this GitHub Action.
Oh, the loath I have for manual processes...
- Definitely not a Shakespeare quote
Was it a success in terms of productivity? - I could lie to you and say that it was.
Was it interesting to create? - Well certainly, it activated the few brains cells I have.
Can I use it? - Yes you can. It automatically supports the licenses listed below, but also support custom RegExp transformations where you specify your own license format.
- Apache 2.0 (Apache-2.0)
- BSD 2-clause "Simplified" (BSD-2-Clause)
- BSD 3-clause "New" or "Revised" (BSD-3-Clause)
- GNU Affero General Public License v3.0 only (AGPL-3.0-only)
- MIT (MIT)
Will this action commit anything on the default branch? - No. The action will create a new pull request, merging it will be your responsibility.
For the majority of repositories on GitHub the following workflow file will do the job. If you find that the outcome didn't meet your expectations, please refer to scenarios or open a new issue.
name: Update copyright year(s) in license file
on:
schedule:
- cron: '0 3 1 1 *' # 03:00 AM on January 1
jobs:
update-license-year:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
The action has support for the following inputs:
- uses: FantasticFiasco/action-update-license-year@v3
with:
# Personal access token (PAT) used when interacting with Git and GitHub.
#
# We recommend using a service account with the least permissions necessary. Also
# when generating a new PAT, select the least scopes necessary.
#
# [Learn more about creating and using encrypted secrets](https://help.github.com/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets)
#
# Required: true
token: ''
# A path or wildcard pattern specifying files to transform. Multiple paths can be
# specified using literal styled YAML.
#
# Required: false
# Default: LICENSE
path: ''
# A regular expression (JavaScript flavor) describing the license transform. The
# expression must have the following properties:
#
# - A capturing group named "from", encapsulating the first year of license
# validity
# - Written to support the RegExp flags "gmi" ("global", "multiline" and "ignore
# case")
#
# The expression will be used by String.prototype.replace() to apply the
# transformation.
#
# Required: false
# Default: null
transform: ''
# The branch name. Supports substituting variable {{currentYear}}.
#
# Required: false
# Default: license/copyright-to-{{currentYear}}
branchName: ''
# The git commit title. Supports substituting variable {{currentYear}}.
#
# Required: false
# Default: docs(license): update copyright year(s)
commitTitle: ''
# The git commit body that will be appended to commit title, separated by two line
# returns. Supports substituting variable {{currentYear}}.
#
# Required: false
# Default:
commitBody: ''
# The git author name, used when committing changes to the repository.
#
# Required: false
# Default: github-actions
commitAuthorName: ''
# The git author e-mail, used when committing changes to the repository.
#
# Required: false
# Default: [email protected]
commitAuthorEmail: ''
# The GPG private key, used in combination with gpgPassphrase when signing
# commits. Private keys protected by a passphrase are supported while private keys
# without a passphrase are unsupported.
#
# Required: false
# Default:
gpgPrivateKey: ''
# The GPG passphrase, used in combination with gpgPrivateKey when signing commits.
#
# Required: false
# Default:
gpgPassphrase: ''
# The title of the new pull request. Supports substituting variable
# {{currentYear}}.
#
# Required: false
# Default: Update license copyright year(s)
prTitle: ''
# The contents of the pull request. Supports substituting variable
# {{currentYear}}.
#
# Required: false
# Default:
prBody: ''
# Comma-separated list with usernames of people to assign when pull request is
# created.
#
# Required: false
# Default:
assignees: ''
# Comma-separated list of labels to add when pull request is created.
#
# Required: false
# Default:
labels: ''
The action is setting the following outputs:
currentYear
: The current year. This output will exist if action ran successfully and licenses where updated.branchName
: The name of the git branch created for the purpose of updating the licenses. This output will exist if action ran successfully and licenses where updated.pullRequestNumber
: The number of the GitHub pull request created for the purpose of updating the licenses. This output will exist if action ran successfully and licenses where updated.pullRequestUrl
: The URL of the GitHub pull request created for the purpose of updating the licenses. This output will exist if action ran successfully and licenses where updated.
For more information on outputs and their usage, please see GitHub Actions by Example: Outputs or the scenario named I want to update my license on first commit each year, and I want it merged.
The following chapter will showcase some common scenarios and their GitHub Action configuration.
- I'm new to GitHub Actions and don't know where to start
- I want to update my license annually at 03:00 AM on January 1
- I want to update my license using a manual trigger
- I want to update my license on first commit each year, and I want it merged
- I want to update my license, but it isn't called
LICENSE
- I want to update my license, but it isn't supported by this action
- I want to update all my licenses, I have more than one
- I want to update all my license in my monorepo
- I want to update the license in my source files
- I want to update my license and a custom source in the same PR
- I want to GPG sign my commits
- I want my pull requests to follow a convention
- I want my pull requests to be automatically merged
- I want my pull requests to trigger new GitHub Actions workflows
GitHub Actions is in detail described on the GitHub Actions documentation, but basically it boils down to creating a file in ./.github/workflows/
, e.g. ./.github/workflows/update-copyright-years-in-license-file.yml
, and then decide on when to trigger the action, and finally configure it if necessary.
The following scenarios will provide you with some examples.
This would be the most common usage of the action, given that you can put up with receiving a pull request during the new year festivities.
name: Update copyright year(s) in license file
on:
schedule:
- cron: '0 3 1 1 *'
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
A year is a long time and January 1 might be far off. Now that GitHub Actions supports manual triggers, we can use workflow_dispatch
to manually trigger our workflow.
name: Update copyright year(s) in license file
on: workflow_dispatch
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
Maybe you don't want to be disturbed during the new year festivities, and you know for a fact that you'll forget to manually trigger the workflow. Another alternative is to update your license at first commit each year? With the addition of automatically merging it? For those situations we can use the following workflow.
name: Update copyright year(s) in license file
on:
push:
branches:
- 'main' # Or 'master' depending on your default branch
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
id: license
- name: Merge PR
if: steps.license.outputs.pullRequestNumber != ''
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Replace '--merge' with '--rebase' to rebase the commits onto the base
# branch, or with '--squash' to squash the commits into one commit and
# merge it into the base branch.
# For more information regarding the merge command, please see
# https://cli.github.com/manual/gh_pr_merge.
run: gh pr merge --merge --delete-branch ${{ steps.license.outputs.pullRequestNumber }}
You have a license in your repository, but perhaps it isn't called LICENSE
. Maybe it's called LICENSE.md
? Then you'd have to configure the action accordingly.
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
path: LICENSE.md
This action has built in support for a couple of common licenses. However, you might use your own special license, and you've discovered that this action doesn't support it. In this case you can define your own transform.
The transform is declared as a regular expression (JavaScript flavor) and must have the following properties:
- A capturing group named
from
, encapsulating the first year of license validity - Written to support the RegExp flags
gmi
(global
,multiline
andignore case
)
The expression will be used by String.prototype.replace()
to apply the transformation.
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
transform: (?<=my own copyright )(?<from>\d{4})?-?(\d{4})?
Your repository might contain more than one license. Perhaps you have one for open source and one for commercial use? In any case, this action supports specifying multiple paths using literal styled YAML.
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
path: |
LICENSE-OPEN-SOURCE
LICENSE-COMMERCIAL
Your repository is perhaps a monorepo and you have a lot of licenses. You would like to update them all at once, preferably without having to specify each and every one. Well, we've got you covered. The path
input parameter supports glob patterns. Yay!
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
path: packages/*/LICENSE
You have a header in each and every source file specifying your license. That's a lot of files to update I guess. Well, we've got you covered. Glob patterns to the rescue.
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
path: src/**/*.js
Additionally to your license file, your project includes other files which require a custom year updating using transform
. You can do the full update in one pull request chaining multiples jobs with the needs
directive.
jobs:
license:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
source:
needs: license
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
path: '*.js'
transform: (?<=my own copyright )(?<from>\d{4})?-?(\d{4})?
You might rely on GitHub commit signature verification and just love the green sparkling badges. No worries, you can configure this action to use the private key with its corresponding passphrase to sign the commit.
Just remember that the GPG key must be registered to a valid GitHub user, and the e-mail address of that user must be configured as well.
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@master
with:
token: ${{ secrets.GITHUB_TOKEN }}
commitAuthorEmail: <your github email>
gpgPrivateKey: ${{ secrets.gpgPrivateKey }}
gpgPassphrase: ${{ secrets.gpgPassphrase }}
Your pull requests might follow some convention. It might require some specific title, or perhaps you wish the pull request to be assigned to a specific maintainer? Whatever the reason, we've got you covered.
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
branchName: license/{{currentYear}}
commitTitle: update my license
commitBody: Let's keep legal happy.
prTitle: Update my license
prBody: It's that time of the year, let's update the license.
assignees: MyUser, SomeMaintainer
labels: documentation, legal
Your pull requests can be merged and the branch deleted by utilizing GitHub CLI.
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Merge pull request
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Replace '--merge' with '--rebase' to rebase the commits onto the base
# branch, or with '--squash' to squash the commits into one commit and merge
# it into the base branch.
# For more information regarding the merge command, please see
# https://cli.github.com/manual/gh_pr_merge.
gh pr merge --merge --delete-branch
Your pull requests can trigger other GitHub Actions workflows, such as CI checks, if you use your personal access token (PAT) instead of GITHUB_TOKEN
.
To do this, you need create a personal access token with minimum required scope public_repo
and create a secret out of this token (e.g. LICENSE_SECRET
).
Put the name of your secret in the token
property:
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: FantasticFiasco/action-update-license-year@v3
with:
token: ${{ secrets.LICENSE_SECRET }}
The following users have made significant contributions to this project. Thank you so much!
Álvaro Mondéjar 💻 📖 💡 |
Gérôme Grignon 🐛 💻 |
Utkarsh Sethi 📖 |
C0D3 M4513R 🤔 |
Aleksei Borodin 💡 |
Jeroen van Warmerdam 🤔 🐛 |
Angel Aviel Domaoan 🐛 |