Imagine that you are versioning your sourcecode in git and building your code via maven. You need to make releases before deploying to production regularly. What should be the strategy we need to follow for releasing?
I've used maven-release-plugin for years to make releases. It worked perfectly with maven and svn, but we started to face problems when we migrated our code to git and to make releases on git.
After checking the literature, we decided to use JGit-Flow which is a maven plugin based on and is a replacement for the maven-release-plugin enabling support for git-flow style releases via maven.
I do not want to explain the details much because there are many great posts explaining all.
- JGit-Flow Atlassian Page
- Painless Maven project releases with Maven GitFlow Plugin
- Improved Releasing with Maven and Git
- Maven Git Release
We have specific needs guiding us through our release process. Let's cover what are our needs briefly.
- Release should be done at master branch.
- Release should be done at local only. We should be able to push all generated commits to remote manually afterwards.
- The commit graph should be as clean as possible. It means, there should not be any irrelevant commits exist after the release.
- We do not want to deploy the released package (pom, war or jar) to central repository during the release. We want to deploy if afterwards manually.
- If SNAPSHOT versions exist in dependencies, the release process should be halted.
- Development branch (next release branch) should always be kept in SNAPSHOT version.
- Master branch is always kept at the latest release version (not a SNAPSHOT).
Let's follow the steps one by one.
The values in the plugin configuration are set according to our needs.
<build>
<plugins>
<plugin>
<groupId>external.atlassian.jgitflow</groupId>
<artifactId>jgitflow-maven-plugin</artifactId>
<version>1.0-m5.1</version>
<configuration>
<flowInitContext>
<masterBranchName>master</masterBranchName>
<developBranchName>development</developBranchName>
<featureBranchPrefix>feature-</featureBranchPrefix>
<releaseBranchPrefix>release-</releaseBranchPrefix>
<hotfixBranchPrefix>hotfix-</hotfixBranchPrefix>
<versionTagPrefix>version-</versionTagPrefix>
</flowInitContext>
<username>USERNAME_FOR_A_GIT_USER</username>
<password>PASSWORD_FOR_A_GIT_USER</password>
<noDeploy>true</noDeploy>
<squash>true</squash>
<scmCommentPrefix>[RELEASE] </scmCommentPrefix>
</configuration>
</plugin>
<plugins>
</build>
Add the configuration above to your pom.xml file. For our current case, we won't let JGit-Flow plugin push commits. Therefore we do not need to put username and password to plugin configuration.
Imagine that the current commit graph is as follows.
* 0f7f7a3 - (HEAD -> master, origin/master, origin/development, development) second commit (10 minutes ago) <Lemi Orhan Ergin>
* 98ea7bd - initial commit (11 minutes ago) <Lemi Orhan Ergin>
The project has 2 permanent branches: master and development. Development branch is the integration branch being used as the next release branch. Master branch is the branch where you get releases and deploy to production.
And the version of the project is set as 1.0-SNAPSHOT in pom file as follows.
<modelVersion>4.0.0</modelVersion>
<groupId>com.product</groupId>
<artifactId>my-project</artifactId>
<version>1.0-SNAPSHOT</version>
SNAPSHOT implies that the current working copy of the sourcecode is in development stage.
You developed new features or fixed bugs. Let's assume that the commit graph became as follows.
* 02c21b6 - (origin/development, development) settings (2 minutes ago) <Lemi Orhan Ergin>
* 34a7d7c - readme (2 minutes ago) <Lemi Orhan Ergin>
* 8d86bbb - (origin/master, master) second commit (2 days ago) <Lemi Orhan Ergin>
* 98ea7bd - initial commit (4 days ago) <Lemi Orhan Ergin>
We made 2 commits to development branch and completed our work. Now it is time to make a release.
We can start the release by using JGit-Flow plugin on master branch. Please note that master branch should contain a SNAPSHOT version which is added with the merge.
$ mvn jgitflow:release-start
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=512m; support was removed in 8.0
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building My Project 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- jgitflow-maven-plugin:1.0-m5.1:release-start (default-cli) @ my-project ---
[INFO] (development) Checking for SNAPSHOT version in projects...
[INFO] (development) Checking dependencies and plugins for snapshots ...
What is the release version for "My Project"? (com.product:my-project) [1.0]:
[INFO] (release-1.0) adding snapshot to pom versions...
[INFO] (release-1.0) updating poms for all projects...
[INFO] turn on debug logging with -X to see exact changes
[INFO] (release-1.0) updating pom for My Project...
What is the development version for "My Project"? (com.product:my-project) [1.1-SNAPSHOT]:
[INFO] (development) updating poms with next development version...
[INFO] (development) updating poms for all projects...
[INFO] turn on debug logging with -X to see exact changes
[INFO] (development) updating pom for My Project...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.694 s
[INFO] Finished at: 2015-12-24T17:41:02+02:00
[INFO] Final Memory: 16M/981M
[INFO] ------------------------------------------------------------------------
The commit graph became a little bit spoiled. Isn't it?
* ba3fb12 - (development) [RELEASE]updating poms for 1.1-SNAPSHOT development (34 seconds ago) <Lemi Orhan Ergin>
| * 8d324a6 - (HEAD -> release-1.0) [RELEASE]updating poms for 1.0 branch with snapshot versions (36 seconds ago) <Lemi Orhan Ergin>
|/
* 02c21b6 - (origin/development) settings (2 minutes ago) <Lemi Orhan Ergin>
* 34a7d7c - readme (2 minutes ago) <Lemi Orhan Ergin>
* 8d86bbb - (origin/master, master) second commit (2 days ago) <Lemi Orhan Ergin>
* 98ea7bd - initial commit (4 days ago) <Lemi Orhan Ergin>
The first step of the release is completed. A release branch is created from development branch and now it is ready to make last-minute changes before completing the release.
We added one single commit as a last minute fix.
* e702400 - (HEAD -> release-1.0) last minute updates (12 seconds ago) <Lemi Orhan Ergin>
| * ba3fb12 - (development) [RELEASE]updating poms for 1.1-SNAPSHOT development (3 minutes ago) <Lemi Orhan Ergin>
* | 8d324a6 - [RELEASE]updating poms for 1.0 branch with snapshot versions (3 minutes ago) <Lemi Orhan Ergin>
|/
* 02c21b6 - (origin/development) settings (4 minutes ago) <Lemi Orhan Ergin>
* 34a7d7c - readme (4 minutes ago) <Lemi Orhan Ergin>
* 8d86bbb - (origin/master, master) second commit (2 days ago) <Lemi Orhan Ergin>
* 98ea7bd - initial commit (4 days ago) <Lemi Orhan Ergin>
The commit graph is a little bit wierd, isn't it? Don't worry, it will be simplified soon.
It is time to finalize the release via JGit-Flow plugin on release-1.0 branch.
$ mvn jgitflow:release-finish
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=512m; support was removed in 8.0
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Project 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- jgitflow-maven-plugin:1.0-m5.1:release-finish (default-cli) @ my-project ---
[INFO] running jgitflow release finish...
[INFO] (release-1.0) Updating poms for RELEASE
[INFO] (release-1.0) removing snapshot from pom versions...
[INFO] (release-1.0) updating poms for all projects...
[INFO] turn on debug logging with -X to see exact changes
[INFO] (release-1.0) updating pom for My Project...
[INFO] (release-1.0) Checking for RELEASE version in projects...
[INFO] (release-1.0) Checking dependencies and plugins for snapshots ...
[INFO] Executing: /bin/sh -c cd ~/project && mvn -s /var/folders/28/z27000gn/T/release-settings4626.xml clean deploy --no-plugin-updates -DperformRelease=true -Pdefault
-- BUILD LOGS --
[INFO] (development) copying pom versions...
[INFO] (development) updating poms for all projects...
[INFO] turn on debug logging with -X to see exact changes
[INFO] (development) updating pom for My Project...
[INFO] copying pom versions...
[INFO] (development) updating poms for all projects...
[INFO] turn on debug logging with -X to see exact changes
[INFO] (development) updating pom for My Project...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.680 s
[INFO] Finished at: 2015-12-24T17:48:41+02:00
[INFO] Final Memory: 20M/981M
[INFO] ------------------------------------------------------------------------
After finalizing the release, you will have the following commit graph. Please note that the graph is simplified by JGit-Flow plugin due to <squash>true</squash>
settings.
* dabcfce - (HEAD -> development) [RELEASE]Updating develop poms back to pre merge state (14 minutes ago) <Lemi Orhan Ergin>
| * 9e60195 - (tag: version-1.0, master) [RELEASE]squashing 'release-1.0' into 'master' (14 minutes ago) <Lemi Orhan Ergin>
* | 4cdfe7d - [RELEASE]squashing 'master' into 'development' (14 minutes ago) <Lemi Orhan Ergin>
* | 0e840df - [RELEASE]updating develop poms to master versions to avoid merge conflicts (14 minutes ago) <Lemi Orhan Ergin>
* | 4476c20 - [RELEASE]updating poms for 1.1-SNAPSHOT development (22 minutes ago) <Lemi Orhan Ergin>
| * 17b1725 - Merge branch 'development' (31 minutes ago) <Lemi Orhan Ergin>
| |\
| |/
|/|
* | 0777c49 - (origin/development) setting file added (34 minutes ago) <Lemi Orhan Ergin>
* | 1d454de - readme file added (35 minutes ago) <Lemi Orhan Ergin>
|/
* 0f7f7a3 - (origin/master) second commit (2 hours ago) <Lemi Orhan Ergin>
* 98ea7bd - initial commit (2 hours ago) <Lemi Orhan Ergin>
The commit graph shows that there are 2 branches where development branch was merged with master at some point. You can see that version-1.0 branch is added at the start stage and deleted at the finalization stage. We now have a version tag, namely verison-1.0. We have still some steps to implement.
As you can see the plugin creates 4 release commits. It would be nice if we can squash them into one perfect commit. So let's use interactive rebase.
(development) $ git rebase -i HEAD~4
Then the editor is opened to let us configure the last 4 commits.
pick 4476c20 [RELEASE]updating poms for 1.1-SNAPSHOT development
pick 0e840df [RELEASE]updating develop poms to master versions to avoid merge conflicts
pick 4cdfe7d [RELEASE]squashing 'master' into 'development'
pick dabcfce [RELEASE]Updating develop poms back to pre merge state
Keep the first line and replace "pick" on the last 3 lines as "squash" or "s".
pick 4476c20 [RELEASE]updating poms for 1.1-SNAPSHOT development
s 0e840df [RELEASE]updating develop poms to master versions to avoid merge conflicts
s 4cdfe7d [RELEASE]squashing 'master' into 'development'
s dabcfce [RELEASE]Updating develop poms back to pre merge state
Then save and exit. A new editor will be opened to write a new commit message.
[RELEASE] Ready for the next release
Then save and exit. Rebase will be completed successfully - fingers crossed;)
(development) $ git rebase -i HEAD~4
[detached HEAD 1b502d3] [RELEASE] Ready for the next release
Date: Thu Dec 24 17:41:02 2015 +0200
2 files changed, 3 insertions(+), 3 deletions(-)
Successfully rebased and updated refs/heads/development.
Now the commit graph is much cleaner.
* 1b502d3 - (HEAD -> development) [RELEASE] Ready for the next release (3 minutes ago) <Lemi Orhan Ergin>
| * 9e60195 - (tag: version-1.0, master) [RELEASE]squashing 'release-1.0' into 'master' (36 minutes ago) <Lemi Orhan Ergin>
| * 17b1725 - Merge branch 'development' (52 minutes ago) <Lemi Orhan Ergin>
| |\
| |/
|/|
* | 0777c49 - (origin/development) setting file added (56 minutes ago) <Lemi Orhan Ergin>
* | 1d454de - readme file added (56 minutes ago) <Lemi Orhan Ergin>
|/
* 0f7f7a3 - (origin/master) second commit (3 hours ago) <Lemi Orhan Ergin>
* 98ea7bd - initial commit (3 hours ago) <Lemi Orhan Ergin>
It is time to push your commits to master and development branches and tags to remote.
Push development branch.
$ git push origin development
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 396 bytes | 0 bytes/s, done.
Total 4 (delta 1), reused 0 (delta 0)
To remote-url
0777c49..1b502d3 development -> development
Push master branch.
$ git push origin master
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 596 bytes | 0 bytes/s, done.
Total 5 (delta 1), reused 0 (delta 0)
To remote-url
0f7f7a3..9e60195 master -> master
And push all tags.
$ git push --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 185 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To remote-url
* [new tag] version-1.0 -> version-1.0
Now the commit graph is finalized as follows.
* 1b502d3 - (HEAD -> development, origin/development) [RELEASE] Ready for the next release (6 minutes ago) <Lemi Orhan Ergin>
| * 9e60195 - (tag: version-1.0, origin/master, master) [RELEASE]squashing 'release-1.0' into 'master' (39 minutes ago) <Lemi Orhan Ergin>
| * 17b1725 - Merge branch 'development' (55 minutes ago) <Lemi Orhan Ergin>
| |\
| |/
|/|
* | 0777c49 - setting file added (59 minutes ago) <Lemi Orhan Ergin>
* | 1d454de - readme file added (59 minutes ago) <Lemi Orhan Ergin>
|/
* 0f7f7a3 - second commit (3 hours ago) <Lemi Orhan Ergin>
* 98ea7bd - initial commit (3 hours ago) <Lemi Orhan Ergin>
I hope that helps. Happy releases!
For the ones who are curious about how we generated commit graphs in ascii, you can use the following.
git log --branches --remotes --tags --graph --pretty=format:'%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative