Git Configuration

Like any other tools, git can be configured.

You can script those configurations and store them at different places within configuration files.

This article answers the following questions:

(1) Why using git configuration? What is that useful for?
(2) What can I configure and how?
(3) What a configuration file might look like?

It will also present you with a use-case to store different git user profiles and there specific ssh keys.

 Why using git config?

Having git configurations stored in place allows you to (1) have a nice commit history with your user information displayed on Github or Gitlab’s UI. It is a way to reconciliate the user stored on the remote with the user stored on your local.

It provides also a (2) handy way to share configuration like git aliases (see this article).

Last but not the least, it allows you to (3) have different config (e.g. ssh and user identification) if you want for instance to switch users if you are multi-tenant (e.g. your ssh key to connect for Gitlab or Github might be different. Same for the email and username in used).

Anatomy of a configuration file

A git config file is a simple file. It can have different names depending on where it is located. Generally when located at the root, the name will be .gitconfig.

zsh> tail -n 3 ~/.gitconfig
[user]
    name = John Doe
    email = john.doe@gmail.com

The data in there are written like in a toml file.

A git config file has different sections. The default one is [core]. Another useful one is [user].

Alias are put under the alias section.

Here is an example of what a simple .gitconfig file might look like:

[alias]
    p=pull
    c=commit
    cm=commit -m
    s=status
    sw=switch
    a=add
    alias=!git config -l | grep alias | cut -c 7-
    cam=commit -am
    lo=log --oneline
    sc=switch -c
    rsm=rm -r --cached
    asm=submodule add
[user]
    name = John Doe
    email = "john.doe@gmail.com"

More here: https://git-scm.com/docs/git-config#_configuration_file

 The different configuration scopes (global, local)

By default, git config will read the configuration options from different git config files in that order:

(1) /etc/gitconfig
(2) ~/.gitconfig
(3) .git/config (at the repository level)
(4) .git/config.worktree (at the repository level)

They are also overwritten in that order. The local one taking precedence over the global configurations.

To read all the current configurations, you can either cat those files or simply run:

zsh> git config --list --show-scope --show-origin

Note: the show-origin argument will list where the config comes from.

If you want to see what are the specific values hold for the current config (depending on your pwd) you can use the get argument:

zsh> git config --get user.name
John Doe
zsh> git config --get user.email
john.doe@gmail.com

Note: depending on where you are located – i.e. the working directory – the value returned by the get command might change.

For instance, let’s say you have two git configuration files with different configuration in place:

zsh> pwd
~/my-workplace-folder

zsh> tree -a .
.
├── .git
    └── config
├── git-project-A
└── git-project-B

zsh> cat .git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[user]
    name = Jane Doe
    email = jane.doe@gmail.com

zsh> git config user.name
Jane Doe

zsh> git config --global user.name
John Doe

zsh> cat ~/.gitconfig
[user]
    name = John Doe
    email = john.doe@gmail.com

You can also edit the git config files via command lines:

zsh> pwd
~/my-workplace-folder

zsh> git config --local user.name "Baby Doe"
zsh> git config --local user.email "baby.doe@gmail.com"

zsh> tail -n 3 .git/config
[user]
    name = Baby Doe
    email = baby.doe@gmail.com

zsh> git config --get user.name
Baby Doe

zsh> git config --global --get user.name
John Doe

One important note when setting value via command line though:

If you add an equal e.g. git config --local user.name = "Baby Doe", the “=” char will be interpreted by the command as the string value passed to user.name and will ignore silently the rest of the line. You will end up with the following file:

ssh> cat .git/config                                                                                                                
[user]
    name = =

Thus, make sure to write this instead:

zsh> git config --local user.name "Baby Doe"

Use-case: different git user profiles

I have a gitlab/ folder and a github/ one.

zsh> tree .
├── github/
└── gitlab/

By default, I want my git config (user name and email) to be associated with gitlab.

However, I want those config to be overwritten when my working directory if under the github/ folder.

Note that because I want those two users to be able to ssh push and pull from the remote repositories, they both have a specific ssh key configured:

zsh> ls -la ~/.ssh
id_rsa_gitlab
id_rsa_gitlab.pub
id_rsa_gitlhub
id_rsa_github.pub

Note: you can create a new ssh key running:

zsh> sh-keygen -o -t rsa -C "gitlab@gmail.com"

Step 1 – in the global ~/.gitconfig file:

[user]
    name = "Gitlab User"
    email = "gitlab@gmail.com"
[includeIf "gitdir:~/github/"]
    path = ~/github/.gitconfig

Step 2 – in the github/ folder:

zsh> git init # create the .git folder
zsh> cat <<EOT >> .gitconfig
[core]
    sshCommand = "ssh -i ~/.ssh/id_rsa_github"
[user]
    name = "Github User"
    email = "github@gmail.com"
EOT
zsh> pwd
~/github/

zsh> git config user.name
Github User

zsh> cd ~/gitlab
zsh> git config user.name
Gitlab User

Note: withing the github/ folder, one must remain careful and keep in mind in case of overlapping issues that there is therefore now two git config files, one being .gitconfig and the second one being .git/config.

Git branch naming convention

When working on a project tracked with git, you will sure do create branches. You have the main branch of course, but then a good practice is also to have one branch per features you are developing. Below what it might look like at then end:

> git branch
* master
  dev/JIRA-1234
  dev/JIRA-5487_add_users_filtering
  dev/add_google_sign_in_authentication_form
  dev/ISSUE-987

Let’s have a closer look on how to name them (even though the above snippet already gives you a hint).

Main branch

The main or master branch is treated as the unique source of truth, the official working code base. This is a place where everything must be working. It is the default branch you come up with when you initialize a new git project.

Notes:

  • Since 2020 and 2022, Github and Gitlab (respectively) renamed their default branch from master to main to avoid language that might be seen as offensive.
  • If you do not want to politicize git and still prefer the old naming convention, you can still rename main for master manually. How to rename git branch explains how to do it.

Feature branches

It is time to add features into our main branch. Since main is the place where everything must be working, you first want to test your changes on a development branch. Usually, you end up with 1 branch per feature. Each of them templated as follow:

dev/JIRA-1234
dev/JIRA-1234_add_users_filtering
dev/add_google_sign_in_authentication_form
dev/ISSUE-987

In a nutshell, the following rules apply:

  • Prefix the non-main branch with dev/. It will makes easier to trigger the dedicated Gitlab CI jobs via branches filtering.
  • If you use an issue tracker like Jira, include the ticket’s ID. Gitlab includes a Jira Integration facility tool e.g. creating links to the Jira ticket on the fly.
  • Beside the ticket ID, stick to the good old snake_case. See also Why you should use Snake Case.
  • If you do not use an issue tracker e.g. for personal projects, simply describe the feature you are implementing.

git checkout vs. git switch

The difference between git checkout and git switch:

  • Both are used to perform actions on branches (create, delete, navigate)
  • git checkout is doing more than git switch
  • git switch has been created to be more simpler

In practice, I always use git switch instead of git checkout. But let’s explore some use cases.

git switch

To switch to an existing branch:

git switch <existing-branch>

To create a new branch and directly jump into it:

git switch -c <new-branch>

Notes:

git checkout

To switch to an existing branch:

git checkout <existing-branch>

To create a new branch and directly jump into it:

git checkout -b <new-branch>

To delete a branch:

git checkout -D <deleted-branch>

Note: in practice you won’t have to delete a branch manually. This will be done automatically after merging the Pull Request (on Github) or Merge Request (on Gitlab).

Last few words

We haven’t explored the full capacities provided by both methods. However, we have seen that both of them provides overlapping logics. To go in-depth, you can dive into the official documentation:

On more thing: we didn’t had the chance to stop and talk about git branch, a functionality allowing us to perform more actions on branches such as renaming a branch This will be done in another chapter (in progress).

Undo git add operation before commit

Simply use:

git restore --staged <file>...

The aforementioned command will unstage the files staged for commit you have accidentally added via git add. However, this relies on you not having committed already. If you have committed but still want to undo the operation, see (in progress).

Example

You have added a couple of files to be staged for commit:

git add .

Following the best practices, you always check the status after such operations:

git status

At this point, you notice that a couple of unwanted files have been added into the list of files to be staged for commit:

On branch dev/VERS-5928
Your branch is up to date with 'origin/dev/VERS-5928'.

Changes to be committed:
(use "git restore --staged <file>..." to unstage)

    modified:   .gitlab-ci.yml
    new file:   .gitmodules
    modified:   Makefile
    modified:   README.md
    new file:   project_foo/__pycache__/__init__.cpython-310.pyc
    new file:   project_foo/__pycache__/main.cpython-310.pyc
    new file:   project_foo/__pycache__/utils.cpython-310.pyc

In our example, you do not want push the last 3 files into the remote repository. The simplest solution is then to undo the git add operation so those files are no longer staged for commit:

git restore --staged project_foo/__pycache__/__init__.cpython-310.pyc

You can then repeat the operation for the last 2 remaining files.

However, even though this manual manoeuver is easily doable in our relative simple case, what about if in place of 3 files, way more files should be deleted?

In such case, the workaround to undo git add operations before commit for multiple files is as follow:

git restore --staged $(git diff --name-only --cached | grep "__pycache__")

Notes:

  • To undo the git add operation after commit, see (in progress).
  • To know more about the git restore command (in progress).
  • The difference between git reset, git restore and git rm (in progress).

Change the URL of a Git repository

What you have to do:

  1. On Github/Gitlab, change the url of (remote) repository via the UI.
  2. On your local repo, use git remote set-url origin <new-url> to replicate the changes and thus restore the bridge between the remote and local repo.
  3. Confirm the changes are now reflected, using git remote -v.

Example

You created a Gitlab repo called client-snapchat-api but you made a mistake. The name should be snapchat-api-client instead. Additional complexity: you have cloned the project on your local environment already.

Note: the name convention related to API clients is quite standard in the industry; the -client part always suffixing (and not prefixing) the client’s name i.e. name-of-the-api-client and not client-name-of-the-api.

First step is to perform the modifications on the remote repository; via Gitlab’s settings on the User Interface, you edit to your likings:

  1. The project’s name
  2. The project’s path via the Settings Advanced section.

Notes:

  • It is common for the repo’s URL to respect the kebab-case formatting. See also Why using snake_case to spotlight the differences.
  • It is common for the project’s name and the repo’s URL to be the same.

Second step (since you have already cloned the project on local) is to reflect those new remote settings in your local repository.

This because your local project still uses the old URLs as “origin” (i.e. the source) to track down, fetch (pull) and push the incoming and out-coming changes.

In other terms, your local repo is still linked to the old remote URLs. But those URLs are no longer attached to an existing remote Gitlab project since you have just changed them (depreciated):

> git remote -v
origin git@gitlab.com:obenard/old-project-in-kebab-case.git (fetch)
origin git@gitlab.com:obenard/old-project-in-kebab-case.git (push)

Notes:

  • The -v option is short for --verbose
  • The URLs for fetching and pushing (i.e. where you get the changes from and where you push the changes at) can be different.

The following image may help you: it’s like uprooting a tree and plotting it somewhere else. The leaves being the local repos instantiated by your developers and the root the remote repository (aka the unique source of truth). You need to rejuvenate the thing by reconnecting the leaves to the torso.

In our example, our local repo is pointing at the now depreciated URLs:

> git remote -v
origin git@gitlab.com:obenard/client-snapchat-api.git (fetch)
origin git@gitlab.com:obenard/client-snapchat-api.git (push)

To edit the URLs:

git remote set-url origin git@gitlab.com:obenard/snapchat-api-client.git

To see the result:

> git remote -v
origin git@gitlab.com:obenard/snapchat-api-client.git (fetch)
origin git@gitlab.com:obenard/snapchat-api-client.git (push)

Congratulations, your local project is now again linked with a valid remote Gitlab repository and can send or retrieve information from it.

Run the extra mile: URI, URL and URN

Before getting any further in polishing our 360° overview and have a full grasp over the concept, we need to understand the difference between URIs, URLs and URNs:

  • An URI – Uniform Resource Identifier – is an identifier, like the id primary key in a table or the social security number for a person. It is used to uniquely discriminate a resource e.g. a Gitlab repository (two Gitlab repositories cannot have the name URI since you won’t know which one you are referring to otherwise).

  • An URL – Uniform Resource Locator – is an URI but with the additional specificity of also being a locator. An URL allows you to locate and access a resource on the Internet e.g. a web page like https://olivierbenard.fr/how-to-create-git-aliases/. This page is unique across the Internet: you can not find the same URL anywhere else, and also, the URL is associated with a page you can navigate through.

  • Every URLs are URIs but there are URIs which are not URLs. For instance, URN – Uniform Resource Name – uniquely identifies a resource by a name in a particular namespace. It is a nice way to talk about a resource but without implying anything about its location or how to access it. URNs are intended to be unique across time and space e.g. the ISBN – International Standard Book Number – is a unique worldwide book identifier.

Note: speaking about ISBNs, you may find book recommendations to get started as Data Engineer in the related article What is a Data Engineer.

Now that the semantic is set, you will notice that I have lied to you 😱

I have indeed implied that we will have to change the URLs of a Git repository. This was an incorrect statement.

What we have done instead with the git remote set-url origin <new-uri> command was to change the URIs, not the URLs:

> git remote -v
origin  git@github.com:olivierbenard/olivierbenard.git (fetch)
origin  git@github.com:olivierbenard/olivierbenard.git (push)

This makes sense: if you have a closer look, you will notice that the git@github.com:olivierbenard/olivierbenard.git thingy does not lead anywhere. For good reason: it is a pure URI!

And for sure, the repo is located by the following https://github.com/olivierbenard URL.

Notes:

  • To be even more specific, the command is changing the URIs and then, Gitlab takes over, changing the URLs in the background.
  • The same result as with git remote set-url origin <new-uri> can be obtained directly by editing the ~/local-git-repo/.git/config file:

    [remote "origin"]
        url = git@github.com:olivierbenard/olivierbenard.git
        fetch = +refs/heads/*:refs/remotes/origin/*
    [branch "master"]
            remote = origin
            merge = refs/heads/master
    

And you, what’s your excuse for not having a green thumb? 🌱

How to create Git Aliases

There are two ways to create custom git aliases:
1. Using the Command Line Interface.
2. Directly editing the git config files.

We will have a look on both methods.

Via the Command Line Interface (CLI)

For instance, git status can be shortened into git s:

git config --global alias.s status

Note: in this example, we are configuring a git alias so git status and git s will be equivalent. Therefore, git status and git s will return the same output.

Editing one of the three git config files

  • .git/config (at your git local repository level)
  • ~/.gitconfig (global)
  • ~/.config/git/config (global)

Just add the following lines within the file, e.g. using vim or nano:

[alias]
    s = status

Notes:

  • If you only edit the git config file at your local repository level, the alias will only be accessible within your current git project.
  • If you set-up the alias at one of the global git config file, the alias will be usable across your overall local environment.

List all the alias you have defined

alias = "!git config -l | grep alias | cut -c 7-"

Note: the exclamation mark tells git that everything within the quotes will be a bash script, therefore it will gives us access to the usual bash commands like grep or cut.

Example of git aliases

Here is a couple of aliases you might find useful to replicate in your configuration:

[alias]
    c=commit
    cm=commit -m
    s=status
    sw=switch
    a=add
    alias=!git config -l | grep alias | cut -c 7-
    cam=commit -am
    lo=log --oneline
    sc=switch -c
    rsm=rm -r --cached
    asm=submodule add
    reinit=!git submodule update --init --recursive && git submodule update --remote

Why using git aliases

It simply makes your life easier since you do not have to type long commands anymore. You can simply call them using a shorter name.

After you have typed the same command again and again 10+ a day, you will start to love git aliases.

Did I hear someone say that software developers are lazy? 😈

How to rename git branch

To make it short, the main take-aways:

git branch -m <new-name>
git branch -m <old-name> <new-name>
git push origin -u <new-name>
git push origin -d <old-name>

Use case: local changes or project not yet existing on remote

To rename the current git branch:

git branch -m <new-name>

To rename any other git branches you’re not currently pointing at:

git branch -m <old-name> <new-name>

Tips:

  • The -m option is short for --move. That way it’s easier to remember.
  • Other custom-made shortcuts can be defined e.g. typing git b for git branch. Check How to create Git Aliases.

Use case: git project already deployed

If your git project is already deployed on a remote environment – e.g. Gitlab or Github – the beforementioned changes won’t be reflected though. To change the branches not only on local but also on the remote, you need to:

First, push the local branch and reset the upstream branch:

git push origin -u <new-name>

Then, delete the remote branch:

git push origin --delete <old-name>

Note: in that case, it is not necessary to use the git branch -m commands.

Use case: changing the capitalization

If you are only changing the capitalization – e.g. dev/vers-5395 becoming dev/VERS-5395 – you need to use -M instead of -m as Git will tell you that the branch already exists otherwise. This matters for users being on Windows or any other case-insensitive file systems.

Note: if you are wondering what might be a good branch naming convention, you can check the git branch naming conventions article.

Example

Github then Gitlab recently introduced changes to their main branch naming convention. By default, drifting from master to main. If you are a person used to the traditional way, you can restore the branch name, turning things more into your likings:

> git clone git@gitlab.com:username/your-project-in-kebab-case.git
> git branch
* main
> git push origin -u master
> git push origin --delete main

You can then check your changes:

> git branch
* master

Run the extra mile

If like me you’re still not 100% sure when it comes to git stash, rebase, forking and traveling back the history line, you can either:

But remember: it’s a marathon, not a sprint 🐢

Git commit message convention

To help you writing commit messages, there is a couple of guidelines, conventions and best practices one might follow. It can be summarized in 9 bullet points:

  • Start with the ticket’s number the commit solves.
  • Separate subject from body with a blank line
  • Limit the subject line to 50 characters
  • Capitalize the subject line
  • Add issue reference in the subject
  • Do not end the subject line with a period
  • Use the imperative mood in the subject line
  • Wrap the body at 72 characters
  • Use the body to explain what and why, not how

Example

VERS-1936: Add user access to datapool table

Access rights for the datapool resource have
been edited. Access is required to work on the
new outfit recommender system. Request has been
approved by the PO.

Please, note the following:

  • Access right is normally granted at the dataset level.
  • Due to pii data, access was granted at the table level.

Resolves: VERS-1936
See also: VERS-0107

Additional notes

When you commit, the Vim editor will be called by default. You will be then asked to prompt your commit message. The big strength of Vim is that it is installed by default on all systems.

If you don’t want to call Vim but rather use the command line, you can use the inline -m flag to write your commit message:

    git commit -m "Your message at the imperative voice"

If you still want to use a text editor to fill-in the commit message, you can change the settings so it opens Visual Studio Code IDE when the git commit command is called:

    git config --global core.editor "code --wait"

This requires you to have Visual Studio Code installed though.

You are now fully equipped to write the next bestseller! 📚

Acknowledgement