Dealing with Maven dependencies when switching to Git

Note: The official version of this post can be found at https://www.atlassian.com/git/articles/maven-dependencies-versions-merging.

So we're moving to Git and we like git-flow. Now what? Let's test it all out!

My team is great. They threw together a hit list of developer workflows in Confluence, all based on what we had been doing as a team and all of the weird things they thought we might have to do in the future. Then, in a project structure mirroring our own (but with no code in it—just a pom.xml), tried every workflow.

Maven dependencies were about to prove themselves to be our biggest problem in all of this.

Maven Build Numbering

Maven produces 1.0.0-SNAPSHOT builds until you release. When you release, -SNAPSHOT is removed and your version is 1.0.0. Your build process needs to be able to support incrementing your minor version up after the fact so that subsequent work on your next effort will produce builds like 1.1.0-SNAPSHOT. You're not tied to three digits -- you can define this when you start a project, but we use three. Anyway, the -SNAPSHOT part is really important to understand. This is always going to represent the latest pre-release cut of a project.

Artifacts

See, our big concern in all of these workflows was how we were going to ensure that our project versions and inter-project dependencies were properly managed.

Each time Maven dependencies are retrieved for a build, it will, by default, pull those down from Ye Olde Internet(e)™. Those artifacts are stored locally so that subsequent builds can be performed more quickly. One solution to make this a bit less painful is to use an artifact repository on your local network for acting as a local cache for those external dependencies. LAN retrieval is almost always going to be faster than downloading even from the quickest of CDNs. We use Artifactory Pro as our artifact repository. In addition, since we have a multi-module structure, we store our own build artifacts in Artifactory as well. When we build one of our common packages, we can pull that specific version in via maven dependency resolution and retrieve the artifact right out of the artifact repository.

This works swimmingly. Artifactory also lets you synchronize your artifacts between instances so if you wanted to, say, use it to replicate your release repository to your data centers for production deployments, you could do this without needing to build a separate process.

Maven dependencies, feature branches and pull requests

All of our builds go into Artifactory. With SVN, we had been using a snapshot repository for keeping the latest 2 snapshot builds, a staging repostory for any release builds not yet approved, and a release repository only for the builds blessed to go into production.1 These builds are numbered like I described earlier, and are retrievable by a predictable URL pattern based on repository and version.

The primary workflow for each developer was to create a feature branch from the develop branch for their work, complete it, and place a pull request to have that work merged back into the develop branch. For a single project, this works mostly without issue, but let me paint a picture for you of the first problem we ran into head-first, and one that had us seriously reconsidering the entire migration:

As I said before we have multiple layers of dependency between our projects. There's a very good reason for this—both historically and strategically—for our products. We've considered alternate architectures that would eliminate this problem, but they'd introduce others. We can make our lives easier (and we did, but that's for a later post), but for now it's strategic for us to keep our structure as it is.

So developer A, let's call her Angela, starts work on a feature in JIRA. This requires two branches: one from our common project and one from product X. The version for common is 2.1.0-SNAPSHOT. The version for productX is 2.4.0-SNAPSHOT. She works locally for a while and then finally pushes back up to Bitbucket Server. Bamboo picks up these changes, builds the common package and uploads common-2.1.0-SNAPSHOT to Artifactory, then builds productX with a dependency on common-2.1.0-SNAPSHOT, uploading productX-2.4.0-SNAPSHOT as well. Unit tests pass!

Developer B, let's call him Bruce, starts work on another feature in JIRA, for a different product: productY. This also requires two branches: one from our common project and one from productY. The version for common is, as above, 2.1.0-SNAPSHOT. The version of product Y is 2.7.0-SNAPSHOT. He works locally for a while and then finally pushes his changes up to Bitbucket Server. Bamboo picks up these changes, builds the common package and uploads common-2.1.0-SNAPSHOT to Artifactory, then builds productX with a dependency on common-2.1.0-SNAPSHOT, uploading productX-2.4.0-SNAPSHOT as well. Unit tests pass!

Angela, meanwhile, finds a small bug in her productX code and writes a unit test to validate her fix. She runs it locally and it passes. She pushes her changes to Bitbucket Server, and Bamboo picks up the change and builds productX. The build succeeds, but some of her unit tests fail. It's not the new ones she wrote, but the first ones from her initial changes to the feature. Somehow the Bamboo build has found a regression that her local build didn't? How is that possible?

Because her common dependency, the one Bamboo pulled in when it built productX, was no longer her copy. Bruce over-wrote common-2.1.0-SNAPSHOT in artifactory when his feature build completed. There was no source code conflict—both developers were working in isolation on their own branches, but the source of truth for Maven's artifact retrieval was corrupted.

Head. Meet desk.

For about a month after we discovered this problem we tried everything to get around this. Through our TAM2, we talked to people on the Bamboo team who use git-flow, and we talked to the developer who maintains jgit-flow, a java implementation of git-flow. They were all super helpful, but short of a process that required a list of manual steps for each developer every time they worked on a feature, we couldn't find a resolution that was tolerable.

If you're curious what we considered, here's everything we tried:

  1. Modify the version number on branch creation, or immediately thereafter.
    • We can do this with mvn jgitflow:feature-start to create the branch.
    • We can use a Bitbucket Server hook or a local githook.
    • We can manually set with mvn version:set-version after we create the branch.
    • We can automate the change with the [mavent-external-version] plugin.
  2. Modify the version number when finishing the branch and merging back to develop.
    • We can do this with mvn jgitflow:feature-finish to finish the branch.
    • Use a git merge driver to handle pom conflicts.
    • Use an asynchronous post-receive hook in Bitbucket Server
  3. Do it all manually. (Just kidding. We didn’t consider this option very long.)

Each one of these options had some sort of negative side-effect. Chiefly, manual steps for a developer each and every time they needed a feature branch. And we wanted them to create feature branches all the time. Also in most cases we could not effectively use pull requests, which was a deal-breaker.

This consumed 1-2 people for almost two months until we had a (mindblown) revelation as to why we were approaching this problem from the wrong direction.

One version to rule them all

Hindsight being 20/20, I can clearly see that our biggest mistake was that we were focusing our attention on the git-flow tools rather than using the tools we already had to implement the workflow we wanted. We had:

  • JIRA Software
  • Bamboo Server
  • Maven
  • Artifactory Pro

Turns out, those were all of the tools we needed.

One of our engineers got the very bright idea that since the problem wasn't the build management itself but rather the artifacts being over-written, that we should fix Artifactory instead. His idea was to use a Maven property to set the snapshot repository URL to a custom URL which included the JIRA issue ID, and then write out its artifacts to a dynamically-created repository in Artifactory with a custom template. Maven’s dependency resolver will find artifacts in the develop snapshot repository if we haven’t needed to branch them, for instance if we’re only working on a product and not also common.

We set that handy little property variable in our build settings file, and wrote a Maven plugin to populate it during the earliest part of maven’s build lifecycle. On paper, this sounded incredible and re-invigorated the team to work harder to solve this problem. Trouble was that we couldn't actually do this. The earliest stage of the maven lifecycle is 'validate'. By the time plugins bound to validate have been run, the repository URLs were already resolved. Because of this, our variable never populated and the URL is not branch-named after all. Even though we had been using a layout in a separate repository from our develop snapshots, it wouldn’t be isolated for parallel development.

Head, meet desk again, your BEST FRIEND.

After a beer, the aforementioned engineer did some more digging and research into another way to add functionality to maven: extensions.

“Here’s to beer: the cause of, and solution to, all of life’s problems.” —Homer Simpson

Extensions, like plugins, give you a whole host of power to enhance your Maven workflow, however they are executed before lifecycle goals, and have greater access to the Maven internals. By utilizing the RepositoryUtils package, we forced Maven to re-evaluate its URLs using a custom parser and then re-set them using our updated values.3

Extension in place and tested, we started knocking off pre-migration tasks one after another, going from "this is never going to happen" to "this IS going to happen Monday... so now I need to write ten pages of documentation by tomorrow". I'll write more soon about how the tools work together to achieve our new development workflow, and some of the lessons we learned about the process.


  1. One downside here was that I had to use a script I wrote to hit the Artifactory REST API to “promote” builds from staging to release. It’s fast enough, but begging for more automation.

  2. Technical Account Manager. More information here.

  3. After the initial development efforts, we found that we had to do even more to make this work 100% of the time, like when a snapshot is newer in Artifactory (from another engineer) than your local snapshot, Maven grabs the remote artifact because hey, it’s NEWER, so it must be BETTER, right?

Oct 31st, 2015

Git or SVN? How Nuance Healthcare chose a Git branching model

This is the first post in a series about my engineering team moving from Subversion to Git, why we did it, and what we encountered along the way. I am speaking on this topic at Atlassian Summit 2015, and since I'm limited to 30 minutes, I wanted to provide more context and some pieces I'll be leaving out of the presentation for anyone interested.

Note: The official version of this post can be found at https://www.atlassian.com/git/articles/git-or-svn-git-branching-model.

Background

My team is in the healthcare division at Nuance. We're geographically-distributed between a couple of offices and homes on the East Cast of the US, and in an office in Pune. We develop Java web services to deliver NLP1 solutions to the healthcare market.

For the most part, our service consumers are other healthcare software companies (including ourselves) such as EHR vendors and healthcare analytics companies. We do directly sell some products to hospitals, and the end-users of the applications range from physicians to medical billing staff. "Normal" people like you and me don't ever touch the software my team builds.

Our team has been around the block a few times with Application Lifecycle Management product combinations. We started life with a mix of Rally Enterprise and Seapine TestTrack Pro, did about 14 months of hard labor with Rational Team Concert, and eventually migrated fully to the Atlassian stack (JIRA Software, Confluence, Bamboo, Crucible, Bitbucket and HipChat). Historically we used Subversion 1.4/1.5 an our SCM with a quasi-normal trunk/branches/tags structure. We have been using maven since forever manage our build projects and dependencies, and switched from Jenkins to Bamboo for continuous integration (CI) a while ago in order to make use of tighter integrations with JIRA and its flexible build and deploy agent capabilities. Everything we use (now) is behind the firewall for reasons2.

Git or SVN?

We support roughly ten individual products across four product families, and the owners of these products are always battling for prioritization and timing. It’s nice to have our work be in high demand, and this is by no means a complaint, but it also necessitates cutting releases at a weird cadence and needing to change directions in the middle of a sprint3.

Our development process really felt prohibitive at times. There was a conversation that my team was having on a regular basis that went something like this:

Me: We need to release 1.8.0 to QA now for regression testing so that Customer foo can go to beta next week.

Dev: I'm still working on ABC-123 which is in trunk. It's not done yet.

Me: Foo doesn't need ABC-123. We could put it in the next release.

Dev: But I've been working on it for weeks. There's no clear spot to branch from to cut a release.

Me: Well, you'll need to pull out all of your changes by hand. You have about two hours or QA can't finish in time.

I know, I sound like a jerk! I never meant to be, and of course I'm exaggerating a bit to make a point, but we really did have to figure out how to get code that was in one place out of that place temporarily so that we could cut a release, and then put it right back for the next release4. And this happened all the time.

Now, I know some of you are thinking "Subversion supports branches, Matt...". It absolutely does, and we used them on occasion with SVN 1.4 and 1.5. Branching is a fine operation in SVN; merging can be a pain in the ass. As SVN has matured, it has gotten better, for sure. But we knew there were better options out there for us, so when the question of SCN or git arose, we set out to get Git.

A side note: We briefly looked at the latest SVN (1.8 at the time) to see if it was strong enough to solve our problems, but weren't completely satisfied. One of our peer groups has a large Perforce setup and it had a lot of what we needed, but I simply couldn't stomach the licensing costs. We also looked at Mercurial for a moment, but in the end, the existing team's exposure to Git was enough to tell us that it was the right direction.

I won't sugar-coat this: Atlassian's tools really favor teams who use git. Other SCMs work fine; our SVN integration was sufficient in that it linked us to where a given user story's changes were made. The integration capabilities for teams who use Bitbucket Server5 instead, however, are both stronger and more natural-feeling in the JIRA Software interface and development experience — ditto with Bamboo.

Knowing this, and having seen some very stellar demos at Summit 2013, I strongly encouraged the team to go for it. Nobody objected, and we already had the licenses in place to make change.

Choosing a Git Branching Model

After deciding to make this change, the first challenge we had was deciding what Git branching model to implement for our team. Atlassian's Git microsite as well as this great presentation from Summit 2013 explain in greater detail what a branching model is. The short version is that it describes how you will use branches in git to power your development workflow.

In SVN, we had a model for branching I'll call "make one when you realize you — OMG! — need one":

  • The newest code is in trunk. Releases from trunk will be numbered A.B.0-{build}.
  • If a fix is required to a trunk-based release (e.g. we have a bug in 1.2.0-64), a branch is created and from there we will release A.B.C-{build} releases, where C increments after every release that goes out the door. These branches may never exist for a given A.B and we could even have more than one.
  • We also tag every release in a tags directory.

An Aside About Versions Many years ago, when I was just cutting my teath on managing a development team, our release engineer had a system of versining that was... how shall I say?... really unintuitive. Essentially, every release was a patch on the previous one (A.B.n), with no respect for the place from which the patch originated. Figuring out where something came from and, in almost all cases, the release order, required you to look at svn log. We printed the tree on a wall for reference.

In addition, our public-facing release numbers tend to be things like 3.0, 3.1, 3.5, 4.0, or essentially something a customer might expect. Remember, though that my team builds web services not a boxed product. Our APIs are a contract. A few years ago I made the executive that my team's builds, and therefore its releases, would adhere to Semantic Versioning rules. I've' had to stand my ground a few times with upper management, but now it is understood why the rules are what they are, and we haven't looked back. Partners appreciate that sort of clarity.

I mentioned a problem earlier wherein we'd be working on a release (let's say 1.2.0) and we'd have a feature still in progress as we approached a release date. We would need to pull that code out, cut our release, branch to branches/1.2.1 and then merge that code back in, hoping nobody had a hard drive crash in the meantime6.

Removing a whole feature by itself from a shared trunk is a pain. Everyone hated life when they had to do that. svn blame can be useful, as can a strong diff tool, but it’s still annoying to work with. I often took it personally, feeling that my bad planning had led to us not having all of our ducks in a row before it was time to be done with a release7. My team dealt with this for long enough.

Sometimes we'd over-correct to avoid the pain and would ask developers to sit on their hands for a couple of days (a virtual code freeze, if you will), just so we didn't pollute trunk before a release.

So we knew we needed, at least, feature branches. There's a simple Git branching model that is applicable: a master branch for what's in prod, and using feature branches for every feature, bug, etc. Team's have to manage merge order to ensure that what ships out in master is what is supposed to ship out for the release. This is, essentially, the same thing we had before, with some better feature isolation, but we wanted freedom with our power.

In our environment, we often need to keep a few versions in production, and may need to fix defects in a release that is 2-3 minor revisions older than what we are working on right now. So, in addition to feature branches, we also needed some sort of release branch or similar that would let us fix issues from previous releases. The Atlassian Bitbucket Server team does this. They make fixes in long-running support branches, and then merge them up the branch stream so that a fix makes it in to all of the support streams.

Their model looked really good, and we ran a few prototype interactions with this model to see if it would suit our needs. The "killer app" for them is their rolling merge of a fix up to their develop branch. While we liked this concept, every time we tried it, we ran into one issue or another with our maven dependencies. Also, as a rule, we couldn't guarantee we wanted a straight merge of the work from one version into another. In some cases we needed to implement the same fix in slightly different ways between versions, so a direct merge wasn't possible.

A few of the members of the team strongly favored a variation of this model known as "git-flow". Git-flow is a set of branch naming conventions and merge guidelines, authored by Vincent Driessen. This felt very natural to the team, and we liked the structure since it eliminated many of the questions around "what do I do when we need to do x?". The answers were generally very obvious. Rather than explaining what git-flow is, you can read more about it in Atlassian's tutorial.

The only gap left for us with git-flow was what to do about those long-running releases in production. Since master keeps moving forward, we couldn't use the git-flow hotfix workflow for a bug fix from a previous release. On the other hand, we didn't always want a support branch.

Most of the time a hotfix, only patching the latest release in production, should be sufficient; support is only there when we need to go back further, or when we need to maintain compatibility for one reason or another. That latter use case we dissected further and came up with criteria for choosing to use a support branch rather than a hotfix and minor version upgrade:

  1. This code cannot be trivially merged back into develop.
  2. The partner/customer cannot handle an interface change that comes with the latest release.
  3. There is an internal dependency which cannot be changed.8

Both git-flow extension packages9 provide support for the support branch concept, which isn't part of the original draft of git-flow, but has become popular enough to warrant inclusion.

Git-flow offered a workflow we liked, with the tooling support we needed. In the next post I'll go into what happened when we actually tried using it in a POC project we used to represent our development process. It was... a learning experience!


  1. Natural Language Processing. WE CAN READ YOUR THOUGHTS. (No. Not really.)

  2. There is a lot that is attractive about Atlassian’s cloud offerings, but we need to keep our fingers wrapped tightly around our servers and data for the time being. While we don’t personally need to do much with PHI data, our software does and it’s important to keep it as secure as possible.

  3. Shhhh… don’t tell Ken Schwaber.

  4. Which might have only been a few days later anyway.

  5. Formerly known as Stash. Hello, Atlassian Fall Rebranding!

  6. I know we could always pull it out of the previous commit. I was kidding.

  7. This wasn’t usually the case—generally it was because someone else’s timeframe moved up and we had to react quickly.

  8. This is one of those things I can’t get into on my own blog. Just trust me. “Reasons”.

  9. The original package by Vincent Driessen isn’t being maintained any longer. A new fork , however, is regularly updated.

Oct 30th, 2015

Canonical Post Urls in Jekyll

In the next couple of days I'll be contributing a series of blog posts to the Atlassian Developers blog to serve as a backdrop for my presentation at Atlassian Summit 2015, about a week and a half from now1. I wanted to syndicate that content here, but for SEO reasons I was asked to mark the Atlassian URL as the canonical one for each post. That isn't possible out of the box, so here's how I modified my Jekyll template to accommodate this change:

First, I add a canonical-url tag to my YAML front-matter:

1
canonical-url: http://www.yahoo.com

And second, I modified my header template to set the canonical variable and populate the proper <link ...> tag:

1
2
3
4
5
6
{% if page.canonical-url == nil %}
{% capture canonical %}{{ site.url }}{% if site.permalink contains '.html' %}{{ page.url }}{% else %}{{ page.url | remove:'index.html' | strip_slash }}{% endif %}{% endcapture %}
{% else %}
{% assign canonical = page.canonical-url %}
{% endif %}
<link rel="canonical" href="{{ canonical }}">

The documentation out there for liquid is somewhat less helpful than I'd prefer, so the proper way to test if a variable was truly unset took a few stabs, and getting the proper context for the page variable, despite pages really being posts, was also unintuitive. One of these days, I might have to give up on Jekyll based purely on the "clear as mud" factor.

NB: Getting the above Liquid code to actually display was a royal pain in the butt. This site was helpful, and I appreciated his closing sentence:

Displaying liquid code in jekyll is really an annoying task. So try to avoid it as much as you can.


  1. Holy crap.

Oct 27th, 2015

Selfish Ambition

He who attempts to act and do things for others or for the world without deepening his own self-understanding, freedom, integrity and capacity to love, will not have anything to give others. He will communicate to them nothing but the contagion of his own obsessions, his aggressiveness, his ego-centered ambitions, his delusions about ends and means, his doctrinaire prejudices and ideas.

Thomas Merton Contemplation in a World of Action
Oct 8th, 2015

Harder Better Faster Stronger Cover

Oct 5th, 2015

Ranked List of Star Trek Films

On a recent Mac Power Users episode, David Sparks and Katie Floyd were trading their most and least favorite Star Trek movies1. I haven't given the series much thought in a while, and had some different opinions.2

  1. Star Trek VI: The Undiscovered Country (1991)
  2. Star Trek (2009)
  3. Star Trek II: The Wrath of Khan (1982)
  4. Star Trek: First Contact (1996)
  5. Star Trek Into Darkness (2013)
  6. Star Trek IV: The Voyage Home (1986)
  7. Star Trek Generations (1994)
  8. Star Trek III: The Search for Spock (1984)
  9. Star Trek V: The Final Frontier (1989)
  10. Star Trek: The Motion Picture (1979)
  11. Star Trek: Insurrection (1998)
  12. Star Trek: Nemesis (2002)

Most people rank The Final Frontier as their least favorite of the set, but I don't mind it as a movie. It's my least favorite of the original series crew, but definitely not my least favorite overall.

I really like The Undiscovered Country. It was the first one I saw in theatres, having only recently gotten into Star Trek TNG around the same time, and it showed off the growing special effects capabilities of the industry with the older cast.

I also am a big fan of the reboot films. I think Chris Pine is a better Kirk than Shatner3, and the casting overall is spectacular. The writing is great and the integration of Leonard Nimoy as Spock' is a bonus. I get all of the timeline weirdness, and truthfully I don't care how/why they are where they are and how it's not the same as the original timeline. I'm not a purist.

Overall, the TNG cast films aren't my favorites. Once Frakes got ahold of the director's chair, the films became too much inside baseball and less about advancing the universe. Generations was good, mostly because of the Kirk. First Contact was really good. Insurrection was ok, but not good. The ending to Nemesis made me furious.4 It might be one of the only times I didn't enjoy Tom Hardy in a film.


  1. though not very seriously

  2. To be clear, I own all of them and would watch any of it were offered.

  3. sacrilege, I know

  4. That whole movie made me furious. It should have been called Star Trek: Narcissist for how Picard-focused it was. I wanted so badly to like it, but ended up hating nearly every plot direction.

Sep 30th, 2015

Preach The Word

A couple of months ago, I was asked by our associate pastor to step in for him and preach this fall. I come from a short-but-wide line of preachers, and despite my last time in a pulpit being about 25 years ago, I agreed to step up. We're in the middle of a series on Spiritual Practices, and I picked "Silence" as a topic from a category about self-denial.1

For inspiration, our associate sent me this video. I had a really hard time not laughing:

My week was this past Sunday. If you're at all interested in hearing me talk about being quiet and still, you can do so here. I think I set a recent record for shortest sermon by keeping it under 25 minutes.2

This was an interesting experience for me. I watched my parents go through the sermon preparation process every week from as early as I can remember through leaving home to go to college. I definitely didn't appreciate it then, and I think I only barely do now. I haven't done much public speaking in my life that didn't involve singing in one way or another, and thankfully this wasn't a very controversial topic on which to expound holy scripture... also I knew 98% of the people sitting in front of me. The barriers were relatively low.

I'm speaking at a conference in about six weeks. More on that another time.. I expect that experience to be far more nerve-wracking than this was.


  1. The ironies about speaking about silence are abundant and appropriate.

  2. You’re welcome.

Sep 28th, 2015

The Solar System To Scale

Wow.

Sep 18th, 2015

Top Four

Launched today on the Relay.FM podcast network, Top Four features husband/wife team Marco and Tiffany Arment sharing their relatively strongly-held opinions about stuff.

You know.. stuff?

In Episode 1 the Arments discuss their top four favorite video games.1 I have never been much of a gamer, and I don't play many video games now, but for a time I spent way too much time staring at a screen for non-productive gaming purposes. From 2001 to 2006, I probably spent between 15 and 30 hours per week playing one or more games. Then I got married. Then my PC died and we bought an iMac. To finish me off, on the second Christmas following our wedding, Cait asked me to give up all video games for one year. I was already waning in my play time anyway, so it wasn't much of a hardship.

After that year, I tried to go back and just could not handle gaming for more than 20 minutes before I started to get twitchy. I was broken2. Having an iPhone for a few years now has given me many opportunites to get sucked back into electronic gaming. Admittedly, I've spent far more time than I'd be willing to type on one of a few dozen iOS games here and there. None of these are really going to rate highly enough for me to say they're a favorite, and the reality is that I just no longer love video games. I'll play them; I'll enjoy them, and I'll waste time playing a game on my phone now and again[^pho], but I'm just not that into it.

If I had to rate the games I used to enjoy and would maybe kinda sorta enjoy them again, they'd be:

  1. Tecmo Bowl (NES): Yes, seriously. I was so good at this game, even without a Game Genie.
  2. Minesweeper (PC): YES. MINESWEEPER. Once you get really really good at Beginner (~5 seconds) and Intemediate (~14 seconds) and then spend your entire middle school years trying to get a high score under 40 seconds for Hard, it becomes part of you. You dream of mines.
  3. Risk (PC/iOS): Also beloved in tabletop form. I heartily enjoy seeing just how each electronic version varies its own internal strategies when playing "AI" players. RISK II for PC had this multi-turn idea where each player would queue up their moves and then they'd all happen at once. This meant that players could have conflicting moves which had to be "merged" into one another. It was awesome and led to some of the shortest games of Risk I've ever played (I conqueued the world in 5 turns at one point). The latest iOS verison let's you keep playing on a turn so long as you have armies left in attack-able positions. It's good.
  4. I can't pick a fourth. It's a toss-up between Starcraft, Half-Life 2 and Unreal Tournament 2004... all of which I used to love for totally different reasons from one another. I'm unwilling to admit how many hours I spent perfecting my Onslaught strategies from 2004-2005. UNWILLING.

No matter. I enjoyed listening to the Arments talk about their love for their most enjoyed games. If you've been around the world of ATP or any of Marco's prior podcasts, and enjoyed listening to him, this is another great one3.


  1. Ok, Tiff does. Marco lists, like, twenty.

  2. In a good way, of course…

  3. All the great shows…

Aug 17th, 2015

Changing the Author of a Single Tag in git

My team recently moved from Subversion to git, a migration we have been considering and planning for the past several months. We tripped over many stumbling blocks on our way; I intend to write about many of those in the coming months.

The day after the migration, we found ourselves needing to create a support branch1 for one of our components. We do this by branching from a tag and naming it as such:

1
2
git checkout -b support/1.2.x component-1.2.0
git push -u origin support/1.2.x

That should just work. However, we have quite a few pre-commit hooks in place to keep everybody working with the same set of standards. One of these requires that you're pushing only your own changes, and that your author info matches. In SVN, our tags were all created by an internal service account running our releases (srv-clubuilder), so the release point and the tag created from it were both done under that user. Well, that user doesn't exist any more, and certainly I'm not him. When my team tried to do the above, we saw this lovely error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Counting objects: 1, done.
Writing objects: 100% (1/1), 190 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
remote:
remote:   (c).-.(c)    (c).-.(c)    (c).-.(c)    (c).-.(c)    (c).-.(c)
remote:    / ._. \      / ._. \      / ._. \      / ._. \      / ._. \
remote:  __\( Y )/__  __\( Y )/__  __\( Y )/__  __\( Y )/__  __\( Y )/__
remote: (_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)
remote:    || E ||      || R ||      || R ||      || O ||      || R ||
remote:  _.' `-' '._  _.' `-' '._  _.' `-' '._  _.' `-' '._  _.' `-' '.
remote: (.-./`-'\.-.)(.-./`-`\.-.)(.-./`-`\.-.)(.-./`-'\.-.)(.-./`-`\.-.)
remote:  `-'     `-'  `-'     `-'  `-'     `-'  `-'     `-'  `-'     `-'
remote:
remote:
remote: Push rejected.
remote:
remote: refs/heads/support/1.2.x: bb3ac1ef7b5ef61d9b999bd7ad231803f18de440: expected committer email 'Matt.Shelton@nuance.com' but found 'srv-clubuilder@nuance.com'
remote:
remote: refs/heads/support/1.2.x: bb3ac1ef7b5ef61d9b999bd7ad231803f18de440: expected committer name 'Shelton, Matt' but found 'srv-clubuilder'
remote:
To http://Matt_Shelton@git-host.nuance-internal.com/stash/git/CLU/clu-component.git
 ! [remote rejected] support/1.2.x -> support/1.2.x (pre-receive hook declined)
error: failed to push some refs to 'http://Matt_Shelton@git-host.nuance-internal.com/stash/git/CLU/clu-component.git'

Yes. Those are teddy bears.

I tried a few things like creating an empty commit as myself and pushing that, but it didn't matter—that wasn't the commit it was complaining about. I tried amending that commit, but since I hadn't changed anything, there was nothing to amend. In order to modify the branch, I needed to change the author of the tag itself. Most search results directed me to use git-filter-branch, and while I'm comfortable re-writing my repositories history (we did just do that, more or less, during the migration), I didn't want to cause any additional confusion.

Git, however, is pretty darn powerful. Some teams, after they have been using git a while, move from a traditional lightweight tag to a richer annotated tag, which might contain additional author information or be cryptographically signed by the author. We're sticking with lightweight tags for now, but the migration process for historical tags was similar to what I wanted to do. In the end, I was able to re-write only the tag and successfully create my branch:

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
$ git checkout component-1.2.0
Note: checking out 'component-1.2.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 86f405f... [maven-release-plugin]  copy for tag component-1.2.0
$ git tag -d component-1.2.0
Deleted tag 'component-1.2.0' (was 86f405f)
$ git commit --amend --allow-empty --author="Shelton, Matt <Matt.Shelton@nuance.com"
[detached HEAD 37a8015] [maven-release-plugin]  copy for tag component-1.2.0
 Date: Thu Feb 20 19:36:40 2014 +0000
$ export GIT_COMMITTER_DATE="$(git show --format=%aD | head -1)"; git tag component-1.2.0
$ git push origin :refs/tags/component-1.2.0 # Push that tag ref to the origin as re-written
To http://Matt_Shelton@git-host.nuance-internal.com/stash/git/CLU/clu-component.git
 - [deleted]         component-1.2.0
$ git push --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 290 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To http://Matt_Shelton@git-host.nuance-internal.com/stash/git/CLU/clu-component.git
 * [new tag]         component-1.2.0 -> component-1.2.0

The result of this was exactly as I expected. When I look at the commit in the history, I now see that I am the author of this tag:

1
2
$ git log --tags --show-notes --simplify-by-decoration --pretty="format:%ai %d %s (%an <%ae>)"| grep component-1.2.0
2014-02-20 19:36:40 +0000  (tag: component-1.2.0) [maven-release-plugin]  copy for tag component-1.2.0 (Shelton, Matt <Matt.Shelton@nuance.com>)

And now I'm in business.


  1. We’re using git-flow as a branching model, but have the need to keep some releases around long-term for production support of integrations which cannot upgrade. This varies from a traditional concept of a hotfix in that we also need to be able to port features back to this release stream.

Aug 12th, 2015