Eagle Versioner is a powerful semantic versioning tool inspired by Angular's Commit Message Convention to calculate the version based on the changes made to the repository. It closely follows the Semantic Versioning Specification created by Tom Preston-Werner.
Table of Contents
- Main Features
- Change Types
- Version Commit
- Versioning Strategies
- Development Appendages
- Logging Levels
- Breaking Changes
- Handling an Initial Commit
- Managing Work in Progress
- Multiple Production Branches
- Changelog Generation
- Merging Changes from Other Branches
- CLI Help Output
- Usage Example
- Versioning Automation
- Docker Image Tagging
- Skipping CICD Pipeline Job Trigger
- Issues and Feature Requests
- Latest Changes
- More from the Author
- Copyright/License Notice
For best results, Eagle Versioner should be installed as a global package, so the main ev command can be invoked from anywhere in the terminal.
npm install -g eagle-versioner
There are three main features which make up the tool, all provided by a simple CLI.
Version Calculation - Calculates the semantic version by looking at commit message history for commits inspired by the Angular Commit Message Convention.
Changelog Creation - Creates a changelog based on the commit message history. Only shows changes affecting the version.
Specialized Commit Creation - Provides a mechanism for creating commits based on the desired change type. Enforces a custom implementation of the Angular Commit Message Convention by allowing the user to pick from a list of change types and entering the desired message while ensuring proper commit message formatting. It also offers an option to check spelling (interactive mode only) and persist words added to the dictionary under the user's home directory (e.g.
- WIP - Work is in progress and ongoing. Expect to squash some commits.
- Bug Fix (causes patch version change) - It's fun squashing bugs.
- Changelog - Updating the changelog for project visibility.
- Chore - Generic chore that doesn't fall in another change type.
- Dependency (causes patch version change) - Bumping dependencies or adding new ones.
- Doc - Documentation to keep stakeholders happy.
- Feature (causes minor version change) - Something new and shiny was added.
- Perf (causes patch version change) - The application is performing way better now.
- Refactor - For refactoring code but not adding new functionality.
- Styling - Fixing ugly code (spaces are better than tabs).
- Test - The addition of tests (e.g. unit tests, integration tests, etc.).
- Version Change - Updates to the file(s) containing version information.
Note: If the change type has been specified as a breaking change, the major version will be changed and minor and patch versions reset to zero.
It is important to understand that the Version Change type creates a commit with version information which is important data used by Eagle Versioner. When calculating the version, only commit history after the last production version change commit will be obtained. This is for optimization purposes; if no production version commits exist then the entire commit history would need to be obtained and depending on the size of the repository, could have a negative impact on performance.
In addition, the version change commit is used in the changelog creation process. In order to categorize certain commits under the correct version, the version change commit is needed. If no version change commits existed then the generated changelog would be missing relevant version information to identify what version the changes belong to.
Even if no version change commits existed, the version calculation process would still work but not optimally. Therefore, it is highly recommended to create version change commits by selecting the Version Change type option when using the ev commit command. This is demonstrated in the usage example.
Note: The only exception requiring version change commits is during initial development. In that case, the only way
to advance the version is to create a version change commit. Then after adding some more changes and running the
ev calculate command, the version will be incremented.
The default versioning strategy for Eagle Versioner is sequential. With this strategy, each applicable commit will be analyzed incrementally. If the commit affects the version, the associated version subset (e.g. major.minor.patch) will be updated.
Conversely, the optional collective versioning strategy will analyze applicable contiguous commits of types which affect the same version subset and count those commits together as one change. For example, if three contiguous commits are for bug fixes, the patch version would only be incremented by one. In contrast, the sequential versioning strategy would increment the patch version by three.
Note: The only exception for the collective versioning strategy is a breaking change which will always cause the major version to increment and the remaining minor and patch versions set to zero. Regardless of being contiguous, these breaking change commits will not be counted as one change.
Upon calculating the version, the type of branch that is currently checked out will determine whether any appendage is added after the version. For example, if it is detected that a development branch is currently checked out, by default it will append the name of the branch (e.g. 1.2.3-latest). In addition, there is also an option to append snapshot (e.g. 1.2.3-SNAPSHOT) to accommodate Maven.
Only development branches will trigger the appendage to appear after the version. Production branches such as master are intended to be release-ready code that can safely run in production. As such, no appendage is added in order to clearly distinguish between a production or development version. This is the only area where Eagle Versioner deviates from the Semantic Versioning Specification.
By default, only relevant output to the current command will be displayed in the terminal. This represents the INFO logging level. However, it is possible to switch to a different logging level to get more or less information. The available logging levels are INFO, DEBUG, WARNING, ERROR, VERBOSE, and SILENT.
Per the Semantic Versioning Specification, when backward compatibility with the Public API has been
broken, the major version is incremented. Eagle Versioner refers to this as a Breaking Change. Since this can occur
in a variety of circumstances, the
ev commit command will always ask if the commit is a breaking change. If it's a
breaking change, the major version will always be incremented regardless of the change type.
Handling an Initial Commit
There are situations where the application is already at a release-ready version and needs to be pushed to the applicable branch on the repository. In this context the repository will not yet contain any branches or commits due to the source code either being migrated from a different source control system or the lack of a source control system at all (not a good idea but a possible situation).
Rather than rewind the clock and attempt to create disparate commits with applicable messages for the existing code, all code can be lumped together into one commit referred to as an Initial Commit. However, creating an Initial Commit should only be reserved for this instance. Outside of this situation, disparate commits are necessary for each applicable change in order to properly use Eagle Versioner.
In order to create an Initial Commit, all applicable source code should be staged, the Version Change type selected, the option specified to indicate the change is for an Initial Commit (only applicable to the Version Change type), and the proper version entered when prompted (e.g. 1.0.0 for a production branch or 0.1.0-<APPENDAGE_TYPE> for a development branch). This will ensure the only commit is a version change commit which will later be used for calculating future versions.
Note: An Initial Commit can only be created once. Also, the creation of an Initial Commit will fail if the current branch already has commits. Therefore, an Initial Commit should be the first commit to seed the branch with existing code which encompasses several features.
Managing Work in Progress
During ongoing development of a project, it is common for developers to work in a feature branch and create many commits. It is nice to be able to create several commits while making changes so that they remain in history and can be reverted if necessary. Eagle Versioner makes this process easy by allowing for the creation of WIP commits. Once all development wraps in that feature branch, a final commit can be created which combines all the prior contiguous WIP commits into a commit of the final specified change type.
Note: Recommended for a feature branch only, ideally from a forked repository.
Multiple Production Branches
By design, Eagle Versioner should only be used with one production branch (usually master). This production branch should always represent code that is considered releasable with the highest quality. It is understandable to have multiple development branches at any given time and Eagle Versioner fully supports independent versioning of each branch.
However, after these branches are eventually merged and then pushed to the production branch, when calculating the production version, only the last production version change commit will be used (this is also true when calculating a development version). That means if multiple production version change commits end up getting merged out of order, only the most recent production version change commit will be used and everything else skipped. This could lead to unintended results so it's not recommended to use more than one production branch.
In contrast, even if multiple development version change commits exist in the branch after a merge and are out of order, it will have no impact on the calculated production version for reasons mentioned above. This too is by design so that multiple disparate development versions can exist which is a common requirement.
Finally, if multiple production version change commits are found in the production branch that are out of order, the generated changelog will also show these out of order versions. Again, this is why using only one production branch is recommended.
Note: If the need for multiple production branches exists, perhaps it's best to create a fork and continue with development as two separate projects.
It is important to understand how the changelog is generated by Eagle Versioner in order to understand how it includes versions along with the changes belonging to them.
Changelog Generation for Production Branch
In the production branch (e.g. master), the generated changelog will aggregate all found development versions from the detected version change commits and include them as a part of the latest production version.
For example, the production branch is at version 1.0.0 and the development branch is at 1.1.2-latest. When the development branch is merged with the production branch, a new version change commit is created to bring the production version to 1.1.2. After this has been done, the generated changelog will transfer ownership of commits belonging to 1.1.2-latest to 1.1.2. This is because these commits now make up the new production version.
Changelog Generation for Development Branch
In the development branch, the generated changelog will show all production versions along with their changes and the latest development versions after the last production version, again with their changes. In situations where multiple development branches end up getting merged into another development branch, it is likely multiple mismatching or out of order development version change commits now exist. Eagle Versioner will generate the changelog and keep the order of these development version change commits intact.
It is important to understand which commits are a part of which version during the development process, especially if multiple changes are merged from other branches on a regular basis. However, it is important to only merge changes from other branches either before starting work on new changes since the last version or after. For example, commits are made to the current branch but no version change commit has been made after to properly identify these changes as being part of the current branch. Then, when changes from another branch are merged into the current branch, any commits made in the current branch prior to the merge will now appear to belong to the next version - the most recent version change commit (closest to the last commit before the merge) coming from the merged branch.
Merging Changes from Other Branches
After changes are merged from other branches it is recommended to make a version change commit with the new calculated version. Any changes coming from another branch should be considered to belong to the current branch. This will also ensure the most recent version found in the current changelog matches up to the most recent version applicable to the current branch.
When calculating the version, Eagle Versioner will ignore development version change commits merged from other branches in order to accommodate the idea that merged changes now belong to the current branch and should be used to calculate the version applicable to the current branch.
CLI Help Output
Note: The below commands will only work when the current folder that's been navigated to (via the terminal, shell, etc.) contains a Git repository. Therefore, switch to the applicable folder before running these commands.
Usage: ev [options] [command]From the creation of special commits, calculates the semantic version based on the Git branch and commit history.Options:-v, --version output the version number-h, --help output usage informationCommands:calculate [options] Calculates the version based on the information provided by Git.commit [options] Creates a special commit later used when calculating the version.changelog [options] Creates the changelog file from versionable commits.
Usage: ev calculate [options]Calculates the version based on the information provided by Git.Options:--dev-appendage <appendageType> the type of dev version appendage--logging-level <loggingLevelTarget> the logging level target--prod-branch <branchName> the name of the production branch--strategy <strategyType> the type of versioning strategy-h, --help output usage information
Usage: ev changelog [options]Creates the changelog file from versionable commits.Options:--directory <dir> the name of the changelog directory--filename <fileName> the name of the changelog file--prod-branch <branchName> the name of the production branch--logging-level <loggingLevelTarget> the logging level target-h, --help output usage information
Usage: ev commit [options]Creates a special commit later used when calculating the version.Options:--manual option to skip interactive commit menu and specify options manually [disables spell checker]--change-type <changeType> the type of change--short-msg <shortMsg> the required short message belonging to the commit [ignored
Before creating a release, the application will have an initial development version. Per the Semantic Versioning Specification that version will start at 0.1.0 and each release prior to the 1.0.0 production release will only increment the minor version.
Once a production release has been made and active development continues, the version will change to 1.0.0-latest with the major, minor, and patch versions being incremented depending upon the changes found.
For a complete understanding, see the example steps below.
Create a Git repository by running
git initin the desired folder.
Create the desired development branch and switch to it by running
git checkout -b latest. Replace lastest with the desired branch name.
Perform development tasks and for each change run
git add .to stage the change(s); this will add all unstaged files. However, use
git add <FILE_NAME>to only add specific files. To see the status of staged/unstaged files, use the
git statuscommand. See this for complete documentation on the
Note: It is generally a good idea to stage the file(s) associated with one type of change instead of bundling all changes together. As a best practice, plan on one commit per change type (e.g. stage file(s) for a disparate change, commit them, and then move on to the next change). This provides an accurate version depiction.
Run the command
ev committo bring up the interactive commit menu and select the applicable change type. In this example, the feature change type is selected. Enter a short commit message which will serve as the header which briefly describes the change. If more details are needed, add them to the optional long commit message. This information will go directly under the short commit message and can bee seen by reviewing the Git log.
Note: There will be a prompt for specifying whether the change is breaking. However, this will not affect the initial development version as it would other version types (e.g. regular development and production). Only specify a breaking change if it truly is and keep in mind indicating this is important information that can help other developers. More information on breaking changes can be found here.
In addition, there will be another prompt asking whether to insert the [ci-skip] tag. Please see this for more information.
(optional) Calculate the initial development version by running
ev calculate. The output received is 0.1.0-latest. This command is generally used to get the version when the interactive mode of the
ev commitcommand isn't possible. In this case the output can be sent to the
ev commit --manual --change-type version_change --new-version 0.1.0-latestcommand.
Note: Each additional commit (regardless of change type) won't bump the version until after a version change commit is made. This only applies to the initial development version.
Create a version change commit by running
ev commitagain and selecting version change. When prompted for the version, enter 0.1.0-latest. The default value for the version should already be populated but can still be manually entered. The default value was derived using the same methods as the
Note: The version has to be entered manually to provide more control. In other words, the version can be incremented higher if necessary for a particular use case without using the calculated version. However, the version cannot be changed to something lower than the last recorded production version found in the most recent production version change commit.
Also, if multiple branches are being independently versioned and contain version change commits of their own, upon merging back to the desired branch there may be multiple development version change commits with version numbers appearing out of order, albeit with a development version format - likely with different development appendages. This is why only the last production version change commit is used when calculating the version and all non-production version change commits are ignored. Because of this, it's possible to enter a development version that is higher than the last production version then another development version that is higher than the last production version, but lower than the last development version.
In addition, in order to create the version change commit, a file will need to be modified so the commit will go through. As a best practice, modifying the file that contains the version should be done prior to making a version change commit. In the case of Node.js, updating the version found in the package.json should suffice.
Perform additional development tasks. In this example, a bug was found that has just been fixed. With the files staged, run
ev commitand select the bug fix change type and enter the relevant information.
Calculate the version again by running
ev calculate. The output received is 0.2.0-latest.
Note: As before, regardless of the number of commits made before running the version calculation command, the output will always increment the minor version by one. The only way to increment the minor version further is to perform a version change commit then an additional versionable commit. Keep in mind this only applies to the initial development version.
When ready to create a production release from the initial development version, ensure the latest applicable version change commit has been made (by selecting the Version Change type from the ev commit command). This can be done by modifying the file containing version information and then creating a version commit with the calculated version.
Switch to the production branch by running
git checkout master. If the branch doesn't exist, use
git checkout -b masterinstead. Be sure to replace master with the desired production branch name.
If the production branch already exists, merge the changes from the development branch by running
git merge latest. Otherwise, if the branch was just created then it should already have the changes from the development branch.
Calculate the version by running the
ev calculatecommand. The output should be 1.0.0.
Modify the file containing the version (e.g. package.json) and update the version to 1.0.0, the version obtained from running the
Stage the updated file by running
git add package.json. Replace package.json with the applicable file.
Create a version commit by running the
ev commitcommand and selecting Version Change as the change type. When prompted, enter 1.0.0 as the version.
If desired, generate a changelog by running the
ev changelogcommand. Be sure to stage the changelog and create a commit using the
ev commitcommand and selecting the Changelog change type.
Note: When ready to return to active development, change to the development branch of choice and merge the changes from the production branch. Continue making changes and using the
ev commitcommand to add them to the repository. Also, when ready to create a development version, run the
ev calculatecommand and then create a version change commit by modifying the applicable file with the version information. Finally, when ready to create a production version, follow the same steps above starting from Step 10.
One of the greatest benefits of using Eagle Versioner is being able to automate versioning. This is done using a CICD pipeline which detects changes pushed to a repository and then runs the appropriate commands to generate the version and create the associated commits or even the changelog.
The below commands are an example of the versioning automation process after changes have been detected in a branch.
NEW_VERSION=$(ev calculate) npm --no-git-tag-version version $NEW_VERSION git add package.json ev commit --manual --change-type version_change --new-version $NEW_VERSION ev changelog git add CHANGELOG.md ev commit --manual --change-type changelog --short-msg "Updated the Changelog"
The point in the CICD pipeline that these commands run is up to the implementor. Valid questions to ask are as follows.
Should the version be incremented only after pushes to the repository?
In this scenario the development team wouldn't worry about incrementing the version before pushing to the repository, ideally by submitting a pull request. Therefore, when the pull request is accepted, assuming the build and tests pass, it will be detected that a push has occurred to the repository which will trigger the CICD pipeline. Once that occurs, specific logic can execute which increments the version, generates the changelog, and creates/pushes commits for both.
In this situation it is important to setup logic to avoid running another build with tests since all that has changed is the version and changelog. However, in cases where the version is heavily coupled to application logic, pull requests should be submitted. Then the CICD pipeline checks out the code from the pull request and merges it with the target, then increments the version and creates the changelog before running the build with tests to detect any potential problems resulting from the version incrementation.
What is the recommended workflow for automatically incrementing the version?
The below example assumes that both pushes and pull requests trigger the CICD pipeline.
(Developer) Change/Add Code -> (Developer) Submit Pull Request -> (CICD Pipeline) Checkout Code and Merge With Target -> (CICD Pipeline) Increment the Version -> (CICD Pipeline) Run Build and Tests -> (Repository Admin) Accept Pull Request -> (CICD Pipeline) Increment the Version, Create the Changelog, Create and Push Resulting Commits with the [ci-skip] tag, Deploy Application, etc. -> (CICD Pipeline) Sees the Last Commits that Were Pushed Have the [ci-skip] Tag and Should Be Ignored (doesn't trigger another job)
When not using a CICD pipeline, how can incrementing the version be automated?
The answer to this question is Git Hooks. Perhaps after merging the changes from the development branch to the production branch a post-merge Git Hook executes a script which runs the commands above. There are many possibilities depending on the desired behavior and use case.
Docker Image Tagging
Eagle Versioner can also be used to tag a Docker image. Below is an example of how this can be achieved.
NEW_VERSION=$(ev calculate) docker build -t org/dashboard:$NEW_VERSION .
Please see this for more information on the Docker build command.
Skipping CICD Pipeline Job Trigger
As mentioned in the above recommended workflow, after a pull request is accepted it will trigger the CICD pipeline to run again. In that scenario, the logic in the CICD pipeline will use Eagle Versioner to calculate and increment the version and optionally create/update the changelog. When this happens it will create the necessary commits and push them. After the commits are pushed, the CICD pipeline will run again and perform redundant tasks. This wastes resources and isn't recommended.
To prevent this from happening, Eagle Versioner has an option to include the [ci-skip] tag. With this information a few things are possible. For example, if using GitLab, Jenkins, and the GitLab plugin for Jenkins, there is an advanced option for the Jenkins job configuration provided by the GitLab plugin which enables the checking of commits for the [ci-skip] tag. If this tag is found, Jenkins will not run the job.
Conversely, in another example logic is included in the CICD pipeline which detects if the commits have the [ci-skip] tag by getting the commit history. If the tag is found then the job can exit without performing any redundant tasks. The only downside to this approach is that for a brief period another job has to run which will consume resources for a short time, detect the commits aren't actionable, and exit. These jobs will also show up in the job history which may be undesirable.
Issues and Feature Requests
As with all software, issues can and are likely to occur. When encountering a possible bug, unexpected behavior, need assistance, or have an idea for a potential feature, please submit an Issue on this repository. Issues will be reviewed on a regular basis with a resolution being the main goal within a timely manner. However, turnaround time is not guaranteed.
Please see the changelog for the latest changes.
More from the Author
For blog posts and other interesting insights from the author, please visit danieleagle.com.
Eagle Versioner and the Eagle Versioner logo are Ⓒ Copyright 2020 Daniel Eagle. All rights reserved. Distributed under the MIT license. Please see Third Party Notices for information on other software dependencies included as a part of Eagle Versioner.