No matter which style of git workflow you use, it is generally (but not by everyone) agreed that committing directly into the default branch
is a bad idea.
We use the term
default branch
to mean any branch you have configured as your default, this is oftenmaster
ormain
but can be configured on a repo by repo basis.
Some of the reasons for this include (and there are many many more):
- If you push a work-in-progress state to remote, the
default branch
is potentially broken. - If another developer starts work for a new feature from the
default branch
, they start with a potentially broken state. This slows down development. - Different features/bug-fixes are not isolated, so that the complexity of all ongoing development tasks is combined in one branch. This increases the amount of communication necessary between all developers.
- You cannot do pull requests which are very good mechanism for code reviews.
- You cannot squash commits/change git history in general, as other developers might already have pulled the
default branch
in the meantime.
There are 2 main solutions that can be used to attempt to solve this problem. There are others but these are the most common.
- Protect the branch - This is a server side solution and requires no involvement from the end user.
- Protect the commit - This is a client side solution and required a small amount of setup work from the end user.
This solution works by making it is impossible to push directly to the branch. In Github (and Gitlab and others) you can give a branch protected
status. This has the effect of stopping anyone from pushing to it. Some of the basic pros and cons for this approach are:
Pros:
- Stop any changes being made directly to the branch
- No user interaction required
- Works across repeated clones
Cons:
- All or nothing solution
- Only stops the push not the local commit (requires user to unpick the local issues)
This solution works by stopping the user from committing code locally to the branch rendering a push the branch pointless as there are no local changes to push. This is most often achieved with the use of pre-commit hooks.
A pre-commit hook is run first, before you even type in a commit message. It's used to inspect the snapshot that's about to be committed, to see if you've forgotten something, to make sure tests run, or to examine whatever you need to inspect in the code.
By using a pre-commit hook you can inspect the branch the user is attempting to commit to and take any required actions. Some of the basic pros and cons for this approach are:
Pros:
- Stop any changes being committed to the local branch
- Doesn't require any server side work
Cons:
- Has to be configured for each new clone
When it comes to local branch protection there are 2 main ways to approach the problem:
- Block - Simply block the commit (see our block-default-branch-commit repo for this solution).
- Prompt - Warn the user they are trying to commit and prompt for confirmation (This solution).
This solution implements a warn/prompt solution, so rather than blocking the user from committing to the default branch
, it will instead warn them they are doing so and ask them to confirm that this is what they want to do.
The main reason for this is that although pushing to default branch
should be avoided as much as possible, we do foresee times where pushing to the default branch
is needed, or at the having that ability is a perceived benefit.
# git commit -m "Some nice commit message"
Are you sure you want to commit to the default branch? [Yes/No]
If the users selects no
then the commit is aborted (same impact as blocking
the commit), but if they select yes
then the commit continues as normal.
This allows for greatly flexibility, as it will alert the user to the fact they are committing to the default branch
, but can allow it to happen when it is the desired intent.
Copy the script to the .git/hooks/pre-commit at the root of the desired repository (and ensure that it is executable [chmod +x]). At this point the hook will run (fire) every time you attempt to commit a change to the repository.
The name pre-commit is a special name used internally by git so must be the name used when you copy the script into the repository.
If you are not sure where the root of the repository is, then execute the following from within any directory in your repository.
r=$(git rev-parse --git-dir) && r=$(cd "$r" && pwd)/ && cd "${r%%/.git/*}" && pwd
The above will give you the full path to the root of your repository no matter which directory you are in (even inside the .git directory or any of its subdirectories).
If you are sure you are in just a normal directory you could issue the following command instead. It is easier to remember but isn't going to work 100% of the time.
git rev-parse --show-toplevel
If you see something similar to fatal: this operation must be run in a work tree then try the first command instead.
By default this script uses master
as the default branch name, but this can be changed in one of 2 ways.
Automatic branch identification does result in a short pause while it identifies the default branch from the remote end.
A pre-commit (and all other git hooks) hook can only be a single script, so if you want to run multiple different scripts as part of a 'pipeline' then you need to make use of our Git Hook Multiplexer.