Using Grunt and Git for versioning
Tagged with: grunt, development, windgazer, bitbucket2phonegap
In our previous article I've shown you how to commit a clean build into an Orphan branch, keeping your build close to your source, without either cluttering up the other. The next logical step is to start properly keeping track of versions. This article will deal with taggin your repository to do just that.
Semantic versioning with tags
Versioning for most projects these days is pretty much all done according to the Semantic Versioning rules declared at semver. It is a really simple set of guidelines and both Node and Bower make use of tags in your repository to full benefits.
This part's really easy if you've been following my series of articles on building html5 application from your command-line all the way to Phonegap. Just in case you're just stepping in, I'll add all the minimum required steps to get this to work. Let's start by making sure you've got grunt-git installed:
npm install grunt-git --save-dev
Now this time we're going to use the gittag task to tag our latest commit with the current versions:
gittag: {
release: {
options: {
cwd: "target/release.git/",
tag: "v<%= pkg.version %>"
}
},
source: {
options: {
tag: "v<%= pkg.version %>-src"
}
}
}
To make sure I'm tagging the right commit, I assume that the latest code on the master
branch is the correct version for tagging the source release and whatever is on the
release
branch must be the latest compiled release. Perhaps at some point it'll be worth
investigating making some safety checks, but for now that's up to manual verification...
Anyways, some safety can be applied, for instance by always checking out the master
branch before tagging:
gitcheckout: {
source: {
options: {
branch: "master"
}
}
}
This way, I can tag the source and compiled release in one fell swoop by running. Adjust the following dish to a flavour of your own liking ;)
grunt clean:release mkdir gitclone:release gitcheckout:source gittag
Now's a good time to verify that the tag got placed on the right branches, etc. All that remains is pushing it upstream, for those who've got some gitpush tasks set up:
grunt gitpush
Although I have no way of proving that I put those tags in the repository by way of grunt, you can see in the commits overview of my reference repository what sweetness the tags bring to the table.
Bumping the version
Great, we've released and tagged our project and source files, wat's next? Right, we'll be needing to 'bump up' the current version number. With most of my projects I'm using both Bower and Node, which unfortunately means I have two files with versions in it. Still easy to change manually, but then, I'm lazy ;) So, some clever use of a little Grunt plugin appropriately named grunt-bumpup should help us solve this puzzle quick enough:
grunt-bumpup --save-dev
Since we're not going to do anything fancy with it, all we need is to tell it which files to bump. For safety I always add the normalize option, which is aimed at keeping all files in sync, even if somebody's been cheeky.
bumpup: {
files: ["package.json","bower.json"],
options: {
normalize: true
}
}
Go ahead, give it a test-run:
grunt bumpup
The result are awaiting you at the relevant commit on our reference repository.
The final dish
Let's combine all that came before with what we've learned in this article and update
our release target. I've realised a few things while writing my articles so that I've been
able to clean up my original work and create, what I believe to be, a nice a clean way of
releasing my work to my upstream repository. First of there's some changes to the
Gruntfile
, I've quoted the entire config section and/or task that I've changed, instead
of only quoting the additions to them:
gitadd: {
release: {
options: {
all: true,
cwd: "target/release.git/",
force: false
},
files: {
src: ["."]
}
},
source: {
options: {
all: true,
force: false
},
files: {
src:["."]
}
}
},
gitcommit: {
release: {
options: {
cwd: "target/release.git/",
message: "Releasing v<%= pkg.version %> build <%= buildVersion %>",
allowEmpty: true //In case of no changes since last dev build...
},
files: {
src: ["."]
}
},
source: {
options: {
message: "Version bump"
},
files: {
src:["."]
}
}
},
gittag: {
release: {
options: {
cwd: "target/release.git/",
tag: "v<%= pkg.version %>"
}
},
source: {
options: {
tag: "v<%= pkg.version %>-src"
}
},
dev: {
options: {
cwd: "target/release.git/",
tag: "v<%= pkg.version %>-<%= buildVersion %>"
}
}
}
grunt.registerTask("release",
"My custom release task, can be run in stages [prep|dev|live], prep must be used " +
"before live!\n" +
"'dev' will commit and push to release branch without confirmation.\n" +
"'prep' will stash anything on current branch and checkout master branch.",
function (type) {
var isDev = type === "dev";
if (!isDev) {
grunt.task.run("releaseclean");
} else {
type = "prep";
}
type = type ? type : "prep"; // Default release type
grunt.task.run("release" + type);
if (isDev) {
grunt.task.run("releasedev");
}
});
grunt.registerTask(
"releaselive",
[
"gittag:source",
"gittag:release",
"gitpush:release",
"bumpup",
"gitadd:source",
"gitcommit:source"
]
);
grunt.registerTask(
"releasedev",
[
"gittag:dev",
"gitpush:release"
]
);
grunt.registerTask(
"releaseclean",
[
"gitstash",
"gitcheckout"
]
);
For reference, have a look at the merge commit on my reference repository. It might help you figure out how the pieces are supposed to fit together...
A couple of things to take note of.
grunt release:dev
The release:dev
task is a little aggressive. It doesn't stash, or checkout anything. It
simply builds the current state of the code, throws it to the release branch and tags it
with a pre-release build version. This allows me to quickly throw what I'm working on to
my release repository and have test-build out there. The semver versioning fully
supports this kind of release as having the lowest priority.
grunt release:prep
When running release:prep
my solution will stash any dirt on the current branch and
checkout the master
branch. I have for myself an unbreakable rule that final releases
are always done from the master
branch. This is a safeguard that helps me realise this
goal without accidentally throwing away code.
grunt release:live
Should only be used after running release:prep
. This task assumes the current state of
your working directory has the latest source code release committed and working as you
want it. It then assumes that in the target/release.git
sits a post-processed and clean
version of the same build. It goes ahead and tags both the master
and release
branch
with the appropriate version tags, pushes the release branch upstream and finally bumps
and commits the master
branch to the next patch version so that you cannot (easily) do
another release with the same version.
Next
In the next chapter we'll be wrapping up this series by kicking Phonegap into action, thus completing our whole cycle.