Skip to content

Git

Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.

Git is easy to learn and has a tiny footprint with lightning fast performance. It outclasses SCM tools like Subversion, CVS, Perforce, and ClearCase with features like cheap local branching, convenient staging areas, and multiple workflows.

(source: https://git-scm.com/)

Installation

Windows 10/11

The most direct way to install Git on Windows 10/11, is to get the "Git for windows" 64 bits installer : https://git-scm.com/downloads/win. Then install it.

MacOS

Dedicated page for MacOS on the Git website: https://git-scm.com/downloads/mac

Linux

Ubuntu/Debian

Ouvrir un terminal Ctrl+Alt+T puis copier/coller les commandes :

1
2
sudo apt update
sudo apt install git

Fedora/Red Hat

1
2
sudo dnf update
sudo dnf install git git-all

Simplified workflow overview

The goal of this workflow is to allow users to work on a common project: sharing, and ensure that all previous work is still available at all time: versioning

Users Overview

  1. Create a Project in Gitlab. A distant Git repository is created as well during the project creation
  2. Clone the distant project on your computer
  3. Other users clone the distant repository as well to work on it in parallel
  4. Users can validate their work locally (add), then create a version (commit), then send their new version to the distant repository (push), or create branches (branch) when needed to isolate their work
  5. Other users can get those modifications (pull, fetch) to update their local repository and get others work.
  6. Locally solve conflicts when two users have done incompatible modifications.
Terminal
 

Zones

Different zones in GIT

  • The Working Tree is the local folder containing the files you are working on, such as source codes, documentation, images and so on.
  • The Staging (also referred as the Index) contains all the modifications you want to add to the repository.
  • The Local Repository contains all the versions of your validated modifications. Each version correspond to a Commit
  • The Distant Repository is another Git repository, often hosted on a dedicated web platform (Github, Gitlab...). It is used by all users working on a common project.

Local configuration

Some mandatory minimal configuration is needed to use Git on your computer: setting your name and your email address:

1
2
git config --global user.name "John Doe"
git config --global user.email johndoe@example.com

Display your configuration :

1
git config --list --show-origin

For more detailed documentation: https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup

Basic Commands

Clone a repository

To clone a remote repository, use the command:

1
git clone <distant repo URL>

For more detailed documentation: https://git-scm.com/docs/git-clone

See status

To see the current status of your Git repository, use the command:

1
git status

It will show you the following information:

  • The branch on which you are (see later in branching)
  • The synchronization status with the remote branch
  • The changes staged for commit (i.e every changes you added in the index/stage with git add)
  • The changes not staged for commit
  • The untracked files (never added to any commit of the history)

Tip

This command can be done at any point during you normal git process. As it does not modify the repository and give you crucial information on the repository current state, you can never overdo it.

For more detailed documentation: https://git-scm.com/docs/git-status

Add modifications

To add modification in the index/stage, use the command:

1
git add <filename 1> <filename 2>

For more detailed documentation: https://git-scm.com/docs/git-add

Commit with a message

To create a commit (version) after adding modifications to the index, use the command:

1
git commit -m "my useful message indicating what this commit consist of"

The -m option stands for "message"

Note

A commit message is very important. It should indicates what this commit changes on the project, in a clear an concise way

For more detailed documentation: https://git-scm.com/docs/git-commit

Push to Remote

To send the modifications (i.e. the commits), use the command:

1
git push origin main

Parameter

Description

origin

Distant repository, visible with git remote -v

main

Name of the branch you want to push modification
main is the default name of the branch created
when creating a project in gitlab

For more detailed documentation: https://git-scm.com/docs/git-push

Syncing and Updating

In case another developer pushed modifications on the distant repository, it is necessary to get these modifications locally before push local modifications. There is two ways to do it:

  • with fetch: it will update the local repository, but not the working directory (i.e. the files). To then update the working directory, merge is used.
  • with pull: it will update the local repository and the working directory. It may induce conflicts that the must be resolved locally.

For more detailed documentation about the difference: https://www.theserverside.com/blog/Coffee-Talk-Java-News-Stories-and-Opinions/Git-pull-vs-fetch-Whats-the-difference

Fetch

To fetch, use the command:

1
2
git fetch
git merge origin main # To update working directory

For more detailed documentation:

Pull

To pull, use the command:

1
git pull

Tip

It is advised to:

  • pull/fetch+merge before doing modifications or a push
  • commit before pull/fetch+merge

For more detailed documentation: https://git-scm.com/docs/git-pull

Initializing and Connecting Repositories

Create a local repository

You can easily create a Git repository anywhere (except in an existing one) on your computer with the command:

1
2
cd /path/to/your/working/directory
git init

For more detailed documentation: https://git-scm.com/docs/git-init

Adding a remote (distant) repository

When your init a repository, you cannot, as is, interact with a distant repository, for example on Gitlab. To add a remote repository, use the command:

1
git add remote <project name> <project URL on gitlab>

A local repository can have several remote repository. To list them with detailed information, use the command:

1
git remote -v

For more detailed documentation: https://git-scm.com/docs/git-remote

Merge and Conflict Resolution

merge is used to merge two different histories. Here we will cover classic conflicts.

Sometimes, conflicts happen. It can be because the commit you want to push to the remote repository is behind its last commit without conflict inside the code itself. The pull to get the last commit will automatically "fast forward" and merge the modification between the last distant commit and your commit. The you can push.

Sometimes, the conflict is inside the code itself. Two contributors made modifications on the same file at the same lines. The pull to get the last commit will signal that there is a conflict of this type, but will still 'fetch' the remote commit into your local repository, but without modifying your working directory.

For more detailed documentation: https://git-scm.com/docs/git-merge

pull often, then push often

The more often you pull, the less your local repository history diverges from the original repository when you push, and the fewer conflicts you have to resolve.

Tip

Talk to your colleague to avoid conflicts. The better you share tasks, the easier it is to manage git repositories.

The following Git commit history show how commit made by different users are synchronized using the Gitlab repository.

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'GitlabRepo'}} }%%
gitGraph:
   commit id: "Initial"
   branch Alice
   branch Bob
   commit id: "Modif1"
   checkout Alice
   commit id: "Modif2"
   checkout GitlabRepo
   merge Bob tag: "push Modif1"
   checkout Alice
   merge GitlabRepo tag:"merge Modif1"
   checkout GitlabRepo
   merge Alice tag: "push Modif2"
   checkout Bob
   commit id: "Modif3"
   merge GitlabRepo tag:"merge Modif2"
   checkout GitlabRepo
   merge Bob tag: "push Modif3"
   checkout Alice
   merge GitlabRepo tag:"merge Modif3"

One user must merge into his local repository the commit pushed in the meantime to the origin repository by other users before being able to push his own commits:

Commit history propagation

Typical message from git when trying to push in this situation:

1
2
3
4
5
6
7
8
9
git push origin main
    To gitlab-df.imt-atlantique.fr:jnbazin/pronto-2.git
     ! [rejected]        main -> main (fetch first)
    error: failed to push some refs to 'gitlab-df.imt-atlantique.fr:jnbazin/pronto-2.git'
    hint: Updates were rejected because the remote contains work that you do not
    hint: have locally. This is usually caused by another repository pushing to
    hint: the same ref. If you want to integrate the remote changes, use
    hint: 'git pull' before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Then, when doing the pull, you will have to make a decision:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
git pull
     remote: Enumerating objects: 5, done.
     remote: Counting objects: 100% (5/5), done.
     remote: Compressing objects: 100% (3/3), done.
     remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
     Unpacking objects: 100% (3/3), 410 bytes | 136.00 KiB/s, done.
     From gitlab-df.imt-atlantique.fr:jnbazin/pronto-2
        c74e76e..f8a1826  main       -> origin/main
     hint: You have divergent branches and need to specify how to reconcile them.
     hint: You can do so by running one of the following commands sometime before
     hint: your next pull:
     hint:
     hint:   git config pull.rebase false  # merge
     hint:   git config pull.rebase true   # rebase
     hint:   git config pull.ff only       # fast-forward only
     hint:
     hint: You can replace "git config" with "git config --global" to set a default
     hint: preference for all repositories. You can also pass --rebase, --no-rebase,
     hint: or --ff-only on the command line to override the configured default per
     hint: invocation.
     fatal: Need to specify how to reconcile divergent branches.

Most of the time (and for this little documentation), the solution is to merge.

If there are no conflicts in the files (i.e two commits that modify the same lines in the same file):

1
2
3
4
git merge ## will ask you to write a commit message
    Merge made by the 'ort' strategy.
     calculator.py | 16 +++++++++++++---
     1 file changed, 13 insertions(+), 3 deletions(-)

If there are conflicts during the merge :

1
2
3
4
git merge
    Auto-merging calculator.py
    CONFLICT (content): Merge conflict in calculator.py
    Automatic merge failed; fix conflicts and then commit the result.

You have to resolve the conflicts manually. Git cannot know what to do with your code. But it will tell you where there are conflicts and highlight them directly in your code:

1
2
3
4
5
<<<<<<< HEAD
Your local code
=======
The remote code that is in conflict with
>>>>>>> refs/remotes/origin/main

Info

All the conflicts are resolved locally. Git impose you to synchronize your local repository history with the remote one before doing a push

Once you resolved the conflicts, a classic add, commit and push should do the trick.

Branching

Until now, we presented a simple workflow where all users are working on the same branch of the code (e.g called main or master). That branch is the succession of commits they made, which are all appended together to get the current version of the code at any time.

You can however decide to create multiple branches, each with their respective commit histories and hence code version. Though it may appear counter-intuitive at first glance, it is often preferrable to create a new branch on which you will be the only one to work on, generally for the purpose of developping a new feature or a bugfix, as you won't encounter any synchronization issue. This allows you to work quickly on your development, reducing the hurdle of constant synchronization with the remote adding potential conflicts, and only integrate your changes in the main/master branch in a completed state.

Note

This section merely presents the git commands you can use but does not extend to the question of when/why to use each of those. This is done in the following guide on Collaborative Git Workflows when discussing Merging Strategies.

Create a Branch

To create a new branch, use the command:

1
git branch <branch name>

This Git commit history shows the creation of a branch named feature from the main branch:

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true}} }%%
gitGraph:
   commit id: "Initial"
   commit id: "Modif1"
   commit id: "Modif2" type: HIGHLIGHT tag: "HEAD"
   branch feature
   commit id: "feature: Modif1"
   commit id: "feature: Modif2"

For more detailed documentation: https://git-scm.com/docs/git-branch

Change branch

The former command creates a new local branch (unsynced), but don't place you on it. To change the current version of your repository, you can use the command:

1
git checkout <branch name>

You can see the checkout command in action, to go on the newly created feature branch from last example:

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true}} }%%
gitGraph:
   commit id: "Initial"
   commit id: "Modif1"
   commit id: "Modif2"
   branch feature
   commit id: "feature: Modif1"
   commit id: "feature: Modif2" type: HIGHLIGHT tag: "HEAD"

Tip

You can create a new branch and place yourself on it in one command:

1
git checkout -b <branch name>

Note

The checkout command can do many more operations that change the current branch. You can also checkout a particular commit (given its SHA code).

For more detailed documentation: https://git-scm.com/docs/git-checkout

Merge branches

This command merges the given branch inside your current branch:

1
git merge <branch name>

There are multiple methods for this command to merge two branches:

  • fast-forward: the command will try to reconcile the branches' histories by collapsing the given branch commits inside the current branch, deleting all traces of the branching in the process
  • three-way merge: the command will create a merge commit reconciling both branches, this commit will depend on both branches latest commits

Let's show a fast-forward merge of the feature branch on the main branch in last example:

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true}} }%%
gitGraph:
   commit id: "Initial"
   commit id: "Modif1"
   commit id: "Modif2"
   commit id: "feature: Modif1"
   commit id: "feature: Modif2" type: HIGHLIGHT tag: "HEAD"
Now let's show a three-way merge as a comparison:
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true}} }%%
gitGraph:
   commit id: "Initial"
   commit id: "Modif1"
   commit id: "Modif2"
   branch feature
   commit id: "feature: Modif1"
   commit id: "feature: Modif2"
   checkout main
   merge feature id: "Merge feature" type: HIGHLIGHT tag: "HEAD"

By default, the command will try a fast-forward merge, if the current branch is a direct descendant of the given branch (i.e no commits were made on the current branch between branching & merging). Otherwise, it will perform a three-way merge.

When used without a branch name argument, the command will assume the branch to merge is the remote version of the current branch (i.e origin/main for main). This a usage of the command you already saw in Merge and conflict resolution.

If any conflict occurs during the merge operation, it will prompt you to perform Conflict Resolution.

For more detailed documentation: https://git-scm.com/docs/git-merge

Rebase a branch (advanced)

This command rebase the current branch on the given branch, so its commit history is pushed after the given branch latest commit:

1
git rebase <branch name>

Warning

This command will override the commit history on the current branch. This should never be done on shared branches, as it will necessarily pose conflicts to every user pulling the branch or pushing commits depending on the branch before reconciliation.

You can see the result if we use rebase the main branch on the feature branch in this example:

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true}} }%%
gitGraph:
   commit id: "Initial"
   commit id: "Modif1"
   branch feature
   commit id: "feature: Modif1"
   commit id: "feature: Modif2" type: HIGHLIGHT tag: "HEAD"
   checkout main
   commit id: "Modif2"
   commit id: "Modif3"
We will get the following after rebasing feature on main:
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true}} }%%
gitGraph:
   commit id: "Initial"
   commit id: "Modif1"
   commit id: "Modif2"
   commit id: "Modif3"
   branch feature
   commit id: "feature: Modif1"
   commit id: "feature: Modif2" type: HIGHLIGHT tag: "HEAD"

When pushing your rebased branch, you will encounter the following error:

1
2
3
4
5
6
7
8
git push origin feature
    To gitlab-df.imt-atlantique.fr:jnbazin/pronto-2.git
    ! [rejected]        feature -> feature (non-fast-forward)
    error: failed to push some refs to 'gitlab-df.imt-atlantique.fr:jnbazin/pronto-2.git'
    hint: Updates were rejected because the tip of your current branch is behind
    hint: its remote counterpart. Integrate the remote changes (e.g.
    hint: 'git pull ...') before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Contrarily to what's suggested in the message, the problem doesn't stem from an outdated local branch but from the rewritten commit history. You will need to force the push operation so the server will accept this new state of the branch:

1
git push -f <remote name> <branch name>

As for merging two branches, conflicts may occur when incompatible commits are encountered. Though a rebase will ask you to resolve conflict for each affected commit in succession. You can then fix each conflict in the individual files, before calling git add (for the conflicted file you fixed), then git rebase --continue to continue the process.

Tip

The general usage of the rebase operation, is to reconcile your current branch with the main branch before merging them. This way the merge operation will be trivial, and the commit history will appear clean (all your branch commits appearing as a sequence at the end). You can therefore apply a rebase to allow for a fast-forward merge later, even if the target branch was changed since you branched:

1
2
3
4
git checkout feature         # Move on your "feature" branch
git rebase main              # Rebase the "feature" branch on "main"
git checkout main            # Move on the "main" branch
git merge feature --ff-only  # Merge "feature" branch with "main" in fast forward
You need to perform those operations quickly though (and push the result), if you don't want another contributor to push new commits on the main branch in the meantime, forcing you to resync your main branch and start again.

For more detailed documentation: https://git-scm.com/docs/git-rebase

Remove a branch

This command removes a branch (cannot remove the one you are currently on):

1
git branch -d <branch name>

Note

This will only remove the local version of the branch. The remote version will still be present. You can also remove it with the command:

1
git branch -d -r <remote name>/<branch name>

For more detailed documentation: https://git-scm.com/docs/git-branch

Customizing a Repository

Git repositories can be customized via multiple files

Gitignore file

Whenever you are working as a team on a repository, your various tests/tool/project executions will produce artifacts (generated files present in your working directory). You should never include unnecessary files in your repository, as at best they are wasting space, at worst they may create unncessary conflicts.

At the same time, every file that can be generated by your source files should not be added to the repository. They can be generated on demand locally, or on the server via a CI/CD pipeline.

Git has a handy file for that, .gitignore, which you should put at the root of your project (i.e in the folder containing the .git/ folder). It's a blacklist of files/folders and directory patterns to ignore when adding files to stage/index. This is what such file can look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Mac/OSX
.DS_Store

# Windows
Thumbs.db

# Virtual environment files
venv/

# Python precompiled module
*.pyc

# Documentation build files
docs/_build/

# Project builds
dist/

Tip

This file can be added to the repository, so every coworker will have the same version of it. Ensuring coherency inside the project in the files ignored by Git.

For more detailed documentation: https://git-scm.com/docs/gitignore

Hooks

Hooks are commands that are called when performing various git operations. They can be called before or after the operation, and are located in .git/hooks/. To create a hook, simply create an executable file (for example a bash file with executable permission on Linux), and place it in the directory with the name of the hook it should be called upon.

For example, a handy hook when working on a clean project is the pre-commit hook. You can write it so it blocks any commit with a messy state (see pre-commit applying linters), forcing you or at least informing you when you are performing such messy operation.

Tip

The hooks are located in the .git/ directory, which cannot be added to the repository as it contains the repository information and utilities. When creating and sharing a hook with your coworkers, add it in the project root directory (or a hooks/ directory) and document how to activate it (i.e copying it to .git/hooks/).

For more detailed documentation: https://git-scm.com/docs/githooks

Best practices

Sources