For some of my projects, I use the following branching model in conjunction with Nerdbank.GitVersioning:
master always contains the latest development version, releases are built from a release/vX.Y branchmaster is always a prerelease version, so in version.json the version is set to e.g. 1.2-premaster would switch to 1.3-pre and a release branch release/v1.2would be created where the version is just 1.2I wrote a small script to automate this process. However, I think this would be useful to be included as a command in the nbgv tool.
This way, creating a new release would be as simple as running
nbgv create-release
which would
version.jsonversion.json on release branchversion.json on master branchWould you be open to include such a feature?
I would be happy to implement it and provide a PR.
That describes my work flow as well. I love the idea. But the release/ prefix to the branch name should somehow not be hard coded. If we can just figure it out automatically that would be great, otherwise perhaps a parameter passed to the command.
I would add one more step at the end: merge the new release branch into master and resolve the merge conflict as "keep ours".
That describes my work flow as well. I love the idea. But the
release/prefix to the branch name should somehow not be hard coded. If we can just figure it out automatically that would be great, otherwise perhaps a parameter passed to the command.
Could you elaborate on " figure it out automatically"? I guess this prefix would stay mostly fixed (at least per repo) so perhaps the default value could be put into version.json.
I would add one more step at the end: merge the new release branch into master and resolve the merge conflict as "keep ours".
I've never thought of merging back the release branch. Could you explain the advantages of this approach?
Could you elaborate on " figure it out automatically"? I guess this prefix would stay mostly fixed (at least per repo) so perhaps the default value could be put into version.json.
Well, the tool might look at the publicReleaseRefSpec property from version.json and find the first(?) regex that starts with refs/heads/, but that feels a little sketchy since it may have a v prefix, or may not, and we'd need to basically convert the regex into a parameterizable string, which doesn't sound generally solvable. So a new, dedicated property in the version.json file to describe it might be good. Can you make a proposal for us to discuss before coding?
I've never thought of merging back the release branch. Could you explain the advantages of this approach?
Fixes that are made to your servicing/release branches should presumably be merged into newer service/release branches, and into your master branch so that those fixes propagate into your upcoming release. But as the flow of changing the same line in version.json in both the release branch and the master branch by definition introduces a merge conflict, and you know you can resolve it at the time you create it as "keep ours", you can keep the next _real_ merge with fixes from release to master free of unnecessary merge conflicts.
If you make your fixes in master and cherry-pick to release branches as your only means of servicing, as some teams do, then this extra merge wouldn't strictly be necessary. But it's harmless to do even in that case, so doing it in the tool allows for either flow.
...
So a new, dedicated property in the version.json file to describe it might be good. Can you make a proposal for us to discuss before coding?
The version.jsoncould also include a setting controlling how the version on master in incremented, so I' suggest to model it something like this:
{
"version": "1.0",
"release": {
"branchName": "release/v{0}",
"versionIncrement" : "minor"
}
}
branchName: Format string for the release branch name. I'd just use a C# format string to keep the implementation simple.versionIncrement: Controls whether the major or minor version is incremented on master... you can keep the next _real_ merge with fixes from release to master free of unnecessary merge conflicts.
Makes sense, I'll include the merge back to master
That looks good. I think the nbgv command should include an optional parameter to explicitly set the new version number though. That way minor can be default but one can force the next version to be a major version bump, for example.
I'm also thinking that release may not be the operative word here, since folks will most likely do this during stabilization of a release, which precedes the release itself. That's not a design change though, so you can proceed with coding. But maybe we'll call the command prepare-release or branch-release instead of create-release.
Okay, great, I'll get back to you once I have something to show
Keep in mind that the pre-release tags might look like this "1.0-preview.{height}" or "1.0.0-preview.{height}" if someone wants to put the auto-incremented number for a prerelease outside of the patch.
What would you expect in that case, @onovotny? Removing the prerelease tag in the release branch would have to remove the shift in the build number, so the patch would bump from then on.
Exactly
Perhaps what we can do then is have this new command accept an optional parameter that is the new unstable tag. For example, if master is building -beta then the prepare-release rc command will change -beta to -rc in the new branch. This makes the command generally more useful (since stabilization branches might not want to jump directly to building "stable" packages), and it also allows folks who move the git height to some prerelease tag to use the command as well.
When the command is invoked on a branch whose name already conforms to the release name convention, instead of branching, it simply changes the prerelease tag. So:
# version: 1.0-beta.{height}
[master]> nbgv prepare-release rc
# v1.0-rc branch created and version set to 1.0-rc.{height}
# v1.1-beta continues in master branch
[v1.0]> nbgv prepare-release
# version.json in current branch is now 1.0 (-rc.{height} is removed)
[v1.0]>
Thoughts?
I think this feature is more complex than I initially thought.
I'll try to summarize the different scenarios.
The git height can appear in the version as either (I'll use the wording from the VersionPrecision enum):
Assuming the release branch format is v{version}:
Scenario 1: The version in version.json is either X.Y or X.Y-tag
nbgv prepare-release on any branch != vX.YX.(Y+1) respectively X.(Y+1)-tagvX.Y without version X.Ynbgv prepare-release rc on any branch != vX.YX.(Y+1) respectively X.(Y+1)-tagvX.Y without version X.Y-rcnbgv prepare-release on branch vX.Y and version has prerelease tagnbgv prepare-release on branch vX.Y and version has no prerelease tagnbgv prepare-release rc on branch vX.Y and version has prerelease tagX.Y-rcnbgv prepare-release rc on branch vX.Y and version has no prerelease tagX.Y-rc (adding the prerelease tag)Scenario 2: The version in version.json is either X.Y.Z or X.Y.Z-tag
This scenario could be handled the same as scenario 1 just using a 3-segment version, e.g. a release branch would be vX.Y.Z
Scenario 3: The version in version.json is something like X.Y.Z-tag.{height}
nbgv prepare-release on any branch != vX.Y.ZX.(Y+1).Z-tag.{height}vX.Y.Z with version set to X.Y.Z. nbgv prepare-release rc on any branch != vX.Y.ZX.(Y+1).Z-tag.{height}vX.Y.Z with version set to X.Y.Z-rc.{height}. nbgv prepare-release on branch vX.Y.Z and version has prerelease tagnbgv prepare-release on branch vX.Y.Z and version has no prerelease tagnbgv prepare-release rc on branch vX.Y.Z and version has prerelease tagX.Y.Z-rc.{height}Some open questions I'm currently not sure about:
X.Y-tag.{height} or can the height be prefixed and suffixed with more tags? This could make setting the tag as part of the prepare-release command difficultShould we prohibit some scenarios, e.g. adding a prerelease tag to a version that has no tag (effectively decrementing the version)?
At least printing a warning makes sense. We could block it and potentially allow it if -force was specified, but we don't need to go to the trouble of supporting that before folks ask for it.
When including the git height in the tag, is the format always X.Y-tag.{height} or can the height be prefixed and suffixed with more tags? This could make setting the tag as part of the prepare-release command difficult
Yes, it can appear in multiple formats. My suggestion would be to simply add/replace the first prerelease identifier using the one specified at the command line. So -beta becomes -rc, -beta.{height} becomes -rc.{height}, -beta.{height}.foo becomes -rc.{height}.foo.
Regarding your scenarios, I'll assume that all your vX.Y branch specs are based on a version.json file with:
{
"release": {
"branchName": "v{0}",
}
}
Since if the branchName was release/v{0} then I presume all your branch name checks would be matching for the new pattern.
Regarding the transformation of the version on the master branch (or any branch that does not conform to the release branch pattern) as part of the command, I think we should add/change the first identifier of the -prerelease tag to be some constant, as defined by version.json. So the user can specify that any new version should start out as -alpha or -beta. So for examples (hint: these should become test cases, encoded as arguments to a [Theory]. I will assume that release.branchName = v{0} and release.firstUnstableTag = -alpha, release.versionIncrement = minor.
When run from any branch whose name does not conform to a release branch:
| HEAD version | Command | New version | Created branch -> version
| -- | -- | -- | -- |
| 1.0-beta | prepare-release | 1.1-alpha | v1.0 -> 1.0
| 1.0-beta | prepare-release rc | 1.1-alpha | v1.0 -> 1.0-rc
| 1.0-beta.{height} | prepare-release | 1.1-alpha.{height} | v1.0 -> 1.0
| 1.0-beta.{height} | prepare-release rc | 1.1-alpha.{height} | v1.0 -> 1.0-rc.{height}
When run from a release branch, no branch is created, and the following truth table applies:
| HEAD version | Command | Original branch version
| -- | -- | -- |
| 1.0-beta | prepare-release | 1.0
| 1.0-beta.{height} | prepare-release | 1.0
| 1.0-beta | prepare-release rc | 1.0-rc
| 1.0-beta.{height} | prepare-release rc | 1.0-rc.{height}
From this we see that there are only 2-3 independent variables, that impact different things. So I hope this is a simpler model and simpler to code up than thinking about all the scenarios and their variants that you have listed.
Regarding your scenarios, I'll assume that all your
vX.Ybranch specs are based on a version.json file with:{ "release": { "branchName": "v{0}", } }
Yes, that's what I meant, forgot to explicitly mention that.
I think the behavior you described makes the most sense. I'll implement it like this
Most helpful comment
At least printing a warning makes sense. We could block it and potentially allow it if
-forcewas specified, but we don't need to go to the trouble of supporting that before folks ask for it.Yes, it can appear in multiple formats. My suggestion would be to simply add/replace the first prerelease identifier using the one specified at the command line. So
-betabecomes-rc,-beta.{height}becomes-rc.{height},-beta.{height}.foobecomes-rc.{height}.foo.Regarding your scenarios, I'll assume that all your
vX.Ybranch specs are based on a version.json file with:Since if the
branchNamewasrelease/v{0}then I presume all your branch name checks would be matching for the new pattern.Regarding the transformation of the version on the
masterbranch (or any branch that does not conform to the release branch pattern) as part of the command, I think we should add/change the first identifier of the -prerelease tag to be some constant, as defined by version.json. So the user can specify that any new version should start out as-alphaor-beta. So for examples (hint: these should become test cases, encoded as arguments to a[Theory]. I will assume thatrelease.branchName=v{0}andrelease.firstUnstableTag=-alpha,release.versionIncrement=minor.When run from any branch whose name does not conform to a release branch:
| HEAD version | Command | New version | Created branch -> version
| -- | -- | -- | -- |
| 1.0-beta |
prepare-release| 1.1-alpha |v1.0-> 1.0| 1.0-beta |
prepare-release rc| 1.1-alpha |v1.0-> 1.0-rc| 1.0-beta.{height} |
prepare-release| 1.1-alpha.{height} |v1.0-> 1.0| 1.0-beta.{height} |
prepare-release rc| 1.1-alpha.{height} |v1.0-> 1.0-rc.{height}When run from a release branch, no branch is created, and the following truth table applies:
| HEAD version | Command | Original branch version
| -- | -- | -- |
| 1.0-beta |
prepare-release| 1.0| 1.0-beta.{height} |
prepare-release| 1.0| 1.0-beta |
prepare-release rc| 1.0-rc| 1.0-beta.{height} |
prepare-release rc| 1.0-rc.{height}From this we see that there are only 2-3 independent variables, that impact different things. So I hope this is a simpler model and simpler to code up than thinking about all the scenarios and their variants that you have listed.