How to manage the source code of a project under version control in a robust and smarter way? Here is a review of the brilliant article by Vincent Driessen on the issue: the workflow of a source code managed by GIT, handling releases, bug-fix and new features …
In this document, the key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
Common package structure
A package’s repository SHOULD be organized around two branches: master
and dev
to differentiate the distributed stable version (the master
branch) and the development one (the dev
branch). The development branch MAY embed a demonstration if so and MUST embed an automatic package’s code documentation.
The repositories workflow SHOULD mostly follow the model described by Vincent Driessen:
fix-XXX
--+--
x.y.z / \ x.y.(z+1)
---------------------+-----------------+---------------------------> master
\ /\ \
----+-------+------+-------+------------+-----+------------------> dev
\ /
----+-------+- feature-YYY
(x-1).y.(z+1)
... ----------------------------------+---------------------------> (x-1).y.z (master archive)
\ /
--+--
fix-XXX
Default organization
A PHP package MUST be structured like the following for both master
and dev
branches:
composer.json // the package manifest
bin/ // optional package's binaries
doc/ // the MDE documentation
src/ // the code base directory
---- NameSpace/ // the namespace root directory
tests/ // optional unit-tests scripts
A README.md
file MUST be present at the root directory of the package and CAN be present in any other sub-directory. This kind of files MUST explain the content and usage of current package/directory scripts.
A CHANGELOG.md
file MUST be present at the root directory or the package with a complete changelog between releases (it is only necessary on the master branch as it may only be updated before each release).
All informational files MUST be written following the MDE syntax for homogeneity.
Development specifics
The dev
branch MAY present the following directories:
phpdoc/ // the PHP documentation
demo/ // optional demonstration
These MAY be excluded from a merge into the master branch (or deleted if they had been merged).
A best and light-weight practice is to use my HTML5 quick template as a templater for demonstrations. It allows all needs to build a demonstration page.
Releases
Following the repositories organization described above, a release can (should) actually be two releases: the classic vX.Y.Z
taken from the master and its development equivalent, named like vX.Y.Z-dev
taken from the dev branch. We MUST follow the Semantic Versioning for a release reference X.Y.Z[-suffix]
:
- the
X
is the major version and MUST only be increased when the app’s API changes - the
Y
is the minor version and MUST only be increased when the app embeds a new feature, with backward compatibility (the API does not change) - the
Z
is the patch version and MUST be increased for each bugfix added to aX.Y
version - a suffix can be added to identify a release’s state (beta, alpha …) or to distinguish it from the master one (dev for instance).
The dev release should always exist at least for any major version, and at the best for each minor version.
Deep in a repository life-cycle
I will redraw here the interesting work of Vincent Driessen about a “git branching model“. The point of this section is to build a robust, clear and specified software development workflow in a Git environment.
The different actions on a source code
During the life of a software source code, we can distinguish three types of “actions” on it: the releases, which are the official publications of the software, the new features which can be just a simple new thing incorporated in a published release, and the bug-fixes which come to correct a bug in the code, for the last published release or an older one.
As explained in the section above, our source code is, at least, separated in two branches with specific life-cycles: the master branch is always the production-ready state of the code and should only be used to create releases, and the dev branch is the preparation-place of the code, with all latest changes to incorporate in the upcoming release. The master MUST always be fully merged in the dev branch, but this last one will often be ahead master as it will embed some new features from last release. Such organization implies two mandatory rules:
- each commit on the master branch (actually a merge of another branch) MUST result in a release tag, with the only exception the update of the CHANGELOG in some cases;
- the dev branch MUST have, at least, the exact same history as the master one, but will often be ahead (have new commits not yet incorporated in master).
This means that if you need to modify history for any reason (well, first, it’s really a bad thing!), like rewrite a commit message, you MUST do it on the dev branch, which will be used for next release …
A word about branching, committing and merging
As we will see below, no work is really done on the master branch (of course) neither on the dev one. Any development is first made on a specific branch which is finally merged following rules we will discuss later. As we can see, a workflow like this implies a lot of branches and commits. The key points to not be lost in our repository history is to follow some simple rules to name our branches and comment our commits.
The branches must be named to identify the related “action” done on the code:
- for a bug-fix action, we would use a branch named
fix-XXX
, where the XXX may refers to a bug ticket referenced on the repository ; - for a feature action, we would use a branch named
feature-XXX
, where the XXX refers to a feature ticket referenced on the repository ; - for a release branch, we would use a branch named
release-A.B
, where the A.B reference is the target version number.
We will see below from which original branch we gonna create all these.
The commits must be commented allowing to retrieve the related action and ticket (if so), and to retrieve the original author if necessary as the branches will be merged into others, often by somebody else than the original worker. We can design the following model for the first line of commit messages:
fix #XXX - [optional scope] your text
feature #XXX - [optional scope] your text
Generally, we can inspire from Tim Pope’s note about commit messages.
Finally, it is a very good advice to always use the --no-ff
option of the git-merge
command when merging a branch into a primary one. This will force Git to create a commit identifying the merge process and listing the merged history. This will be very useful if we have to revert a merge one time (just in case).
Workflow review
For the explanations below, let’s say the last release of our software is X.Y.Z.
Creating a new release
The work on a planed new release is done on a branch named release-A.B, created from the dev one. The A.B name must be defined when creating the branch as it will happen soon. The A can be the original X for a minor release or X+1 (with B=0) for a major one. The B can be Y+1 (if A=X) or 0 for a major release.
The version number (A.B) MUST be changed in files as soon as the branch is created, to differentiate it from current production version. This way, the first commit of a release branch identifies that next release:
release-A.B - milestone-XXX - your text
In that new branch, no feature is merged at any time. They will be integrated after the first publication of that release. During the branch life-cycle, a bug-fix correcting a bug on that branch will be merged into it directly (and not in the dev one).
If the repository is hosted on GitHub, a best practice would be to create a new milestone for the release, with some key features.
Before to publish a new release, some beta or nightly versions can be made and published for beta-testers. It is important to identify these releases as “not officially published* by suffixing the releases tags with a status word like beta or nightly:
A.B-beta
A.B-nightly-DATE
A good practice could be to propose a specific package.dev@domain.com mailing-list to inform our beta-testers about each beta releases or the way they are automatically constructed.
Finally, when the release is ready to pbe published, it MUST be first merged back into master. Then a release tag is prepared from master, eventually signed with a GPG key, and officially published to software users. The new release MUST be named following the original feature branch name (in our case, A.B.0
). A good practice could be to propose a specific package.releases@domain.com mailing-list to inform our users about each releases.
For each new release, we MUST merge back the master branch into the dev one to keep it at least in last software state. Some conflicts may happen during this merge but it is required.
Once the original branch is fully merged into master, it can be deleted.
Creating a new feature
A feature is a new “thing” that will be added in our software’s source code. As it does not correct a bug, a feature is not incorporated in an existing release but will be merged into the dev branch to be embedded in the next release (major or minor). The actual master will never have a new feature before the next release.
Knowing that, a new feature is developed on a branch named feature-XXX created from the dev one and where the XXX should refer to a registered feature ticket on the repository (as said above). The developments made on that branch may not follow a specific model since they can be many.
When the feature seems ready, it must be merged into the dev branch, with a commit identifying the feature like (using the --no-ff
option as said above):
feature #XXX - [optional scope] your text
# or
close #XXX - name of the feature (original author)
Once the feature is actually merged into the dev branch, the original feature-XXX
branch can be deleted and the ticket should be marked as “ready-for-next-release”.
When incorporating a feature in the dev branch, the CHANGELOG should always be updated to include the new code, at least to present the merge commit message (to let users retrieve embedded features in next release).
dev
is ready for a new minor release:
- update the CHANGELOG
- upgrade version to
X.(Y+1).0
- backup current
master
in branchX.Y
- merge
dev
intomaster
(with subtree exclusion) - build a tag release
X.(Y+1).0
- email info to the
package.releases@domain.com
Creating a bug-fix
The work is done on a branch named fix-XXX
, created from the concerned “dev-master” one: the master
branch itself if the bug concerns current version, the release-A.B
one otherwise.
Once the bug is fixed, we must commit it with a commit message constructed like:
fix #XXX - [optional scope] your text
Then we must merge the branch into the dev one. Once it has been merged, we must update the CHANGELOG to add the fix info.
If a new release must be done (if the bug badly affects the current release for instance), we must first merge the fix branch into the master one and update the CHANGELOG. Then, we must upgrade to version X.Y.(Z+1)
and then publish the new tag release.
Once all this is done, the original fix-XXX branch can be deleted.