As a Java developer, I’m always trying to find ways to streamline all the mundane tasks that come along with development but aren’t necessarily part of the actual code I’m trying to write. Managing source control and performing releases are definitely on that list.

I use Git as my DVCS of choice, and while it certainly makes managing source changes a bit more, well, manageable, it can also feel like the “wild wild west” at times with random branches and commits flying around.

Enter Git Flow. Git Flow makes managing features, releases, and bug fixes really simple and provides structure to the dev cycle. But there’s one problem: There’s no Java implementation, but let’s not get ahead of ourselves…

 

What Is Git Flow?

Git Flow is a branching and merging model introduced by Vincent Driessen that provides a little bit of structure to your development workflow.

At its core you have two “long running” branches:

  • develop – this is where all changes are made (or eventually end up) while in active development.
  • master – no development work is done on master, but instead all changes are merged in from the develop branch.

Along with the long running branches, you may have some other short-lived branches as well:

  • feature/<some feature> – used to develop new features to be released at some point in the future.
  • release/<some version> – used to prepare for a new planned production release.
  • hotfix/<some version> – used to prepare for a new unplanned production release specifically to fix up high priority issues.

For more in-depth information about git flow and how git flow can help your business, check out our git flow guide.

JGit Flow Java Library

The JGit Flow Java Library is a general purpose library that implements git flow on top of JGit and is licensed under the Apache 2 License.

Now some people may not like the command pattern that JGit uses for most of its functionality, but I don’t mind it and to keep things familiar and cohesive, JGit Flow uses the same pattern. That being said, we’ve added some abstractions to make it easy to interact with both JGit and JGit Flow instances.

So let’s get into some code already! Say you have a project that you want to git flow enable. It doesn’t even need to be a git project, just a regular project directory will do (or even an empty directory for that matter).

1
2
JGitFlow flow = JGitFlow.getOrInit(new File("/home/my-projects/example-gitflow-project"));
//your local working copy is now on the develop branch

That’s it. One line and you’ve set up git flow for your directory.

The getORInit method essentially checks to see if the folder is already a git project, and if not, runs git init. After that, it checks to see if the directory is already a git flow project and if not, runs git flow init with the default branch names/prefixes and finally returns a git flow instance.

To maintain compatibility with the command line git-flow, JGit Flow uses the same git config keys/values that the CLI uses. This means that you can switch between using JGit Flow and the git-flow CLI with no side effects.

If you need a more customizable approach, here are some other examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//create a flow instance with a few custom prefixes
InitContext ctx = new InitContext();
ctx.setMaster("GA").setHotfix("bugfix/").setVersiontag("rtags/");

JGitFlow flow = JGitFlow.getOrInit(new File("/home/my-projects/example-gitflow-project"), ctx);

//initialize git flow or throw an error if it's already been initialized
JGitFlow flow = JGitFlow.init(new File("/home/my-projects/example-gitflow-project"));

//initialize git flow with custom values that override the current values
// in the case it's already been initialized
InitContext ctx = new InitContext();
ctx.setMaster("GA").setHotfix("bugfix/").setVersiontag("rtags/");

JGitFlow flow = JGitFlow.forceInit(new File("/home/my-projects/example-gitflow-project"), ctx);

Once you have a JGitFlow instance, you can use the familiar JGit style command pattern on the instance to carry out git flow operations.

For instance, to start a release, you can do:

1
2
3
4
5
6
flow.releaseStart("1.0").call();

// Or if you need a reference to the newly created branch, you can get that too
Ref newBranchRef = flow.releaseStart("1.0").call();

//Note: your local working copy is now on release/1.0

All of this is well and good, but you’re probably going to need to mix JGitFlow commands with other JGit commands on the same repository. JGitFlow makes this easy by providing a reference to the Git instance created by JGit.

For instance, if you want to start a release, update a file with the release version and commit it and then finish the release, you could do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//get a JGitFlow instance
JGitFlow flow = JGitFlow.getOrInit(new File("/home/my-projects/example-gitflow-project"));

//start our release
flow.releaseStart("1.0").call();
//Note: your local working copy is now on release/1.0
//update the version.txt file in the working tree
File versionFile = new File(flow.git().getRepository().getWorkTree(), "version.txt");
FileHelper.writeStringToFile("1.0", versionFile);

//commit the change
flow.git().add().setUpdate(true).addFilepattern("version.txt").call();
flow.git().commit().setMessage("updating version file").call();

//finish the release and push to origin
flow.releaseFinish("1.0").setPush(true).call();

//Note: your local working copy is now on develop

Why A Java Library?

After using git flow for a while and becoming a convert, I found one big wrench in the process. That wrench is called the Maven release plugin.

With its checkouts, tagging, pom rewrites and extraneous release.properties and *.backup files mostly taking place within your working tree, the Maven release plugin doesn’t play well with git flow.

Now that doesn’t mean integrating Maven releases with git flow can’t be done, but it requires some special configuration, and you never quite get it to do exactly what you want.

The best you can do is to configure your pom.xml like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<!-- v 2.4 has a bug with localCheckout. will be fixed in 2.4.1 -->
<version>2.3.2</version>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
<goals>deploy</goals>
<pushChanges>false</pushChanges>
<localCheckout>true</localCheckout>
<tagNameFormat>mvn-@{project.version}</tagNameFormat>
</configuration>
</plugin>
</plugins>
</build>

Notice lines 11 through 13:

11. tell Maven not to push rewritten POMs to the remote repo, git flow will do that

12. tell Maven not to check out the project from the remote repo as we want to use our local changes

13. give Maven some junk tag name so it doesn’t conflict with the git flow tagging

With this in place, doing a release is as “simple” as:

1
2
3
4
5
git flow release start 1.0
mvn release:prepare release:perform -DreleaseVersion=1.0
git tag -d mvn-1.0 #if you forget this, you'll get 2 tags in the remote repo

git flow release finish -p 1.0

So after all is said and done, you’ll have created a new release branch, Maven will have:

  • messed with your POMs
  • maybe committed a release.properties
  • run your build and your tests 2 times
  • created Java docs
  • created a Maven site (that you didn’t ask for)
  • and pushed your artifacts the the Maven repo

then git flow merged your release into develop and master, created a tag, and then pushed master, develop and the tag to the remote repo, which now has 1.1-SNAPSHOT as the version in both master and develop.

While this works, there are some obvious flaws here. So I decided that using git flow in a Maven release process probably warrants a specific Maven gitflow-release plugin.

“Shouldn’t be too hard”, I thought. “I’ll just grab whatever java git flow library is out there and wrap a Maven plugin around it”.

To my surprise there was only one git flow java library that was started but never completed.

Clearly there are lots of developers using Maven that could probably take advantage of a git flow plugin, and surely lots of Java projects to benefit from adding git flow support, right?

And so was born the JGit Flow Java Library.

curious about the maven plugin?

When things go wrong

The JGit Flow examples above seem pretty simple from an end user API perspective. However, JGit Flow has to do some complex things under the covers to ensure the proper state exists before carrying out the requested operation—things like making sure branches exist/don’t exist, ensuring that your local tree is not behind the remote repo, ensuring that remote branches exist, etc.

To enable you, as the integrator, to do something meaningful when good things go bad, JGit Flow provides a fine-grained set of exceptions that all inherit from a base exception. This allows you to catch very specific exceptions or just catch a single exception depending on what your code needs to do.

Taking the start release example from above, it could be re-written to catch specific exceptions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//Note: we catch what we want and throw any other JGitFlowException
public boolean doReleaseStart() throws JGitFlowException
{
//Initialize our project if needed
JGitFlow flow = JGitFlow.getOrInit(new File("/home/my-projects/example-gitflow-project"));

//try to start our release
try
{
flow.releaseStart("1.0").call();

wasReleased = true;
}
catch (BranchExistsException e)
{
//the release branch already exists, just check it out
try
{
flow.git().checkout().setName(flow.getReleaseBranchPrefix() + "1.0").call();
}
catch (GitAPIException e1)
{
log.error("Something went wrong checking out the release branch");

return wasReleased;
}
}
catch (DirtyWorkingTreeException e)
{
log.error("you have un-committed changes!");

return wasReleased;
}
catch (BranchOutOfDateException e)
{
log.error("your local develop branch is behind the remote, please do a git pull");

return wasReleased;
}

//all is good!
return wasReleased;
}

Let the flow begin!

Armed with this new library, hopes are that more and more Java projects will use it to integrate git flow into their apps. But don’t stop there! I believe this is just the tip of the iceberg and as JGit Flow gets adopted by more and more projects, I hope to see it evolve beyond the base git flow workflow.

Anything is possible and everything is up for grabs. If you find a bug or have a feature request, by all means add it to the JGitFlow issue tracker.

If you’d like to contribute, please fork the JGit Flow project on Bitbucket. When your super cool new feature is ready, just issue a pull request to the develop branch of the main project.

For more information and usage instructions, see the JGit Flow Wiki on Bitbucket.

Enjoy!

See my follow up blog post about the maven-jgitflow-plugin.