Table of Contents
Git
Un bon tutoriel pour jouer de manière interactive (https://learngitbranching.js.org/) et une sandbox en bonus (https://learngitbranching.js.org/?NODEMO).
Commençons par créer un dépôt Git “remote” vide, qui se trouve en principe sur un serveur distant. Dans cet exemple, il se trouvera dans le répertoire /tmp/remote de la machine locale.
mkdir /tmp/remote cd /tmp/remote git init --bare --shared
Nous pouvons maintenant “clôner” notre dépôt Git en local dans un répertoire “local”.
git clone /tmp/remote /tmp/local cd /tmp/local
A partir de maintenant nous pouvons commencer à travailler dans le dépôt local.
Tout d'abord, modifions un peu la configuration globale de Git sur la machine :
# mail / user name git config --global user.name "orel" git config --global user.email aurelien.esnard@u-bordeaux.fr # editeur de texte git config --global core.editor emacs # coloration syntaxique git config --global color.diff auto git config --global color.status auto git config --global color.branch auto
Pour afficher votre configuration :
git config --global -l
Commandes de base
Ensuite, on va pouvoir commencer à jouer le depôt local (/tmp/local). Tout d'abord, on crée un fichier “date.txt”. Initialement, ce fichier a comme le statut untracked, c'est-à-dire non suivi.
date > date.txt git status
Pour suivre ce fichier dans le dépôt Git, il va falloir tout d'abord l'ajouter à l'index (git add). Puis dans un second temps, on va pouvoir effectuer le commit, ce qui consiste à conserver dans le dépôt local une version de ce fichier. On prendra garde de mettre des commentaires compréhensibles lors des commits.
git add date.txt git commit -m "premier commit" git status
A tout moment, la commande git status permet de vérifier le statut des différents fichiers afin d'éviter les bétises : à utiliser sans modération ! Personnellement, j'aime bien la version courte : git status -s.
Dans un second temps, on modifie le fichier… On va souhaiter committer cette nouvelle version dans le dépôt local. Attention, il ne faut pas oublier de signaler la modification d'un fichier en faisant un git add avant d'effectuer le commit !
date > date.txt git status # le fichier est indiqué comme "modifié" git add date.txt git commit -m "second commit"
Synchronisation avec le dépôt distant
Les commits sont uniquement locaux et n'ont aucun effet sur le dépôt distant (remote). D'ailleurs, il n'est pas nécessaire d'être connecté à Internet pour effectuer ces commits ! Pour synchroniser le dépôt local avec le dépôt distant (remote), il faut faire un push. Ainsi tous les commits vont être “poussés” sur ce serveur pour y être archivés.
git push
A l'inverse, pour mettre à jour son dépôt local, quand le dépôt remote a été modifié (par quelqu'un d'autre), il faut faire un pull.
git pull
Log & Diff
La commande log permet de suivre l'historique des “commits”. On notera que chaque commit produit une nouvelle version de l'ensemble du dépôt, identifié par un ID de la forme “83109ce0d335a…4e8bd58f”.
git log
En un peu plus complet :
git log --summary
En un peu plus joli :
git log --graph --oneline --all
Par ailleurs, la commande diff permet de voir quelles modifications ont été effectuées pour tous les fichiers ou pour un fichier particulier :
git diff [file]
Il existe plusieurs variantes de diff :
git diff <commit> <commit> [file] # comparaison explicite entre deux commits git diff <commit> [file] # comparaison du workspace avec un commit git diff [file] # comparaison du workspace avec HEAD
avec <commit>, un identifiant de commit qui peut être :
- un ID explicite comme “83109ce0d335a…4e8bd58f” tel qu'affiché par git log ;
- HEAD, qui désigne le dernier commit dans la branche courante du dépôt local ;
- HEAD~1, qui désigne l'avant dernier commit dans la branche courante du dépôt local ;
- un nom de branche sur le dépôt local comme master ;
- un nom de branche sur le dépôt distant (origin), comme origin/master.
Une option intéressante permet d'afficher uniquement le nom des fichiers modifiés~:
git diff --name-only
Gestion des conflits
Imaginons maintenant que deux développeurs ont commité des modifications sur leur copie locale respective. Que se passe-t-il au moment de la synchronisation ? On va devoir fusionner ces modifications, ce qui peut engendrer un conflit quand les modifications portent sur le même fichier !
Simulons le problème avec deux copies locales du dépôt : local1 et local2. Dans ce premier cas, nous allons ajouter concurremment deux fichiers différents :
# récupérons deux copies loales du dépôt git clone /tmp/remote /tmp/local1 git clone /tmp/remote /tmp/local2 # ajout de date1.txt dans local1 cd /tmp/local1 date > date1.txt git add date1.txt git commit -m "ajout de date1" # ajout date2.txt dans local2 cd /tmp/local2 date > date2.txt git add date2.txt git commit -m "ajout de date2" # fusion cd /tmp/local1 git push # ok cd /tmp/local2 git push # ko (rejected, pull before!) git pull # ok après merge automatique...
Regardons maintenant ce qu'il se passe, si on modifie concurrement le même fichier, disons “date1.txt”…
# modification de date1.txt dans local1 cd /tmp/local1 date >> date1.txt git add date1.txt git commit -m "modif de date1" # modification de date1.txt dans local2 cd /tmp/local2 date >> date1.txt git add date1.txt git commit -m "modif aussi de date1" cd /tmp/local1 git push # ok cd /tmp/local2 git push # ko (rejected, pull before!) git pull # conflit, le merge automatique échoue !!!
Pour résoudre le conflit, il faut éditer le fichier “date1.txt” responsable du conflit et faire la fusion à la main… Le fichier en cause marque explicitement la zone de conflit et affiche les deux versions à comparer…
<<<<<<< HEAD ma version HEAD dans le dépôt local ======= l'autre version qui vient du dépôt remote >>>>>>> <commit>
On corrige à la main, on commit et on push à nouveau
Les branches locales et distantes
Par défaut, nous travaillons sur la branche master du dépôt local que nous synchronisons avec la branche distante origin/master du dépôt distant avec les commandes git pull et git push. Il est possible de créer et d'utiliser d'autres branches. C'est particulièrement utile pour développer une nouvelle fonctionnalité, expérimenter des choses ou réparer un bug, sans perturber le reste du projet.
Nous allons maintenant créer une nouvelle branche hotfix, afin de corriger un bug.
git branch hotfix git branch hotfix * master
Nous sommes encore dans la branche master comme l'indique “*”. Passons dans la branche hotfix :
git switch hotfix git branch * hotfix master
Regardons l'état du dépôt :
git log --graph --oneline * c908469 (HEAD -> hotfix, origin/master, origin/HEAD, master) bla bla * d144ffa autre bla bla (...)
Le dernier commit est bien commun à la branch hotfix, la branch master. Corrigeons le “bug” maintenant :
date >> date.txt git add date.txt git commit -m "deux dates dans hotfix"
Une fois le bug corrigé, nous pouvons le commiter dans la branche courante hotfix. Notons que cela n'a aucun impact sur la branche master:
git log --graph --oneline * c38c81a (HEAD -> hotfix) deux dates dans hotfix * c908469 (origin/master, origin/HEAD, master) bla bla * d144ffa autre bla bla (...)
Pour propager cette modification vers la branche master, il va falloir repasser dans la branche master, faire quelques modifs sur master puis effectuer un merge.
git switch master date >> date2.txt git add date2.txt git commit -m "date2.txt" git merge hotfix
git log --graph --oneline * 36db317 (HEAD -> master) Merge branch 'hotfix' |\ | * c38c81a (hotfix) deux dates dans hotfix |/ * c908469 bla bla * d144ffa autre bla bla
Pour propager ces modifications dans le dépôt distant on fera des pull/push comme d'habitude.
Si l'on souhaite que sa branche locale hotfix deviennent une branche distante (ou “remote”) :
git log --graph --oneline * c38c81a (HEAD -> hotfix) deux dates dans hotfix * c908469 bla bla * d144ffa autre bla bla (...) git switch hotfix git push --set-upstream origin hotfix git log --graph --oneline * c38c81a (HEAD -> hotfix, origin/hotfix) deux dates dans hotfix * c908469 bla bla * d144ffa autre bla bla (...)
La encore on se synchronisera avec la branche distance à l'aide des commandes push/pull:
date >> date.txt git add date.txt git commit -m "mise à jour de la date" git log --graph --oneline * 68b1978 (HEAD -> hotfix) mise à jour de la date * c38c81a (origin/hotfix) deux dates dans hotfix (...) git push git log --graph --oneline * 68b1978 (HEAD -> hotfix, origin/hotfix) mise à jour de la date * c38c81a deux dates dans hotfix (...)
Pour voir toutes les branches (locales et distantes) :
git fetch -p git branch -a
Nota Bene : le fetch permet de mettre à jour son cache local et éventuellement (option -p) d'effacer dans ce cache les références à des branches mortes !
Puis pour supprimer la branche locale, il faut faire ceci :
git branch -d mybranch
Puis pour supprimer la branche distante (enfin presque…), il faut faire ceci :
git push origin --delete mybranch
Pour récupérer une branche distante et la suivre via une branche locale :
git checkout --track origin/mybranch
Considérons le cas suivant… Expliquer ensuite le rebase simple :
git switch mybranch git rebase master # git switch master git merge mybranch # merge naif git branch -d mybranch
Pour aller plus loin :
Les Tags
En bref :
# création d'un tag git tag -a v1.4 -m "my version 1.4" # liste des tags git tag # afficher les infos sur un tag git show v1.4 # push le tag sur le serveur git push origin v1.4 # création d'une nouvelle branche locale à partir d'un tag git checkout -b version-1.4 v1.4 # git checkout -b [branchname] [tagname]
En détail : https://git-scm.com/book/en/v2/Git-Basics-Tagging
Restaurer un fichier supprimé ou une ancienne version
Si l'on souhaite annuler les modifications faites sur un fichier dans le working directory (qui ne sont pas encore commités)… c'est-à-dire, si l'on souhaire récupérer un fichier dans sa dernière version commitée :
git checkout -- myfile
Lorsque le fichier path/to/myfile a été supprimé plus loin dans l'historique, il faut examiner les log en détail afin d'identifier le “commit” juste avant la suppression :
git log --summary git checkout <commit> -- <path/to/myfile>
Lorsque l'on a commité (en local) des trucs par erreur, il est possible d'annuler facilement un commit à condition de ne pas l'avoir push ! Par exemple, si l'on souhaite annuler le dernier commit, on peut faire :
git reset --hard HEAD~1
Attention, l'option –hard a pour effet de supprimer *définitivement* dans vos fichiers les modifications du dernier commit…
Si on souhaite maintenant annuler le dernier commit sans toucher aux fichiers, par exemple pour corriger un commit incomplet, on peut faire :
git reset HEAD~1
Dans ce cas, les fichiers impliqués dans le commit annulé apparaissent comme *modified and not staged*… A vous de retravailler ces fichiers et de proposer un nouveau commit ! Une autre solution à ce problème consiste à faire un nouveau commit qui se fusionne avec l'ancien grâce à l'option –amend :
git commit -m "maj de mon dernier commit..." --amend
Autre cas de figure, j'ai commité des modifs sur deux fichiers (*good* et *bad*), et je souhaite annuler partiellement mon commit en annulant les modifs sur le fichier *bad*, sans pour autant perdre les modifications sur ce fichier dans répertoire local…
git reset HEAD~1 # reset global (HEAD recule) git add good git commit -m "update good only"
Ou encore, une solution un peu plus tricky…
git reset HEAD~1 -- bad # reset partiel (HEAD ne bouge pas) git commit -m "update good only" --amend
Dans le cas, où l'on a *push* le commit que l'on souhaite supprimer, les chosent se compliquent car elles peuvent maintenant impacter d'autres utilisateurs du dépôt ! Pas trop de choix dans ce cas, il faut faire un *revert* qui se présentera comme un nouveau commit qui annule l'ancien… mais tout restera écrit dans l'historique
git revert HEAD~1 git push
Si l'on souhaite synchroniser à nouveau son dépôt local avec *origin/master* en ignorant les supprimant les derniers commits locaux (non synchronisés), on peut biensûr faire un nouveau git clone, mais il y a plus simple et plus rapide :
git reset --hard origin/master
A utiliser avec prudence ! Néanmoins, vos anciens commits ne sont pas totalement perdus, on peut les retrouver en faisant :
git reflog
D'autres commandes
Pour éviter de devoir toujours taper son mot de passe au moment de push/pull, il est possible de demander à Git de sauvegarder les credentials :
git config credential.helper store
Si l'on souhaite supprimer du dépôt tous les fichiers non suivis, la commande clean permet de faire le ménage. Attention, tout de même à ne pas supprimer des fichiers par erreur ! Personnellement, j'aime bien les options suivantes : en interactif (-i), suppression des directory aussi (-d) et ignore de .gitignore (-x) !
git clean -i -x -d
Si l'on souhaite faire une archive toto.zip (ou toto.tgz) de son dépôt master pour un projet Git toto :
git archive --format=zip --prefix=toto/ --output /tmp/toto.zip master git archive --format=tar --prefix=toto/ master | gzip > /tmp/toto.tgz
Statistique : pour connaitre le nombre de commit git par utilisateur…
git shortlog -sn
Utilisation du Stash
Une petite “étagère locale” pour sauvegarder toutes les modifications efectués dans un dépôt sans les commiter, par exemple avant de change de branche… cela conserme également les fichiers “non suivis”.
# ... hack hack hack ... $ git stash $ edit emergency fix $ git commit -a -m "Fix in a hurry" $ git stash pop # ... continue hacking ..
Utilisation du Worktree
C'est une alternative au stash pour réaliser des développements concurrents dans des branches différentes…
$ git worktree add /path/to/myproject_mybranch mybranch # or <commitID> Preparing worktree (checking out 'mybranch') HEAD is now at 88d22d5 ... $ git worktree list /path/to/myproject 231b057 [master] /path/to/myproject_mybranch 88d22d5 [mybranch]
L'idée c'est que le .git/ n'est pas dupliqué !
Suppression d'une Branche Distante
Pour supprimer une branche locale sur votre ordinateur, la commande est la suivante :
$ git branch -d [nom_de_la_branche]
Ou bien :
$ git branch -D [nom_de_la_branche]
Pour supprimer la branche située sur le dépôt distant (syntaxe récente) :
$ git push origin --delete [nom_de_la_branche]
Pour mettre à jour les changements sur un autre ordinateur, il faut faire :
$ git fetch --all -prune
Suppression de Commit Fautifs sur le dépôt distant
Revenir à l'état désiré :
$ git reset --hard <commit hash>
Forcer cet état comme le dernier état du dépôt :
$ git push --force origin master
Attention : Ces commandes sont susceptibles d'engendrer le chaos lorsque plusieurs utilisateurs collaborent sur le même dépôt. https://makandracards.com/git/7347-supprimer-des-commits-fautifs-avec-git
Savane
Pour créer son projet Git hébergé au CREMI, il faut utiliser Savane. Choisir un projet privé
Pour aller encore plus loin
- Submodules : https://git-scm.com/book/en/v2/Git-Tools-Submodules