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}
Annuler le dernier git cherry-pick (successful):
git reset --hard 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
**Retirer un commit particulier sur une branch**
git reset –hard commit-hash
**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` ou `tig '*mon_fichier'`
> Installation de tig via `brew install tig` ou `apt install tig`
> [Tig Full documentation](http://jonas.nitro.dk/tig/manual.html)
## 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//mon_repo_as_submodule mon_repo_as_submodule
## Cloner un repo git contenant un submodule
git clone –recursive https://github.com//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/