# Git Guide This document is meant to serve as a repository of best practices for large, multi-environment source control management. **Table of Contents** 1. [Introduction](#introduction) 2. [Rules of the Trunk](#rules-of-the-trunk) 3. [The Basic Process](#the-basic-process) 1. [Creating a topic branch](#creating-a-topic-branch) 2. [Releasing](#releasing) 1. [Preparation](#preparation) 2. [Rebasing](#rebasing) 3. [Resolving conflicts](#resolving-conflicts) 4. [Cleanup](#cleanup) 5. [Updating topic branches](#updating-topic-branches) 6. [Tagging](#tagging) 4. [Managing Environments](#managing-environments) 1. [Introducing topic branches](#introducing-topic-branches) 2. [Reset, rebase, re-merge](#reset-rebase-re-merge) 5. [Tips and Tricks](#tips-and-tricks) 1. [Git setup](#git-setup) 2. [Automated reset](#automated-reset) 3. [Rewriting history](#rewriting-history) ## Introduction 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: ``` K---L---M topic3 / | E---F---G---H---I---J---TX2---K---L---M---TX3 develop |/ A---B---C---D---E---F---G---H---I---J master ``` 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: ```bash git checkout develop git reset --hard master ``` giving us this structure: ``` K---L---M topic3 / A---B---C---D---E---F---G---H---I---J master, develop ``` 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: ``` K---L---M topic3 / A---B---C---D---E---F---G---H---I---J master, develop ``` 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. ``` [alias] co = checkout s = status clear = checkout -- . last-diff = diff HEAD~1 hist = log --pretty=format:'%C(auto)%h%C(reset) | %s%C(auto)%d%C(reset)' --graph dump = cat-file -p reset-head = reset --hard HEAD reset-trunk = reset --hard master root = ! git log | tail -n 5 | grep ^commit | cut -d ' ' -f 2 tip = ! git log | head -n 1 | cut -d ' ' -f 2 b = "!f() { git checkout -b $1 master; }; f" [push] default = simple ``` #### Automated reset 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.