Git par la pratique

Git est un fabuleux outils de versioning, mais il parait aussi puissant qu’obscur…

Qui n’a jamais tremblé en tentant une commande trouvé sur Stack Overflow ?
Cela peut parfois ressembler à une incantation.

Git workshop

Dans cet article, nous allons démystifier l’outil au travers d’un workshop assez simple.

Pré-requis: Vous connaissez les bases de git et l’avez installé sur votre système.

Retrouvez plus d’information sur l’ebook de Scott Chacon et de Ben Straub .

Setup

Le workshop se déroulera dans un répertoire dédié.

mkdir workshop
cd workshop

Les bases

Initialisation

git init

En initialisant un dépôt, git va créer une structure de fichier complexe dans le répertoire .git.

Le fichier .git/config est notable, il contiendra entre autres les informations locales concernant l’utilisateur, les branches et remote…

~/workshop$ git config user.name 'jb'
~/workshop$ git config user.email 'jb@gojob.com'
~/workshop$ cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	ignorecase = true
	precomposeunicode = true
[user]
	name = jb
	email = jb@gojob.com

Le cycle de vie des fichiers

Les commandes de base à connaître

  • git status pour voir l’état des fichiers.
  • git log pour voir les derniers commits.
  • git add pour ajouter des modifications au suivi.
  • git reset pour annuler l’ajout de modifications au suivi (git add).
  • git diff pour voir les modifications en cours.
  • git commit pour enregistrer les modifications dans le dépot.

Avec quelques options:

  • Voir l’état des fichiers en version courte (short):

    git status -s
  • Voir les logs en version courte:

    git log --oneline

    Et en limitant au 3 derniers commits:

    git log --oneline -3
  • Commiter sans passer par l’étape de add et en ajoutant toutes modifications:

    git commit -a
  • Commiter en mettant le message directement:

    git commit -m "commit message"

Exercice

Créer le fichier index.html avec le contenu suivant:

<!DOCTYPE html>
<html>
<body>
<span>Initial version</span>
</body>
</html>
  1. Lancer git init pour initialiser le dépôt.
  2. Lancer git status -s pour obtenir le status.
  3. Lancer git add index.html pour ajouter le fichier (ses modifications) au suivi.
  4. Modifier le fichier index.html pour remplacer Initial version par Seconde version.
  5. Lancer git status (et sa version courte) afin de constater l’état particulier du suivi.
  6. Lancer git commit -m « Initial version » pour valider les modifications au dépot.
  7. Lancer à nouveau git status -s
  8. Lancer git diff pour voir la différence entre le fichier et le dépot
  9. Lancer git add index.html pour ajouter les nouvelles modifications au suivi.
  10. Lancer git status -s
  11. Lancer git reset pour annuler le suivi des nouvelles modifications
  12. Lancer git status -s
  13. Lancer git diff pour vérifier que l’état du fichier n’a pas changé

Comme vous avez pu le constater, git add effectue une sorte de snapshot des modifications, et toutes celles apportées par la suite ne seront prise en compte au prochain commit que si un nouveau git add les y rajoute.

Et pour aller plus loin, revertons le fichier index.html avec:

git checkout HEAD -- index.html

Allons plus loin avec git rebase

Garder un historique propre est essentiel pour travailler en équipe ou même s’y retrouver plus tard.

Il est fréquent d’avoir besoin de changer de branche pour quelques raisons que ce soient (hotfix…) et plutôt que de stasher, il est plus pratique de faire un petit commit de backup (git commit -n quand le lintage le bloque 😉 ).

Ce commit n’a pas lieu de rester dans l’historique, car il n’ammène pas le projet à un état de consistence satisfaisant (feature incomplete, bug / todo divers…).

Cloner le dépot servant de support.

git clone https://github.com/gojob-1337/workshop-2018-04-rebase.git

Ce dépot contient deux branches master et css

Exercices

Partie 1

Sur la branche master, lors du commit libellé append h3, le bout de code class = «  » a été laissé par erreur.

Pour l’exercice, nous allons le supprimer.

  1. Récuperer le hash du commit (à savoir a5c7315)
    ~/workshop$ git log --oneline
    20a65bd (HEAD -> master, origin/master) remove spaces
    69ea0a9 remove heading
    0e18763 append head
    3040084 append h6
    ba65167 append h5
    eafcab6 append h4
    a5c7315 append h3
    282cc62 append h2
    a1a2109 append h1
    cabcc16 Initial commit
    
  2. Lancer git rebase -i a5c7315^ (Noter le ^, sinon, il aurait fallu prendre le hash précédent, à savoir 282cc62)
  3. Remplacer le premier pick par edit ou e

    Puis enregistrer.
  4. Effectuer les modifications nécessaires sur le fichier index.html
  5. Ajouter les modifications au suivi
    git add index.html
  6. Continuer le rebase
    git rebase --continue
  7. Certain commits ne vont pas poser de problème alors que d’autres seront en conflit car les mêmes lignes y sont modifiées. Corriger les conflits, adder pour les valider et continuer le rebase jusqu’à ce qu’il soit terminé.
    ~/workshop$ git rebase --continue
    Successfully rebased and updated refs/heads/master.
    		
  8. Une fois le dernier rebase terminé, regarder l’historique pour constater les changements

Dans cet exercice, nous remontons dans le passé pour modifier un commit, il faut bien comprendre que le process de rebase va rejouer tous les commits qui suivent celui modifié et qu’en cas de conflit, il peut falloir intervenir sur chaque commit.
Cela peut devenir fastidieux en cas de conflit assez massif. Si vous prévoyez de squasher un certain lot de commit, il sera alors plus judicieux de squasher d’abord puis modifier un ancien commit.

En cas d’erreur, il est possible d’annuler le rebase en court avec :

git rebase --abort

Petit plus:

  1. Modifier le contenu du span.
  2. Lancer
    git add index.html
    git commit --amend
  3. Enregistrer et regarder l’historique

Cette commande très pratique permet de modifier le dernière commit simplement en ajoutant le paramètre –amend à la commande de commit.

Partie 2

Le but va être de déplacer la branche css à la fin de la branche master, afin de pouvoir la merger et obtenir un historique clair.

  1. Passer sur la banche css
    git checkout css
  2. Rebaser sur master css
    git rebase -i master


    Dans ce cas, le commit nommé append h3 va diverger de celui que l’on a modifié sur master, c’est pourquoi il apparait ici. Il va donc y avoir un commit de merge.

  3. Comme dans le premier exercice, alterner les résolutions de conflit jusqu’à la fin du rebase

Partie 3

Mergeons la branche css sur master et supprimons la.

git checkout master
git merge css
git branch -d css

Comme certain commits de la branche css ne sont pas mergés sur master (du fait des rebases, squash…), il faut passer par le -D majuscule.

git branch -D css

Regarder alors l’historique.

~/workshop$ git log --oneline
73a013f (HEAD -> master) Merge branch 'css'
c47310b append h2 style
f7cbcf5 append h1 style
d287293 merge
0ca1dea remove spaces
9c987c8 remove heading
45c110e append head
67e1ebc append h6
1b15031 append h5
60b2ee1 append h4
fd17720 append h3
282cc62 append h2
a1a2109 append h1
cabcc16 Initial commit

Merger les commits par lot pour obtenir l’historique suivant:

~/workshop$ git log --oneline
af498bc (HEAD -> master) append style
38e2d86 replace content
31522ed append headings
cabcc16 Initial commit

Partie 4

Nous allons annuler les deux derniers commits et conserver les modifications afin de nous remettre en édition simple.

~/workshop$ git reset --soft HEAD~2

~/workshop$ git log --oneline
31522ed (HEAD -> master) append headings
cabcc16 Initial commit

~/workshop$ git status -s
M  index.html

~/workshop$ git diff HEAD index.html
diff --git a/index.html b/index.html
index 0aac673..c944863 100644
--- a/index.html
+++ b/index.html
@@ -1,15 +1,17 @@


-
-
-
+
	+    Git Example
	+    
	+

-

This is heading 1

-

This is heading 2

-

This is heading 3

-

This is heading 4

-
This is heading 5
-
This is heading 6
+ This page is just a demo - \ No newline at end of file

Modifier le title avec Git workshop et commiter le tout.

Nous allons maintenant annuler le dernier commit et reverter les changements de celui-ci:

~/workshop$ git log --oneline
f691b73 (HEAD -> master) workshop
31522ed append headings
cabcc16 Initial commit

~/workshop$ git reset --hard HEAD~1
HEAD is now at 31522ed append headings

~/workshop$ git log --oneline
31522ed (HEAD -> master) append headings
cabcc16 Initial commit

~/workshop$ git status -s

Comme vous avez pu le voir dans cette partie, le reset permet d’annuler des commit en se déplacement par rapport à la HEAD (HEAD~1 pour se positionner à l’avant dernier commit, HEAD~2 à celui le précedent…) et l’option – – soft permet de conserver les changements alors que – – hard les supprime tout simplement.

Use the force

Lorsque l’on modifie l’historique localement, il y a par définition une divergence avec la remote, il faut donc ne pas avoir peur de pousser ses modifications en utilisant la force.

Attention, il ne faut pas puller dans ce cas, sinon… vous rappatriez des commits dépréciés et vous n’avez pas fini de régler les conflits.

~/workshop$ git push --force-with-lease

Notez qu’au contraire de force, force-with-lease permet de ne pas écraser de commits poussés par d’autres pendant vos modifications.

Cette commande ne sera pas executé ici, car le workshop est protégé.

Use case

Le cas suivant m’est arrivé récemment:

Je developpais une feature sur sa branche dédié et sur laquelle j’etais bien avancé.
J’y ai fixé différentes choses liées aux tests unitaires, et à la stricture des tests unitaires mais qui n’étais pas vraiment en lien avec ma feature. Au début, je pensais que ce serait anecdotique mais au bout de quelques commits, j’ai compris mon erreur.
Je faisais alors face à 2 issues sur ma branche, ma feature et une refacto du système de test en partie commité au milieu de ma feature.

J’ai donc stoppé cette branche sur ce point pour créér une issue et une branche dédié (issue de master). Une fois celle-ci terminée.
Il fallait donc que je repasse sur ma branche initiale, que je la nettoie des commits lié à la refacto des tests (qui par ailleurs étaient pas complètement iso avec la nouvelle branche).
Plutot que de chercher à cleaner le tout à coup de résolution de conflit, de patience et de café, j’ai opté pour une solution plus acrobatique:

  1. git rebase -i pour squasher toutes les modifs de ma branche (je n’avais pas grand interet à conserver les commits atomiques)
  2. git reset –soft HEAD~1 pour annuler le commit en question et conserver les modifications
  3. git reset pour un-stager les modifications
  4. suppression des modifications lié au début de refacto des tests

Ensuite, j’avais deux solutions qui se valent:

  • commit de la feature puis rebase de master pour merger la MR de la refacto des test
  • git stash puis rebase de master pour merger la MR de la refacto des test puis git stash apply pour récuperer les modifs sans les commiter

Laisser un commentaire