Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
How to make a release with Git and Maven via JGitFlow

How to make a release with Git and Maven via JGitFlow

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.

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.

Add JGitFlow maven plugin to your pom file

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.

The status before starting your development

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.

Make your changes

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.

Start the 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.

Add last minute changes to the release branch

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.

Finalize the release

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.

Clean up irrelevant commits via interactive rebase

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>

Now push all commits and tags

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!

Notes

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
@michaelilyin

This comment has been minimized.

Copy link

michaelilyin commented Jan 24, 2017

Hello. How you resolve conflicts which may behind when release branch megeing into development branch on release-finish goal? Plugin failes in this case. Is there way to resolve conflicts and continue release-finish process?

@fogartyp

This comment has been minimized.

Copy link

fogartyp commented Jan 28, 2017

Also trying to resolve the same issue as @michaelilyin.

ERROR

[ERROR] Failed to execute goal external.atlassian.jgitflow:jgitflow-maven-plugin:1.0-m5.1:release-finish (default-cli) on project

Error finishing release: Error finishing release: org.eclipse.jgit.api.errors.CheckoutConflictException: Checkout conflict with files:
.......
at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:212) at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153) at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145) at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:116) at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:80) at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51) at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:128) at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:307) at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:193) at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:106) at org.apache.maven.cli.MavenCli.execute(MavenCli.java:863) at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:288) at org.apache.maven.cli.MavenCli.main(MavenCli.java:199) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289) at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229) at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415) at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356) Caused by: org.apache.maven.plugin.MojoExecutionException: Error finishing release: Error finishing release: org.eclipse.jgit.api.errors.CheckoutConflictException: Checkout conflict with files:
.......

at com.atlassian.maven.plugins.jgitflow.mojo.ReleaseFinishMojo.execute(ReleaseFinishMojo.java:178) at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:134) at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:207) ... 20 more Caused by: com.atlassian.maven.plugins.jgitflow.exception.MavenJGitFlowException: Error finishing release: org.eclipse.jgit.api.errors.CheckoutConflictException: Checkout conflict with files:
.......
at com.atlassian.maven.plugins.jgitflow.manager.DefaultFlowReleaseManager.finish(DefaultFlowReleaseManager.java:123) at com.atlassian.maven.plugins.jgitflow.mojo.ReleaseFinishMojo.execute(ReleaseFinishMojo.java:174) ... 22 more Caused by: com.atlassian.jgitflow.core.exception.JGitFlowGitAPIException: org.eclipse.jgit.api.errors.CheckoutConflictException: Checkout conflict with files:
........
at com.atlassian.jgitflow.core.command.ReleaseFinishCommand.call(ReleaseFinishCommand.java:171) at com.atlassian.maven.plugins.jgitflow.manager.DefaultFlowReleaseManager.finish(DefaultFlowReleaseManager.java:99) ... 23 more Caused by: org.eclipse.jgit.api.errors.CheckoutConflictException: Checkout conflict with files:
.......
at org.eclipse.jgit.api.CheckoutCommand.call(CheckoutCommand.java:263) at com.atlassian.jgitflow.core.command.AbstractBranchMergingCommand.doMerge(AbstractBranchMergingCommand.java:53) at com.atlassian.jgitflow.core.command.AbstractBranchMergingCommand.doMerge(AbstractBranchMergingCommand.java:44) at com.atlassian.jgitflow.core.command.ReleaseFinishCommand.call(ReleaseFinishCommand.java:142) ... 24 more Caused by: org.eclipse.jgit.errors.CheckoutConflictException: Checkout conflict with files:
........
at org.eclipse.jgit.dircache.DirCacheCheckout.doCheckout(DirCacheCheckout.java:415) at org.eclipse.jgit.dircache.DirCacheCheckout.checkout(DirCacheCheckout.java:396) at org.eclipse.jgit.api.CheckoutCommand.call(CheckoutCommand.java:259) ... 27 more

I have found that it is only on the master branch and after one successful run of the plug?

Found an open bug here:
https://ecosystem.atlassian.net/browse/MJF-266

Centos 7, JRE 7 (1.7.0_79), Maven 3.39

@McFoggy

This comment has been minimized.

Copy link

McFoggy commented Feb 15, 2017

Personally I would love to be able not to pollute the git history of my projects and use git meta information ONLY.
I created jgitver-maven-plugin & jgitver to try to achieve this.
I worked with several users to make it work with jgitflow, see jgitver/jgitver#16 and the unit tests here that use the following scenario.

Any feedback on the concept (email, issue report, tweet) would be much appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.