====== 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 [file] # comparaison explicite entre deux commits git diff [file] # comparaison du workspace avec un commit git diff [file] # comparaison du workspace avec HEAD avec , 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 >>>>>>> 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 : * https://git-scm.com/book/fr/v2/Les-branches-avec-Git-Branches-distantes * https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging * https://git-scm.com/book/en/v2/Git-Branching-Rebasing ==== 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 -- 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 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 => https://www.journaldunet.fr/web-tech/developpement/1202943-effacer-une-branche-git-a-la-fois-locale-et-distante/ ==== Suppression de Commit Fautifs sur le dépôt distant ==== Revenir à l'état désiré : $ git reset --hard 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 [[https://services.emi.u-bordeaux.fr/projet/savane/ | Savane]]. Choisir un projet //privé// ;-) ==== Pour aller encore plus loin ==== * Submodules : https://git-scm.com/book/en/v2/Git-Tools-Submodules ==== Documentation ==== * https://git-scm.com/docs/ * http://git-scm.com/book/fr/ * https://education.github.com/git-cheat-sheet-education.pdf * https://services.github.com/kit/downloads/github-git-cheat-sheet.pdf ==== A Lire ==== * https://gitlab-ce.iut.u-bordeaux.fr/Pierre/DEMO-GIT-PT2 * https://marklodato.github.io/visual-git-guide/index-en.html * https://help.github.com/articles/syncing-a-fork/ * http://nvie.com/posts/a-successful-git-branching-model/ * https://marklodato.github.io/visual-git-guide/index-en.html * http://eagain.net/articles/git-for-computer-scientists/ * https://yakiloo.com/getting-started-git-flow/ * https://git-man-page-generator.lokaltog.net/ * https://thor.enseirb-matmeca.fr/ruby/docs/repository/git