You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The first thing to understand when trying to make a clean git project is that there can only be one trunk. Most commonly this branch is referred to as `master`, the default branch name in git. This branch serves as the source of truth in your project. Everything should be based on your trunk, and defer to your trunk when messes are made. When this idea is employed correctly, it gives us a lot of power to manage our codebase.
## Rules of the Trunk
To properly use a trunk branch, a couple of rules need to be followed.
##### #1 - _All_ branches are based off the trunk
The first rule is clear: all branches must use the trunk as a base. This is fairly obvious when making topic branches - everyone should already be branching off `master` to make features, bug-fixes, etc. The emphasis however, is on **all** branches, including environment specific branches, such as `develop`. This takes a lot of care and planning from the developer, but can be done, and will be explained in depth later on.
##### #2 - The trunk should _never_ be merged into a branch
The second rule is a repeat of the first, in essence. If the first rule is always true, a merge of the trunk into the branch would be a no-op. But the trunk can be updated and topic branches can fall behind. The point of this rule is to enforce the use of `rebase` over `merge` when dealing with the trunk. If you instead merge trunk into your topic branch, the first rule will be broken, as your topic branch's history is merged with your trunk's history, rather than being based upon it.
##### #3 - The trunk should only contain commits from a _single parent_
The last rule enforces a linear history in your trunk branch. "Commits from a single parent" means no merges commits. Merge commits have two parents, one from both branch. This rule is designed to keep the history of the trunk branch clean, as it serves as the source of all branches.
## The Basic Process
The basic git process for most projects uses `master` for production releases, other branches such as `develop` for environment releases, and topic branches named for the JIRA issue ticket. For the basics, we will pretend the other environment branches do not exist, only the trunk and topic branches.
### Creating a topic branch
According to [the first rule](#1---all-branches-are-based-off-the-trunk), all branches should be based off the trunk. So, after you ensure that you have the latest version of `master` in your local repository, you should use the following command to create **all** topic branches:
```bash
git checkout -b topic master
```
After you add a few commits to your topic branch, it should look something like the following:
```
E---F---G topic
/
A---B---C---D master
```
Clearly, the `topic` is cleanly based on `master`, no problems so far. So let's create another topic branch, for the next feature:
```bash
git checkout -b topic2 master
```
And after a few commits, we will have something like this:
```
H---I---J topic2
/
| E---F---G topic
|/
A---B---C---D master
```
Both branches are still based off `master`, as running a quick `git rebase master` will confirm. Now we are done making changes, and want to release `topic` and `topic2`.
### Releasing
Currently all releases are done through individual merges to `master`. But you might have noticed that this breaks [rule #3](#3---the-trunk-should-only-contain-commits-from-a-single-parent) of dealing with trunks. The trunk branch should not contain merge commits, only fast-forward merges are acceptable. In order to achieve this, we create a release branch.
#### Preparation
First we create a release branch off our trunk:
```bash
git checkout -b release master
```
Then before we start the release process, we ensure all the branches we want to release (`topic` and `topic2`) are up to date with our trunk. Following [the second rule](#2---the-trunk-should-never-be-merged-into-a-branch), we do this with `rebase`:
```bash
git checkout topic
git rebase master
git checkout topic2
git rebase master
```
#### Rebasing
Now we need to get the commits from our topic branches, into the `release` branch. The `release` branch is a special branch, however, since it will eventually become a part of the trunk. This means we can also not merge to this branch either, as those commits will carry over into our trunk. So we instead use `rebase`. Using `rebase`, you can resolve commits from many sources into a linear history on a branch, without changing the topic branch.
```bash
git checkout release
git rebase topic2
git rebase topic
```
#### Resolving conflicts
Like doing a `merge`, a `rebase` can also have conflicts if two files are edited in different branches. This can cause some small inconvenience when creating a release branch. Let's pretend that the branches `topic` and `topic2` both edit a shared configuration file. When doing the release rebasing process, a conflict appears:
```
error: could not apply sha12345...
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply sha12345678901234567890... Changes configuration file in topic
```
From here is gives you two options to resolve: using `rebase --skip` or `rebase --continue`. Using `rebase --skip` is straightforward - it will skip this commit during the rebase. This would be useful if `topic` and `topic2` both had made the same change to the configuration file, thusly only adding one commit for it in the history.
Otherwise, this is the same situation as a regular `merge` conflict. Git will tell you the files in conflict, and uses the same notation in the files to show you where. Once you resolve all the conflicts, the following commands will continue the rebase along.
```bash
git add .
git rebase --continue
```
#### Cleanup
Once all topic branches have been rebased into the `release` branch, you should have the following structure.
```
H---I---J topic2
/
| E---F---G topic
|/
| E---F---G---H---I---J release
|/
A---B---C---D master
```
From here you can now do a fast-forward merge from `release` to `master` (also pruning the branches):
```
git checkout master
git merge release
git branch -D topic
git branch -D topic2
git branch -D release
```
to keep a linear history in your trunk branch:
```
A---B---C---D---E---F---G---H---I---J master
```
#### Updating topic branches
Let's pretend now that we had a thrid topic branch, `topic3`, that did not make it into this release. Giving us this structure:
```
K---L---M topic3
/
A---B---C---D---E---F---G---H---I---J master
```
After each release you want to ensure all branches follow [the first rule](#1---all-branches-are-based-off-the-trunk). In order to do this, `rebase` the `topic3` branch onto the updated trunk.
```bash
git checkout topic3
git rebase master
```
Any conflicts that need to be resolved become a part of the topic branch's history on a per commit level, as if it was always branched directly off the updated trunk. This keeps all changes sandboxed to their current branch. Now the structure should be:
```
K---L---M topic3
/
A---B---C---D---E---F---G---H---I---J master
```
#### Tagging
It should become a part of everyone's release strategy to use tags as snapshots of the trunk. This way it becomes a simple task to view the trunk at different parts of the project's lifespan. To tag the trunk, use the following commands:
```bash
git checkout master
git tag v1.0.0
git push --tags
```
## Managing Environments
In theory, the above example is the complete process for development. We have a trunk with topics that, through a release process, eventually become a part of the trunk codebase. In reality, we have environments to maintain with different versions of the code to test against. The important thing to remember is, while these branches must follow the rules, **they are not a trunk branch themselves**.
### Introducing topic branches
Since environment branches are not trunks, they are allowed merge commits. After each topic branch is ready to be introduced to an environment, you are free to merge the brach in normally. Resolving conflicts on the environment branch is also ok. These conflicts will be resolved again when you rebase onto the release branch later, but now you can be aware of potential problems.
### Reset, rebase, re-merge
The most difficult part of managing environments is updating them after a release. Assume the following structure, directly after a release:
where the `TX` commits indicate merges of topic branches into `develop`. These merge commits show that develop now has different history than the current trunk. Since environment branches are subject to [the first rule](#1---all-branches-are-based-off-the-trunk), you need to **reset** each environment branch to the trunk. Doing a `rebase` is not possible, because of the messy merge commits. The following will reset `develop` to the current trunk:
Now we have two problems. First, `topic3` is violating [the first rule](#1---all-branches-are-based-off-the-trunk). This can be fixed by performing a **rebase** on all remaining topic branches after every release. Now we have this structure:
Second problem, we are now missing code that used to be in develop: the commits from `topic3` were overwritten in the `reset`. To remedy this, we simply **re-merge** the topic branch back into develop. Giving us this final structure:
```
K---L---M topic3
/
| K---L---M develop
|/
A---B---C---D---E---F---G---H---I---J master
```
This needs to be repeated for all environment branches after each release. This ensures each branch follows trunk, instead of having its own unique history, to prevent environment specific issues.
## Tips and Tricks
Git can be made a lot easier with a proper environment setup, and some knowledge of how to perform some simple git magic.
### Git setup
Here is a .gitconfig file (located in `~/.gitconfig`) with some very useful aliases and configurations.
Here is a script to automatically reset all environment branches for multienvironment projects. This can be run after each release, then only in-progress branches will need to be updated, and re-merged.
```bash
#!/bin/sh
git fetch
# reset branches
git co develop
git reset --hard origin/master
git push -f
git co qa
git reset --hard origin/master
git push -f
git co staging
git reset --hard origin/master
git push -f
git co sandbox
git reset --hard origin/master
git push -f
git co master
```
#### Rewriting history
This [blog post by Thoughtbot](https://robots.thoughtbot.com/git-interactive-rebase-squash-amend-rewriting-history) gives many good examples on how to fix small mistakes in your git history.