13 minutes
Config, Commandes et tips utiles Git
Indispensable à savoir
Index
L’index est une zone qui permet de préparer un commit.
HEAD
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:
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
F---G ← bug2
/
A---B---E---H---I ← master
\
C---D ← bug1
En utilisant un rebase avant chaque fusion, on obtient l’historique suivant :
A---B---E---H---I---C---D---F---G ← master
Les commandes pour parvenir à ce résultat sont les suivantes, explications juste après.
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:
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
Exemple 3 (ultra simple que je recommande)
git checkout votre_feature_branch
git fetch
git rebase origin/master
Et c’est tout!
Merge (quand même)
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
git stash list
Appliquer un stash
# Exemple:
git stash apply stash@{0}
Annuler un git stash pop
ou git stash apply
si conflit
git reset --merge
Annuler le conflit sur un fichier après git stash (reprendre version serveur)
# Après un $ git stash pop
git add sur_le_fichier_avec_confict
git reset HEAD -- sur_le_fichier_avec_confict
git checkout sur_le_fichier_avec_confict
# Note perso:
# Tester si git rm --cached sur_le_fichier_avec_confict peut faire le job
Créer un nouveau stash (stasher tous les fichiers)
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.
git add app/controllers/cart_controller.php
git stash --keep-index
git reset
Appliquer un stash
git stash branch myfeature
Appliquer le dernier stash (LIFO)
git stash pop
Voir le contenu du dernier stash
git stash show -p
Voir le contenu d’un stash spécifique
git stash show -p stash@{42}
Cherry-picking
Cherry-pick permet de sélectionner un commit quelconque et de l’appliquer sur la branche actuelle.
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:
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
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:
Gérer un conflit sur un binaire
git checkout --ours -- path_binary_file
git checkout --theirs -- path_binary_file
Réécrire l’historique
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:
git stage
git rebase --continue
Config (en plus de oh-my-zsh)
A ajouter au fichier ~/.gitconfig:
[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
lolar = log --graph --pretty='format:%C(auto)%h %d %s %C(green)%an%C(bold blue) %ad' --all --date=relative
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:
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = [email protected]:joyent/node.git
Maintenant ajouter la ligne suivante:
(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:
[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:
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:
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
git config --global --add remote.origin.fetch "+refs/pull/*/head:refs/remotes/origin/pr/*"
Effacer git config globale
git config --global --unset user.name
git config --global --unset user.email
Vérifier la config globale
git config --global -l
git config local
git config user.name = "username"
git config user.email = "email"
git config core.sshCommand "ssh -i ~/.ssh/id_rsa_example -F /dev/null"
Préciser git ssh key globale
Editer le fichier ~/.ssh/config et ajouter le contenu suivant:
Host github.com
HostName github.com
IdentityFile ~/.ssh/id_rsa_example
Différences entre branches
Différences entre branche actuelle et master:
git diff master
Différences entre 2 branches (par exemple master et staging):
git diff master..staging
Seulement montrer les fichiers différents entre 2 branches (sans les changements):
git diff --name-status master..staging
Différence entre un fichier local (déjà en stage) et le dernier commit (ou l’avant dernier):
git diff <commit> <path>
# Dernier commit:
git diff HEAD^ myfile
# Avant dernier commit:
git diff HEAD^^ myfile
# Equivalent à:
git diff HEAD~2 myfile
Example:
git diff HEAD~1 HEAD -- mon_fichier.js
git diff HEAD~2 HEAD -- mon_fichier.js
Commandes en vrac
Annuler la commande git commit
(et donc conserver/ récupérer tous les changements qui étaient prêts à être pushés:
git reset --soft HEAD^
Modifier le dernier commit:
git commit --amend
Annuler le dernier git amend:
git reset --soft HEAD@{1}
Renommer une branche locale:
git checkout ancien_nom
git branch -m nouveau_nom
git push origin nouveau_nom
Effacer une branche locale:
git branch -d new-branch
Effacer une branche remote:
git push origin --delete new-branch
Retourner sur la dernière branche:
git checkout -
Contrôler le comportement de git push par default pour éviter de devoir specifier la branch (sur laquelle on est) avant de pusher:
git config --global push.default current
Annuler les changements en local d’un fichier
git checkout filename
Pusher un seul commit
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
ougit 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 simplegit 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
puisgit rm --cached -r .
Annuler le dernier commit et garder tous les changements staged
git reset --soft HEAD~
C’est différent de git reset HEAD~
qui ne garde pas staged les derniers changements.
Annuler le dernier git pull
git reset HEAD~1 --hard
--hard
précise à git que l’on souhaite avoir notre répertoire de travail avec le contenu du commit choisi
Pull remote branch sans merge
On suppose que l’on est déjà sur la branch que l’on souhaite resynchroniser
git pull --rebase
Voir les derniers commits locaux pas encore pushé sur l’origin:
git log --branches --not --remotes
Effacer les fichiers du working directory en incluant les nouveaux untracked files
git clean -fd
Lister les tags sur un commit
git tag --points-at HEAD
Tagguer (le dernier commit d’) une branche
git tag mon_tag
git push origin mon_tag
# (non recommandé) pousser tous les tags
git push --tags
Lister les fichiers modifiés dans une branche par rapport à master:
git diff --name-only ma_branche $(git merge-base ma_branche master)
Ajouter un tag sur un commit particulier
git tag -a v1.0 d613c59
git push origin v1.0
Effacer un tag local et remote
# Effacement local
git tag -d v1.0
# Effacement remote
git push --delete origin v1.0
ou
git push origin :refs/tags/v1.0
Lister les commits entre un tag et HEAD
git log --pretty=oneline HEAD...v1.0
Alternative sans les commits de merge:
git log --pretty=oneline HEAD...v1.0 --no-merges
Alternative avec tous les détails:
git log HEAD...v1.0
Autre alternative:
git log --pretty=format:"%h; author: %cn; date: %ci; subject:%s" HEAD...v1.0
Lister les commits de merge depuis le dernier tag et afficher uniquement le commit message
git log $(git describe --abbrev=0 --tags)..HEAD --merges --pretty=format:"%b"
Lister les commits de merge entre les 2 derniers tags et afficher uniquement le commit message
git log $(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`)..$(git describe --abbrev=0 --tags `git rev-list --tags --skip=0 --max-count=1`) --merges --pretty=format:"%b"
Récupérer le dernier tag
git describe --abbrev=0 --tags `git rev-list --tags --skip=0 --max-count=1`
Récupérer l’avant dernier tag
git describe --abbrev=0 --tags `git rev-list --tags --skip=0 --max-count=1`
Voir les commits par contributeur depuis la dernière release/tag
git shortlog --merges $(git describe --abbrev=0 --tags)..HEAD
Get current branch
git rev-parse --abbrev-ref HEAD
Voir les changements/ commits sur un fichier
git log --follow -p -- full_path_mon_fichier
# --follow permet de suivre un fichier s'il a été renommé
Alternative:
find . -name "*mon_fichier" | xargs git log
Super Alternative:
tig full_path_mon_fichier
outig '*mon_fichier'
Installation de tig viabrew install tig
ouapt install tig
Tig Full documentation
Naviguer dans tout son historique Git
$ 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... HEAD@{0}: reset --hard HEAD^: updating HEAD
f6e5064... HEAD@{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
$ 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.
$ 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
Ajouter un submodule dans son repo
git submodule add https://github.com/<user>/mon_repo_as_submodule mon_repo_as_submodule
Cloner un repo git contenant un submodule
git clone --recursive https://github.com/<user>/mon_repo
Effacer un submodule du repo
git submodule deinit -f — mon_repo_as_submodule
rm -rf .git/modules/mon_repo_as_submodule
git rm -f mon_repo_as_submodule
Pull git submodules après avoir cloné un projet sur GitHub
Normalement la commande suivante est automatiquement jouée par git sur les nouvelles versions
git submodule update --init --recursive
Importer les mises à jour d’un submodule dans le repo
git submodule update --recursive
Mettre à jour un submodule à la dernière version
cd mon_repo_as_submodule
git checkout master && git pull
cd ..
git add mon_repo_as_submodule
git commit -m "updating submodule to latest"
.gitignore global
touch ~/.gitignore_global
git config --global core.excludesfile ~/.gitignore_global
Différence entre git pull et git pull –rebase
Point de départ:
D---E master
/
A---B---C---F origin/master
Un git pull
donnera:
D--------E
/ \
A---B---C---F----G master, origin/master
Un git pull --rebase
donnera
A---B---C---F---D'---E' master, origin/master
Ajouter un tag en 2s (shortcuts)
Ajouter ceci au fichier ~/.zshrc
last_release ()
{
git describe --abbrev=0 --tags `git rev-list --tags --skip=0 --max-count=1`
}
previous_release ()
{
last_release
}
last_tag ()
{
last_release
}
previous_tag ()
{
last_release
}
last_branches ()
{
git branch --sort=-committerdate
}
function new_release {
readonly releaseversion=${1:?"The release version (i.e. new_release vX.X.X) must be specified."}
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$current_branch" != "master" ]; then
echo "Your are not on master branch. Please switch to it to create a new tag"
else
git tag $releaseversion
git push origin $releaseversion
fi
}
Convertir la remote URL de HTTPS en SSH et vice-versa
alias git-https="git remote set-url origin https://github.com/$(git remote get-url origin | sed 's/https:\/\/github.com\///' | sed 's/[email protected]://')"
alias git-ssh="git remote set-url origin [email protected]:$(git remote get-url origin | sed 's/https:\/\/github.com\///' | sed 's/[email protected]://')"
Ajouter une clé privé dans une session SSH distante
eval `ssh-agent -s`
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/