Git installation and configuration

Installation

For ix distributions, Git should be in the standard repository.

The git-all package provides a complete Git working environment. Install it with:

$ sudo apt install git-all

To install only Git the git package suffices:

$ sudo apt install git

The bash autocompletion makes Git easier to use on the command line. The according package is called bash-completion. Install it with:

$ sudo apt install bash-completion

There are several different ways to install Git on a Mac. Probably the easiest way to do is to install the Xcode Command Line Tools. For this you only have to call up git in the terminal for the first time:

$ git --version

git-completion you can install with Homebrew:

Then you have to add the following line to the file ~/.bash_profile:

[[ -r "$(brew --prefix)/etc/profile.d/bash_completion.sh" ]] && . "$(brew --prefix)/etc/profile.d/bash_completion.sh"

Go to https://git-scm.com/download/win and start the download automatically. Further information can be found at https://gitforwindows.org/.

Configuration

Save the global configuration in ~/.config/git/

Git uses three levels of configuration files, which are applied in this order:

system

applies to all users on your computer, but it’s unlikely you’ll ever use this.

global

applies to all repositories of a single user and we will look at this in more detail here.

local

applies to a single repository and is only suitable for a few repository-specific options.

Git searches for a global configuration file in two places: ~/.config/git/config and ~/.gitconfig. The first location is the default location for configuration files while the second is the legacy option.

Note

On Linux machines, ~/.config can sometimes be a different path set by the environment variable XDG_CONFIG_HOME. This behaviour is part of the X Desktop Group (XDG) specification. You can get the other path with:

$ echo $XDG_CONFIG_HOME

If this does not result in anything, then your system will use ~/.config, otherwise it will use the path shown. For the sake of simplicity, we will only refer to ~/.config from now on.

See also

Since you can set options at multiple levels, you may want to keep track of where Git reads a particular value from. With git config --list [1] you can list all the overridden options and values. You can combine this with --show-scope [2] to see where Git is getting the value from:

$ git config --list --show-scope
system  credential.helper=osxkeychain
global  user.name=veit
global  user.email=veit@cusy.io

You can also use --show-origin [3] to list the names of the configuration files:

$ git config --list --show-origin
file:/opt/homebrew/etc/gitconfig        credential.helper=osxkeychain
file:/Users/veit/.config/git/config     user.name=veit
file:/Users/veit/.config/git/config     user.email=veit@cusy.io

Note

You can find a comprehensive example of a configuration file in my dotfiles repository: .gitconfig.

Migrate from ~/.gitconfig to ~/.config/git/config

If you are currently using the old file name ~/.gitconfig, you can move it to the ~/.config directory in just a few steps:

  1. Make sure that the ~/.config directory exists.

  2. Move your existing configuration file to its place:

    $ mv ~/.gitconfig ~/.config/git/config
    
  3. Check whether Git can still read the configuration file by asking for your user name:

    $ git config --global user.name
    Veit Schiele
    
  4. You may then have to move other files, for example ~/.gitattributes and ~/.gitignore. You can check whether these files are available with

    $ git config --global core.excludesFile
    ~/.gitignore
    $ git config --global core.attributesFile
    ~/.gitattributes
    

    You must then move the files and delete the associated configuration entries:

    $ mv ~/.gitignore_global ~/.config/git/ignore
    $ git config --global --unset core.excludesFile
    $ mv ~/.gitattributes ~/.config/git/attributes
    $ git config --global --unset core.attributesFile
    

Read and write configuration entries

As we have already seen above, configuration entries can be read with git config, for example:

$ git config --global user.name
Veit Schiele

… and to change it

$ git config --global user.name 'veit'

You can also edit the configuration file directly by calling git config with the -e|--edit option:

$ git config --global -e

This opens the ~/.config/git/config file in your default editor:

[user]
    name = veit
    email = veit@cusy.io

Git saves its configuration in INI files.

The default editor for Git is defined in the GIT_EDITOR environment variable or in Git’s core.editor option or in the VISUAL or EDITOR environment variable. You can query the values with

$ echo $GIT_EDITOR
$ git config core.editor
$ echo $VISUAL
$ echo $EDITOR

Normally you always want to use the same editor and therefore the EDITOR environment variable should be set. To do this, you can enter the following in ~/.bash_profile or ~/.zprofile, for example:

export EDITOR='C:\Program Files (x86)\Microsoft VS Code\code.exe --wait'

Note

On macOS, you must first start Visual Studio Code, then open the command palette with +-p and finally execute the Install ‘code’ command in PATH.

or

export EDITOR='vim'

Basic configuration

Git commits have two mandatory fields that refer to employees: the author who wrote the code change and the committer who submitted the code to the repository. For most workflows, this is the same person. With the options user.name and user.email you can configure information for the author and committer.

$ git config --global user.name "NAME"

defines the name NAME associated with your commit transactions.

$ git config --global user.email "EMAIL-ADDRESS"

defines the email address EMAIL-ADDRESS that will be linked to your commit transactions.

See also

Tip

Git hosts, such as GitHub or GitLab, link commits to your profile via the email address. If your configured email address does not match your profile, your commits will not be assigned. This makes it difficult for team members to determine that you have written a specific commit. Therefore, check your configured name and your e-mail address.

Alternative configuration file

You can use other configuration files for certain working directories, for example to distinguish between private and professional projects. You can use a local configuration in your repository or conditional includes at the end of your global configuration:

~/.config/git/config
[includeIf "gitdir:~/private"]
path = ~/.config/git/config-private

This construct ensures that Git includes additional configurations or overwrites existing ones when you work in ~/private.

Now create the file ~/.config/git/config-private and define your alternative configuration there, for example:

~/.config/git/config-private
[user]
    email = kontakt@veit-schiele.de
[core]
    sshCommand = ssh -i ~/.ssh/private_id_rsa

See also

Colouring

By default, Git uses your terminal’s ability to colour and format different types of text. Such colouring allows you to analyse the output more quickly. However, the default colours are suboptimal: for example, git status marks changed files in red, a colour generally associated with errors; however, changing files is not an error, but perfectly normal in any Git process. You can use the color.* options to adjust the colours per command. I have been using the cheat sheet colours for a long time:

[color "branch"]
    current = yellow reverse
    local = yellow
    remote = green

[color "status"]
    added = yellow
    changed = green
    untracked = cyan

Note

Later we will look at delta, a tool to better visualise differences. Its colouring would overwrite information from [colour ‘diff’] and therefore we have not added this section.

Correcting commands

If you make a mistake when entering a Git command, similar commands are listed by default and the programme is terminated:

$ git comit -m ':wrench: Update git config'
git: 'comit' is not a git command. See 'git --help'.

The most similar command is
    commit

However, you can also configure Git with git config --global help.autoCorrect immediate [4] so that the first hit is executed automatically:

$ git comit -m ':wrench: Update git config'
WARNING: You called a Git command named 'comit', which does not exist.
Continuing under the assumption that you meant 'commit'.
[main 48cafbf5f] :wrench: Update git config

However, Git only corrects automatically if a command has a sufficiently large match. If there are several potential matches, these are listed and the correction is cancelled:

$ git co -m ':wrench: Update git config'
git: 'co' is not a git command. See 'git --help'.

The most similar commands are
    commit
    clone
    log

If the automatic correction of an command is too much for you, you can use the Prompt mode instead:

$ git config --global help.autoCorrect prompt
$ git comit -m ':wrench: Update git config'
WARNING: You called a Git command named 'comit', which does not exist.
Run 'commit' instead [y/N]? y
[main 48cafbf5f] :wrench: Update git config

Pagination

You can activate pagination by default for a command by setting the corresponding option: pager.CMD = true. [5] For example, to switch git status to pagination:

$ git config --global pager.status true

Manage login data

Since Git version 1.7.9, the access data to git repositories can be managed with gitcredentials. To use this, you can, for example, specify the following:

$ git config --global credential.helper Cache

This will keep your password in the cache for 15 minutes. If necessary, the timeout can be increased, for example with:

$ git config --global credential.helper 'cache --timeout=3600'

With Linux you have to select a so-called: Credential Store. In most cases, you will opt for the Secret Service API, such as libsecret from Git, which you can select with:

$ git config --global credential.credentialStore secretservice

With macOS you can use osxkeychain to store the login information. osxkeychain requires Git version 1.7.10 or newer and can be installed in the same directory as Git with:

$ git credential-osxkeychain
git: 'credential-osxkeychain' is not a git command. See 'git --help'.
$ curl -s -O http://github-media-downloads.s3.amazonaws.com/osx/git-credential-osxkeychain
$ chmod u+x git-credential-osxkeychain
$ sudo mv git-credential-osxkeychain /usr/bin/
Password:
git config --global credential.helper osxkeychain

This enters the following in the ~/.gitconfig file:

[credential]
    helper = osxkeychain

Alternatively, you can also install the Git Credential Manager with

brew install --cask git-credential-manager

For Windows, Git Credential Manager (GCM) is available. It is integrated in Git for Windows and is installed by default. However, there is also a standalone Installer in Releases.

It is configured with

$ git credential-manager configure
Configuring component 'Git Credential Manager'...
Configuring component 'Azure Repos provider'...

This will add the [credential] section to your ~.gitconfig file:

[credential]
    helper =
    helper = C:/Program\\ Files/Git/mingw64/bin/git-credential-manager.exe

Now, when cloning a repository, a Git Credential Manager window opens and asks you to enter your credentials.

In addition, the ~/.gitconfig file is supplemented, for example by the following two lines:

[credential "https://ce.cusy.io"]
    provider = generic

The .gitignore file

In the .gitignore file you can exclude files from version management. A typical .gitignore file can look like this:

/logs/*
!logs/.gitkeep
/tmp
*.swp

In doing so, Git uses Globbing patterns, among others:

Pattern

Example

Description

**/logs

logs/instance.log, logs/instance/error.log, prod/logs/instance.log

You can put two asterisks to prefix directories anywhere.

**/logs/instance.log

logs/instance.log, prod/logs/instance.log but not logs/prod/instance.log

You can put two asterisks to prefix files with their name in a parent directory.

*.log

instance.log, error.log, logs/instance.log

An asterisk is a placeholder for null or more characters.

/logs
!/logs/.gitkeep

/logs/instance.log, /logs/error.log, but not /logs/.gitkeep or /instance.log

An exclamation mark in front of a pattern ignores it. If a file matches a pattern, but also a negating one that is defined later, it is not ignored.

/instance.log

/instance.log, but not logs/instance.log

With a preceding slash, the pattern only matches files in the root directory of the repository.

instance.log

instance.log, logs/instance.log

Usually the pattern match files in any directory.

instance?.log

instance0.log, instance1.log, but not instance.log or instance10.log

A question mark fits exactly on a character.

instance[0-9].log

instance0.log, instance1.log, but not instance.log or instance10.log

Square brackets can be used to find a single character from a specific range.

instance[01].log

instance0.log, instance1.log, but not instance2.log or instance01.log

Square brackets match a single character from a given set.

instance[!01].log

instance2.log, but not instance0.log, instance1.log or instance01.log

An exclamation mark can be used to find any character from a specified set.

logs

logs logs/instance.log prod/logs/instance.log

If no slash appended, the pattern fix both files and the contents of directories witch this name.

logs/

logs/instance.log, logs/prod/instance.log, prod/logs/instance.log

Appending a slash indicates that the pattern is a directory. The entire contents of any directory in the repository that matches the name – including all its files and subdirectories – are ignored.

var/**/instance.log

var/instance.log, var/logs/instance.log, but not var/logs/instance/error.log

Two Asterisks match null or more directories.

logs/instance*/error.log

logs/instance/error.log, logs/instance1/error.log

Wildcards can also be used in directory names.

logs/instance.log

logs/instance.log, but not var/logs/instance.log or instance.log

Pattern, that specify a particular file in a directory are relative to the root of the repository.

Git-commit empty folder

In the example above you can see that with /logs/* no content of the logs directory should be versioned with Git, but an exception is defined in the following line: !logs/.gitkeep allows the file .gitkeep to be managed with Git. The logs directory is then also transferred to the Git repository. This construction is necessary because empty folders cannot be managed with Git.

Warning

However, this technique has several disadvantages:

  • Both .gitignore and log/.gitkeep must be edited.

  • When renaming the directory, it is easy to forget to change the .gitignore file as well.

  • .gitkeep is a completely normal file for Git; however, the name suggests that the file would be treated specially by Git.

A better option is to create a .gitignore file with the following content in an empty directory:

# ignore everything except .gitignore
*
!.gitignore

excludesfile

However, you can also exclude files centrally for all Git repositories. For this purpose, you can set excludesfile in the ~/.config/git/config file:

[core]
    # Use custom ignore file
    excludesfile = ~/.config/git/ignore
    

Note

You can find helpful templates in my dotfiles repository or on the gitignore.io website.

Ignoring a file from the repository

If you want to ignore a file that has already been added to the repository in the past, you need to delete the file from your repository and then add a .gitignore rule for it. Using the --cached option on git rm means that the file will be deleted from the repository but will remain in your working directory as an ignored file.

$ echo *.log >> .gitignore
$ git rm --cached *.log
rm 'instance.log'
$ git commit -m "Remove log files"

Note

You can omit the --cached option if you want to remove the file from both the repository and your local file system.

Commit an ignored file

It is possible to force the commit of an ignored file to the repository with the -f (or --force) option on git add:

$ cat data/.gitignore
*
$ git add -f data/iris.csv
$ git commit -m "Force add iris.csv"

You might consider this if you have a general pattern (like *) defined, but want to commit a specific file. However, a better solution is usually to define an exception to the general rule:

$ echo '!iris.csv' >> data/.gitignore
$ cat data/.gitignore
*
!iris.csv
$ git add data/iris.csv
$ git commit -m "Add iris.csv"

This approach should be more obvious and less confusing for your team.

Troubleshooting .gitignore files

For complicated .gitignore patterns, or patterns that are spread across multiple .gitignore files, it can be difficult to figure out why a particular file is being ignored.

With git status --ignored=matching [6], an Ignored Files section is added to the output, showing all ignored files and directories:

$ git status --ignored=matching
On branch main
Ignored Files:
  (use "git add -f <file>...", to pre-mark the changes for committing
    .DS_Store
    docs/.DS_Store
    docs/_build/doctrees/
    docs/_build/html/
    docs/clean-prep/.ipynb_checkpoints/

    nothing to commit, working tree clean

You can use the git check-ignore command with the -v (or --verbose) option to determine which pattern is causing a particular file to be ignored:

$ git check-ignore -v data/iris.csv
data/.gitignore:2:!iris.csv  data/iris.csv

The output shows FILE_CONTAINING_THE_PATTERN:LINE_NUMBER_OF_THE_PATTERN:PATTERN FILE_NAME

You can pass multiple filenames to git check-ignore if you like, and the names themselves don’t even have to match the files that exist in your repository.

You can get a complete list of all ignored files with git ls-files --ignored --exclude-standard --others [7]. With --exclude-standard the standard ignored files are read and with --others the non-versioned files are displayed instead of the versioned ones:

$ git ls-files --ignored --exclude-standard --others
.DS_Store
_build/doctrees/clean-prep/bulwark.doctree
_build/doctrees/clean-prep/dask-pipeline.doctree
_build/doctrees/clean-prep/deduplicate.doctree

Occasionally you may want to bypass the global ~/.gitignore file to see which files Git always ignores, regardless of your configuration. You can do this by switching to another exclude option, --exclude-per-directory, which uses only the repository’s .gitignore files:

$ git ls-files --ignored --exclude-per-directory=.gitignore --others
docs/_build/doctrees/clean-prep/bulwark.doctree
docs/_build/doctrees/clean-prep/dask-pipeline.doctree
docs/_build/doctrees/clean-prep/deduplicate.doctree

Note that the .DS_Store file is no longer listed as ignored.

If you replace --others with --cached, git ls-files will list files that would be ignored unless they have already been committed:

$ git ls-files --ignored --exclude-per-directory=.gitignore --cached
data/iris.csv

You may have such files because someone added them to a .gitignore file before the relevant patterns, or because someone added them with git add --force. Either way, if you no longer want to manage the file with Git, you can remove it from Git management with the following one-liner, but don’t delete it:

$ git ls-files --ignored --exclude-per-directory=.gitignore --cached | xargs -r git rm --cached
rm 'data/iris.csv'