Skip to content

Instantly share code, notes, and snippets.

@eashman
Forked from arempe93/git.md
Created January 25, 2019 16:43
Show Gist options
  • Select an option

  • Save eashman/09716520691fc2ec6b9c15b93e16614d to your computer and use it in GitHub Desktop.

Select an option

Save eashman/09716520691fc2ec6b9c15b93e16614d to your computer and use it in GitHub Desktop.

Revisions

  1. @arempe93 arempe93 created this gist Jul 25, 2018.
    314 changes: 314 additions & 0 deletions git.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,314 @@
    # 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.