Indispensable à savoir

Index

L’index est une zone qui permet de préparer un commit.

HEAD est la référence sur le commit sur lequel on se trouve actuellement. On le change avec git checkout.

Rebase vs Merge

Préférer le rebase pour garder un historique propre

Rebase

Eviter git pull pour éviter que Git ne considère notre branche locale et remote comme 2 branches différentes alors que ce sont les mêmes.

Faire un rebase:

1
2
3
git pull --rebase 
ou
git rebase origin/master

Les commits qui n’existaient que sur la branche master sont supprimés et réappliqués/ rejoués à la suite des commits de la branche origin/master.

En rebasant vos branches avant de les fusionner, vous obtiendrez un historique tout plat et bien plus agréable à parcourir.

Exemple 1:

1
2
3
4
5
          F---G ← bug2
/
A---B---E---H---I ← master
\
C---D ← bug1

En utilisant un rebase avant chaque fusion, on obtient l’historique suivant :

1
A---B---E---H---I---C---D---F---G ← master

Les commandes pour parvenir à ce résultat sont les suivantes, explications juste après.

1
2
3
4
5
6
7
8
git rebase master bug1
git checkout master
git merge bug1
git branch -d bug1
git rebase master bug2
git checkout master
git merge bug2
git branch -d bug2

Explications des commandes.

  • Transplante bug1 sur l’actuelle branche master. Si on est déjà en train de bosser sur bug1 on peut se contenter de taper git rebase master
  • Switche sur master
  • Fusionne bug1 dans master
  • Supprime la branche bug1 devenue inutile
  • Transplante bug2 sur la branche master
  • Switche sur master
  • Fusionne bug2 dans master
  • Supprime bug2 devenue inutile.

Exemple 2

Autre manière de faire, peut être plus simple…

En supposant que l’on veuille garder la feature branche synchronisée avec la branche develop, voici les commandes à suivre:

1
2
3
4
5
6
7
8
9
git fetch # depuis la feature branche (vérifier que la feature branche sur laquelle vous travaillez est à jour)

git rebase origin/develop

# S'il y a des conflits, il faut les résoudre un par un.

Utiliser git rebase --continue une fois que tous les conflits sont traités

git push origin feature_branch --force

Merge (quand même)

1
2
3
4
git checkout master
git pull
git checkout your_feature_en_attente
git merge master

Stash

Cette commande permet de mettre de côté des modifications de la copie de travail et de l’index.

Afficher tous les stash

1
git stash list

Créer un nouveau stash (stasher tous les fichiers)

1
git stash save "<nom du stash>"

Créer seulement certains fichiers

Add to index changes we don’t want to stash and then stash with –keep-index option.

1
2
3
git add app/controllers/cart_controller.php
git stash --keep-index
git reset

Appliquer un stash

1
git stash branch myfeature

Appliquer le dernier stash (LIFO)

1
git stash pop

Cherry-picking

Cherry-pick permet de sélectionner un commit quelconque et de l’appliquer sur la branche actuelle.

1
2
git checkout my-feature
git cherry-pick e32c529d

Résoudre un conflit

Pendant un merge

Après un git merge ou git pull, on commence par exécuter la commande:

1
2
3
4
5
6
7
8
9
10
11
git status # ou gss # ou git st # git stp (voir mes alias)
# On branch master
# Changes to be committed:
#
# modified: mon_fichier1
#
# Unmerged paths:
# (use "git add/rm < file >..." as appropriate to mark resolution)
#
# both modified: mon_fichier2
#

Puis on corrige le problème de merge grâce à la commande git mergetool qui ouvrira l’outil de merge configuré sur votre poste.
Autre possibilité, faire la correction manuellement, faire un git add ou git stage sur le fichier corrigé et enfin un git commit.

En cas de problème, il est possible d’exécuter un git reset --hard HEAD pour revenir en arrière sur le merge

Pour configurer git mergetool, il est possible d’utiliser la commande suivante: git config --global merge.tool bc3 après avoir téléchargé et installé l’extension vscode-ext-git-mergetool pour OSX .

Pendant un rebase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
git checkout master # ou git co master
git rebase origin/master # ou git pull --rebase

git status
# Unmerged paths:
# (use "git add/rm < file >..." as appropriate to mark resolution)
#
# both modified: mon_fichier
#

git mergetool

# On corrige

git rebase --continue

Pour revenir en arrière (état du dépot) sur un rebase qui se passe mal: git rebase --abort

Un schéma vaut mieux qu’un long discours. Voici ce que fait un rebase:

image

Gérer un conflit sur un binaire

1
2
git checkout --ours -- path_binary_file
git checkout --theirs -- path_binary_file

Réécrire l’historique

1
2
3
4
5
git rebase --interactive HEAD~3
# ou
# git rebase -i HEAD~3
# ou
# git rebase -i <sha-de-votre-commit> (via git log)

Plusieurs options s’offrent à nous pour les 3 commits:

  • Supprimer la ligne pour supprimer le commit de l’historique
  • Changer l’ordre des lignes pour changer l’ordre d’application des commits
  • Remplacer “pick” par “edit” pour pouvoir modifier le commit
  • Remplacer “pick” par “squash” pour merger le commit avec le précédent pour n’en créer qu’un seul
  • Remplacer “pick” par “reword” pour juste changer le message de commit

Pour poursuivre la réécriture de l’historique, terminer avec les commandes suivantes:

1
2
git stage
git rebase --continue

Config (en plus de oh-my-zsh)

A ajouter au fichier ~/.gitconfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[alias]
st = status
stp = status --porcelain
ci = commit
br = branch
co = checkout
rz = reset --hard HEAD
pullr = pull --rebase
unstage = reset HEAD
lol = log --graph --decorate --pretty=oneline --abbrev-commit
lola = log --graph --decorate --pretty=oneline --abbrev-commit --all
lpush = "!git --no-pager log origin/$(git currentbranch)..HEAD --oneline"
lpull = "!git --no-pager log HEAD..origin/$(git currentbranch) --oneline"
whatsnew = "!git diff origin/$(git currentbranch)...HEAD"
whatscoming = "!git diff HEAD...origin/$(git currentbranch)"
currentbranch = "!git branch | grep \"^\\*\" | cut -d \" \" -f 2"
...
[color]
branch = auto
diff = auto
status = auto
interactive = auto

Télécharger en local les Pull Requests

Projet par projet

Dans le fichier .git/config, localiser le code qui ressemble à ceci:

1
2
3
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = [email protected]:joyent/node.git

Maintenant ajouter la ligne suivante:

1
2
3
(pour Gitlab) fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*

# fetch = +refs/pull/*/head:refs/remotes/origin/pr/*

La section dans le fichier devrait resssemble à cela:

1
2
3
4
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = [email protected]:joyent/node.git
fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*

Maitenant vous pouvez récupérer toutes les PR en cours:

1
2
3
4
5
6
7
git fetch origin
From github.com:joyent/node
* [new ref] refs/pull/1000/head -> origin/pr/1000
* [new ref] refs/pull/1002/head -> origin/pr/1002
* [new ref] refs/pull/1004/head -> origin/pr/1004
* [new ref] refs/pull/1009/head -> origin/pr/1009
...

Vous pouvez faire un checkout sur une pull request en particulier:

1
2
3
git checkout pr/999
Branch pr/999 set up to track remote branch pr/999 from origin.
Switched to a new branch 'pr/999'

Ajouter automatiquement cette commande à tous les repos

1
git config --global --add remote.origin.fetch "+refs/pull/*/head:refs/remotes/origin/pr/*"

Différences entre branches

Différences entre branche actuelle et master:

1
git diff master

Différences entre 2 branches (par exemple master et staging):

1
git diff master..staging

Seulement montrer les fichiers différents entre 2 branches (sans les changements):

1
git diff --name-status master..staging

Commandes en vrac

Annuler la commande git commit (et donc conserver/ récupérer tous les changements qui étaient prêts à être pushés:

1
git reset --soft HEAD^

Modifier le dernier commit:

1
git commit --amend

Effacer une branche locale:

1
git branch -d new-branch

Retourner sur la dernière branche:

1
git checkout -

Effacer une branche remote:

1
git push origin --delete new-branch

Contrôler le comportement de git push par default pour éviter de devoir specifier la branch (sur laquelle on est) avant de pusher:

1
git config --global push.default current

Annuler les changements en local d’un fichier

1
git checkout filename

Pusher un seul commit

1
git push <remote_name> <commit SHA>:<remote_branch_name>

La branche <remote_branch_name> doit exister sur remote. (Si ce n’est pas le cas, il est possible d’utiliser git push <remote_name> <commit SHA>:refs/heads/<remote_branch_name> pour la créer automatiquement.)

Note:
git push --set-upstream origin dev ou git push -u origin dev
Cette commande envoie la branche « dev » vers le dépôt « origin » (i.e. celui dont on a fait un clone au départ), et l’option –set-upstream permet de dire à Git de se souvenir qu’un « git push » de notre branche « dev » a été fait. Le prochain push pourra donc se faire simplement avec un simple git push.

Annuler le dernier commit

Les commandes à appliquer dépendent du dernier commit:

  • Quand le dernier commit n’est pas le commit initial, il suffit d’exécuter la commance suivante: git reset HEAD~

  • Quand le dernier commit est le commit initial, il faut exécuter les commandes suivantes: git update-ref -d HEAD puis git rm --cached -r .

Annuler le dernier commit et garder tous les changements staged

1
git reset --soft HEAD~

C’est différent de git reset HEAD~ qui ne garde pas staged les derniers changements.

Naviguer dans tout son historique Git

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ git init
Initialized empty Git repository in .git/

$ echo "testing reset" > file1
$ git add file1
$ git commit -m 'added file1'
Created initial commit 1a75c1d: added file1
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 file1

$ echo "added new file" > file2
$ git add file2
$ git commit -m 'added file2'
Created commit f6e5064: added file2
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 file2

$ git reset --hard HEAD^
HEAD is now at 1a75c1d... added file1

$ cat file2
cat: file2: No such file or directory

$ git reflog
1a75c1d... [email protected]{0}: reset --hard HEAD^: updating HEAD
f6e5064... [email protected]{1}: commit: added file2

$ git reset --hard f6e5064
HEAD is now at f6e5064... added file2

$ cat file2
added new file

Taille en local du repo

1
2
3
4
5
6
7
8
9
10
$ git count-objects -v -H

count: 6
size: 24.00 KiB
in-pack: 0
packs: 0
size-pack: 0 bytes
prune-packable: 0
garbage: 0
size-garbage: 0 bytes

Bisect

Le bisect permet de réaliser une dichotimie sur un range de commit afin d’identifier celui qui génère une régression.

1
2
3
$ git bisect start          # Start the binary search process
$ git bisect bad # Identify the end of the range
$ git bisect good v4.2.0 # Identify the start of the range

Puis après un test sur votre app ou tests unitaires préciser soit git bisect bad ou git bisect good

Workflow pour travailler en équipe

Gitflow:
http://danielkummer.github.io/git-flow-cheatsheet/

Edited 13 november 2016:

Better method: Github flow:

https://blogs.technet.microsoft.com/devops/2016/06/21/a-git-workflow-for-continuous-delivery/