Gitflow and Gitversion wrapped in Powershell
2017-04-18
brings to teams. I think it adds just enough rigor with declared releases and features with the agility to being able to move fast for hotfixes and keep production humming along. Getting of continuous delivery pipelines against them is fairly straightforward.
The simplicity of it does come at the price a number of git commands to get your process moving but I think that after you've first become comfortable with the git command line and going though some of the gymnastics of using the processes directly some helper scripts become useful.
I started out with a Powershell module. First setting up the consumer with some variables to keep some churn down. $m = "master"
and $d="develop"
. Since the high level unit of currency will be with features, releases and hotfixes I've setup functions for those concepts as well.
function Start-Feature {
[cmdletbinding()]
param
( [Parameter(Mandatory=$true)]$name
)
process {
Write-Host "Starting new feature $name" -ForegroundColor Green
$name = $name -replace "feature/", ""
git checkout -q -b "feature/$name" $d
}
}
The key here is that it's a branch cut from develop
and with a useful name after the features/
prefix, such as feature/add-contact-form
. This branch will land all the commits related to this feature and frees up other developers to also do independent features work derived develop
as well.
As features are finished they are merged back into develop
. Since there are couple of different flows around merging I found it useful to wrap that up as well for folks where are not super experienced with some of the subtle differences between rebase and merging.
function Update-BranchFrom {
[cmdletbinding()]
param(
[Parameter(Position = 0)]$branch,
[Parameter(ParameterSetName = "rebase", Position = 1)][switch]$rebase,
[Parameter(ParameterSetName = "merge", Position = 1)][switch]$merge,
[Parameter(ParameterSetName = "merge", Position = 2)][switch]$noff
)
process {
if ($rebase) {
Write-Host "git rebase $branch" -ForegroundColor Green
git rebase $branch
}
else {
if ($noff) {
Write-Host "git merge --no-ff $branch" -ForegroundColor Green
git merge --no-ff $branch
}
else {
Write-Host "git merge $branch" -ForegroundColor Green
git merge $branch
}
}
}
}
I also have a function for rebase
and not one for any specific merge flows is because I will rebase my feature branches from develop as develop lands commits or when really when any derived branches need to be updated from the source. Not everyone does it this way but I prefer my teams do use this approach as I personally keeps the software story cleaner.
function Resume-Rebase {
git add -A
git rebase --continue
}
My release related functions take on another dependency (the first one being git itself) by requiring GitVersion in your path. GitVersion is a super useful tool for calculating current version numbers based on the state of your repo. It outputs Semantic Versioning or SemVer which is a solid version strategy in my view.
function Start-Release {
[cmdletbinding()]
param
( [Parameter(Mandatory=$false)][switch]$majorVersion
)
process {
Set-Branch $m
$version = (gitversion | convertFrom-json)
if($majorVersion) {
$major = [int]$version.Major + 1
$minor = [int]$version.Minor
} else {
$major = [int]$version.Major
$minor = [int]$version.Minor + 1
}
$patch = [int]$version.Patch
$name = "release/$major`.$minor`.$patch"
Write-Host "Starting new $name" -ForegroundColor Green
git checkout -q -b $name $d
return $name;
}
}
This function needs to get the current version information from master
so that is can increment the minor version number and use that for the next release. For example, v1.1.8 would have the next minor release of v1.2.0 or major release of v2.0.0.
Completing a release is as simple as merging the release branch down to master. This function then assigns a tag the current commit and heads back to develop and will rebase the latest state of master
into develop
and removes the release branch afterwards. This isn't as robust as it could be but if your source branches were correctly rebased and current it should "Just Work"
function Complete-Release {
[cmdletbinding()]
param( [Parameter(Mandatory=$true)]$releaseBranch
)
process {
Set-Branch $m
$name = ($releaseBranch -split "/")[1]
Update-BranchFrom $releaseBranch -merge -noff
New-Tag $name
Set-Branch $d
Update-BranchFrom $m -rebase
Remove-Branch $releaseBranch
}
}
Here is the New-Tag
function
function New-Tag {
[cmdletbinding()]
param ( [Parameter(Mandatory=$true)]$tag
)
process {
Write-Host "git tag -a v$tag -m version v$tag" -ForegroundColor Green
git tag -a "v$tag" -m "version v$tag" --force
}
}
A Hot Fix release is almost identical except they are derived from master
function Start-HotFix {
process {
Set-Branch $m
$version = (gitversion | convertFrom-json)
$major = [int]$version.Major
$minor = [int]$version.Minor
$patch = [int]$version.Patch + 1
$name = "hotfix/$major`.$minor`.$patch"
Write-Host "Starting new $name" -ForegroundColor Green
git checkout -q -b $name $m
return "hotfix/$major`.$minor`.$patch"
}
}
function Complete-HotFix {
[cmdletbinding()]
param
( [Parameter(Mandatory=$true)]$hotfixBranch
)
process {
Set-Branch $m
Update-BranchFrom $hotfixBranch -merge -noff
Remove-Branch $hotfixBranch
$tag = ($hotfixBranch -split "/")[1]
New-Tag $tag
}
}
I have the module up on GitHub (https://github.com/motowilliams/poshflow) where PRs and suggestions are welcome.