git

git is a very powerful distributed version control system allowing people to track changes to and work together on all kinds of (text)files. Alternative free and open-source version control systems are Fossil, Darcs and Mercurial.

This wiki will NOT cover the very basics of git. Please read the first two chapters of the freely available ProGit book to for an introduction to git.

Writing clean commits

When creating commits, try to follow this advice1:

  1. Write good commit messages. Apart from following the correct formatting (header + body + commit trailers), make sure that your commit message is informative. Keep the description of what your changes do short (make sure the code is as self-explanatory as possible) and rather focus on why you chose to implement things this way rather than another one.
  2. A commit should be a self-contained change (i.e. atomic) and represent a unit of work (feature, bug fix, etc.). Avoid clumping separate things together or drawing out the implementation of the same feature of multiple commits. Ideally, every single commit in the history of your project would compile and pass the tests.
  3. Follow the standards of the project you are working on: While all of us might have personal preferences, it aids collaboration if everyone plays by some agreed upon rules. Use pre-commit hooks extensively, if that aids you.

Specifying revisions

Pointing to specific commits can be done in a myriad of ways in git.

Some examples for the most common use cases:

git checkout <branchname>
git checkout <commit hash>
git checkout <tag>
git checkout HEAD^{n}  # n-th parent of HEAD (remember: commits can have multiple parents)
git checkout HEAD~{n}  # n-th generation ancestor following first parent

Specifying ranges

Just like for individual revisions (i.e. commits), git also has multiple ways for specifying commit ranges since there are git commands which operate on commit ranges (e.g. git log).

When specifying ranges, the notion of what is reachable from a given revision is central:

A commit’s reachable set is the commit itself and the commits in its ancestry chain.

Some common syntax cases for specifying ranges include:

git log <rev>  # include commits that are reachable from <rev>
git log ^<rev>  # exclude commits that are reachable from <rev>
git log <rev1>..<rev2>  # include commits that are reachable from <rev2> but exclude those that are reachable from <rev1>
git log <rev1>...<rev2> # include commits that are reachable from either <rev1> or <rev2> but exclude those that are reachable from both.

Storing executable bits

Git has the ability to track executable bits (not read/write permissions though).

On POSIX systems, git picks up changes to the executable bit from the filesystem and those "changes" to the file can be commited as usual. On Windows, you need to use the

git add --chmod +/-x <executable>

crutch.

For more details, see here.

Inspecting diffs

git diff and git diff --cached give you the changes in the working directory or index, respectively.

Since git by default diffs line-by-line, this can be a problem if you're working with very long lines. To view the changes on a character level, adapt the git diff call thus:

git diff -U0 --word-diff-regex=.

To follow the diffs from commit_a to commit_b in chronological order, use:

git log --reverse -p commit_a_hash...commit_b_hash

git bisect

git bisect is a powerful tool to find commits which introduced regressions. Basically, you tell git two things: a good commit where a feature worked as expected and a subsequent bad commit where the feature is broken; somewhat like this:

git bisect start
git bisect bad  # assuming the currently checked out commit has the bug
git bisect good <pointer_to_good_commit>  # a commit where you know that the bug is not present

git will then take a commit in the middle of that range and ask you to tell it whether the bug exists at this point in the git history. If you indicate that the bug exists with

git bisect bad

git will choose a commit between the initial good commit and the current commit. Should you indicate that the bug does not exist with

git bisect good

git will checkout a commit between the current commit and the initial bad commit. This process of checking a particular commit and passing the feedback to git bisect repeats until git bisect can single out the one commit which introduces the breakage.

For more information on git bisect, have a look at the documentation.

Branch management

Delete remote branch

Run

git push <remote name> --delete <branchname>

to delete the branch <branchname> in the remote <remote name> repository.

Searching the commit history

This wiki section will focus on very specific use cases of working with the git commit history. For more general information on viewing the git commit history, refer to chapter 2.3 of the ProGit book.

List all authors in repository

To list all authors in a repository with commit count and email, run

git shortlog -e -s -n

Add --no-merges to the command options to exclude merge commits from the statistic.

Finding deleted files

Sometimes you want to find information on or restore files that have been deleted from the repository in the past. Obviously, those are not around anymore in the working directory with the checked out files, but the history of the deleted files is still preserved in the git repository.

Now, the difficulties start with finding the deleted file, e.g. maybe you don't remember the full path to the file. Run

git log --diff-filter=D --summary

to show all commits which have deleted files.

If you know the filename, simply use

git log -- path/to/file

to show all commits for that file.

To then restore the file from a specific revision, run:

git checkout REVISION -- path/to/file

Working with patches

Before software forges like Microsoft Github or Gitlab have made the Pull Request based collaboration workflow popular, developers used to collaborate with git by sending around patches via email.

Creating a patch

To create one/or multiple patches, run

git format-patch <single commit identifier>

to create patches for all commits since the specified commit. Alternatively, you can run

git format-patch <revision range>

to create patches for all commits in the specified revision range. For a single commit use

git format-patch -1 <single commit identifier>

See the documentation page for more information.

Applying a patch

If someone has sent you a .patch file, you can apply it to the HEAD of the current repository in different ways:

If you want to apply the patch to your index without commiting yet, run:

git apply <path/to/patchfile>

More often, however, you will want to create a commit from the patchfile which preserves the original author information. To do so, run:

git am <path/to/patchfile>

If the patch does not apply, you can either ask the submitter to fix the patch or try to fix it up yourself. If you decide to do it yourself, you can use

git apply -3 <path/to/patchfile>  # requires you to have the commit the diff in the patch is applied to locally

or

git apply --reject --whitespace=fix <path/to/patchfile>

While the former command will create a merge with conflict markers, the latter will break the commit into hunks, apply those that apply cleanly, and store the rejected hunks in .rej files (which you can then fix manually or using wiggle).

Mailing a patch

In short,

git send-email --to="mailing-list@project.org" COMMIT_OR_REVISION_RANGE

Please make sure to use git send-email to mail around patches as other tools tend to break the patch format.

For more details on the installation and configuration of git send-email please consult this excellent guide.

Submodules

Sometimes it happens that you need the files from one (git-versioned) project within another. To ease administering local and upstream changes of the included project, git has the concept of submodules.

adding submodules

To add a submodule to your project, run:

git submodule add <SUBMODULE_REPO>

and commit the changes to the .gitmodules file and the newly checked out folder to the parent project repository.

cloning projects with submodules

The normal git clone command will not fetch and checkout the submodules' contents. To include those, run

git clone --recurse-submodules <REPO_URL>

If you have forgotten to specify the --recurse-submodules option, you can load the submodules retroactively by

git submodule update --init --recursive

working with submodules

How you use the submodule will drive the way you interact with it in git. Please refer to the "Working on a Project with Submodules" section in the submodules documentation which outlines different use cases.

git and the web

While one can use git locally on a single machine to track changes to files over time, the tool itself was built in a way to allow collaborating with others over the internet.

web frontends

forge software with git support

See also


  1. Slightly modified from this blog post of Drew Devault