Git is a source code management tool designed for managing the development of software (Private) projects, originally built to replace BitKeeper for Linux kernel development. Its key innovation is the use of a DAG to represent relationships between commits.

Git is distributed: all repositories are a complete copy of the state of the repository at the time they fetched changes. There is no requirement for a canonical, centralised repository: the Linux kernel uses a series of forks for development and merging, with changes gradually flowing between maintainer forks until they reach Linus's tree.


  • Repositories are the root.
  • Commits make up the history, describing the patches included in each file in the change set.
  • References represent positions or entries in the history:
    • Tree-ish can be dereferenced to a tree.
    • Commit-ish can be dereferenced to a commit.
    • Branches represent places where the history diverges.
    • Tags represent release versions of software.
  • The Staging area contains files staged for the following commit.
  • The Working tree represents our working copy of the files.
  • Remotes are other repositories.
  • HEAD represents the location of the checkout.

Configuration levels

Git configuration can be stored at the following levels, with the most specific winning:

  • System configuration is typically stored in /etc/gitconfig and covers all users.
  • Global refers to user configuration, usually at ~/.gitconfig.
  • Repository configuration is stored in .git/config.
  • Worktrees each have their own .git/config file.
  • Specified file with the --file or --blob switches.


Git has multiple merge strategies:

  • Fast forwards can be applied when the mergee shares a common base with the destination branch. This can be achieved via a rebase if necessary.
  • Merge commits are required when there's no common base, or the contents of the objects would be in conflict.

Merge commits have two parents, one for each of the conflicting histories.

Merging a branch that already contains a merge commit resolving a conflict back into one of the branches that initiated the conflict is a fast-forward operation: since the content is the same, there's no need to create another object.

Detaching your HEAD

Git allows you to directly checkout a specific commit, leading to a detached HEAD state. When doing this, the HEAD file's ref value will point directly at a commit. This even allows committing: the HEAD moves with each commit.

If we don't checkout a new branch based on these commits, the dangling commits will eventually be pruned by git gc.


All clones are a complete copy of the repository's history: they're equal peers from a technical perspective.

Remotes are stored in .git/config:

[remote "origin"]
url =
fetch = +refs/heads/*


Git's CLI is split into "porcelain" and "plumbing" commands. The former at intended for user interaction, the latter expose Git's internals.

  • git init
  • git remote add name url
  • git clone
  • git switch my-branch
  • git switch -c new-branch
  • git rebase origin/master
  • git rebase -i origin/master
  • git log
  • git branch -v
  • git branch new-branch
  • git merge
  • git fetch
  • git push
  • git pull
  • git merge-base feature-branch base-branch

git grep across branches and tags

git grep searches all tracked objects for the given pattern, and can be useful in software archeology. This command searches all local revisions rather than just those in the current branch's history:

git rev-list --all | (while read rev; do git grep Helm $rev; done)

You can narrow down the search space by only checking branches, tags, etc.:

git rev-list --branches | (while read rev; do git grep Helm $rev; done)

The following alias in ~/.gitconfig reduces this to git grep-all branches Helm:

    grep-all = "!f() { local type="--${1}"; shift; git rev-list $type | (while read rev; do git grep "$@" "$rev"; done); }; f"


Line endings on Windows

There are two different characters, CR and LF, that can be used as EOL characters to break a line. Naturally there's little consistency to their use:

  • Linux, macOS and other POSIX systems use LF.
  • Windows uses CRLF.
  • Older versions of macOS used CR.

To compensate for this Git provides a tool for conversion of line endings on platforms, exposed via the core.autocrlf option, which has three possible values:

  • input means "use the files as they appear in the repository".
  • true enables converting to CRLF on checkout and LF on commit.
  • false means leaving them as they appear in the checked out files.

Using Meld as merge tool

First of all, install Meld; on Windows using their binaries, on Linux using your package manager.

Configure Git to use it, on Linux:

git config --global diff.tool meld
git config --global merge.tool meld

On Windows:

git config --global difftool.meld.path 'C:\Program Files (x86)\Meld\meld\meld.exe'
git config --global diff.tool meld
git config --global mergetool.meld.path 'C:\Program Files (x86)\Meld\meld\meld.exe'
git config --global merge.tool meld

Have pull only apply fast-forwards

You can force git pull to only allow a merge with the fast forward merge strategy:

git config --global pull.ff only


Make pull and fetch automatically update submodules:

git config --global submodule.recurse true

Interactive ergonomics

Setting interactive.singleKey to true removes the need to press Enter> in a bunch of scenarios, particularly during staging changes in --patch mode. Note that it requires an otherwise optional dependency: Perl's Term::ReadKey.

  1. Attributes
  2. Bisect
  3. Flow
  4. Hooks
  5. Ignore
  6. LFS
  7. Object database
  8. Rebasing
  9. Staging from diffs
  10. Submodules
  11. Subtrees
  12. Worktrees
  13. git-crypt