# Contrôle de version avec Git 

[Git](https://fr.wikipedia.org/wiki/Git) permet de faire du contrôle de version. La différence avec d'autres systèmes de gestion de version est son aspect distribué et son système de branche.

La première partie de ce document ressemble beaucoup à l'introduction à Git que vous avez normalement eu en APL.
Vous pouvez la relire très rapidement.
De manière générale, le [site officiel](https://git-scm.com/about) est une excellente source d'information.

La seconde partie contient plus de détails et des exercices pour aller plus loin, en particulier, comment à travailler à plusieur et une introduction aux branches, qui est un aspect majeur de git.

Tout au long de ce TP, je vous invite à conserver une petite feuille de papier ou un fichier texte dans lequel prendre des notes pour lister chaque commande de git et une description indiquant à quoi elle sert.

À la fin de ce TP vous avez un quizz et une fin cocasse pour vous motiver à aller jusqu'au bout.

## digressions : rsync

Notez que si vous travaillez seul, et si vous souhaitez juste synchroniser des arborescences il existe la commande 'rsync'.

Du temps ancestral d'avant dropbox et autre client de nuage, pour travailler seul sans home sur un serveur mais sur plusieurs machines, il existait 'rsync' une commande qui permet de synchroniser une arborescence manuellement ou régulièrement et automatiquement avec un démon. 

Il s'agit de 'green IT' avant l'heure puisque rsync ne va pas transmettre intégralement la nouvelle version de l'arborescence mais va envoyer en gros seulement les petites différences de chaque fichier. 

Inconvénient par rapport à git : on ne peut pas revenir à une version antérieure. 
Avantage par rapport à git : potentiellement beaucoup moins gourmand en bande passante.

## Git
Pour rappel, nous avons un serveur git au département
['https://dwarves.iut-fbleau.fr/gitiut/'](https://dwarves.iut-fbleau.fr/gitiut/)

Rendez-vous sur la page d'accueil de Gitea et identifiez-vous.  
NB : ce service acceptera les mêmes identifiants que les machines virtuelles proxmox.

Pour éviter de saisir votre identifiant et votre mot de passe à chaque fois que vous transmettez
une commande au serveur, je vous invite à suivre les étapes ci-dessous, si ce n'est pas déjà fait.

Une fois identifié  accédez aux paramètres de votre compte et mettez à jour le nom complet
et l'adresse e-mail principale. Nous allons nous assurer que `git` sur votre
machine et `Gitea` sur le serveur ont bien les mêmes informations. Dans un
terminal lancez les commandes suivantes (en utilisant les mêmes informations que sur `Gitea`):


```
    git config --global user.name "Votre nom"
    git config --global user.email "login@domaine.example"
    git config --global core.editor vim
```

NB. si vous voulez utiliser un autre éditeur, vous pouvez tout à fait remplacer 'vim' par 'emacs' ci-dessus.
(trivia voir [editor war](https://en.wikipedia.org/wiki/Editor_war)). C'est l'éditeur qui sera choisi en cas de besoin par git, par exemple si vous faites un 'commit' sans donner l'option '-m "un message"' git vous demandera d'éditer le message en lançant un éditeur. 
Il vaut mieux donc choisir un éditeur que vous maitrisez un minimum.

Pour sécuriser les échanges entre votre machine et le serveur, il va vous falloir une
paire de *clés de cryptage*. Tapez la commande suivante et validez sans entrer de
réponse si des questions vous sont posées :

```
    ssh-keygen -t rsa -C "login@domaine.example"
```

Ceci a créé parmi vos fichiers une clé privée et une clé publique. Nous devons maintenant
donner la clé publique à `Gitea`. Commençons par afficher la clé publique :

```
    cat .ssh/id_rsa.pub
```

Vous pouvez copier cette clé en la sélectionnant à la souris (aucun raccourci clavier
n'est nécessaire). Revenez ensuite dans le navigateur et parmi les paramètres de votre
compte `Gitea`, choisissez *Clés SSH*. Cliquez sur *Ajouter une Clé* puis copiez la clé dans la boîte *Contenu* 
(en appuyant sur la molette de la souris). 
Choisissez le nom que vous voulez pour la clé (vous pouvez par exemple l'appeler *TP* si c'est la clé générée dans 
une machine des salles de TP).


## Création d'un dépôt en ligne

Chaque projet auquel vous participez doit avoir un dépôt dédié (et même plusieurs,
comme nous le verrons par la suite). Vous pouvez aussi avoir un dépôt dédié pour chaque cours, surtout si il y a des TPs.

Créez un nouveau dépôt en utilisant le bouton qui ressemble à un '+'.

Si c'est un projet, il faut probablement suivre une convention de nommage pour que le correcteur retrouve le projet facilement.
Rendez-le *privé*. Donnez les accès adaptés au(x) correteur(s) et aux autre(s) membre(s) de votre projet.

Dans la catégorie '.gitignore' vous pouvez choisir plusieurs modèles en fonction de votre projet et du type de fichiers qu'il va contenir 
(ou plus exactement de ceux que vous ne souhaitez pas sauvegarder). 

Vous pouvez ensuite valider et constater que vous avez un dépôt contenant deux fichiers
1. un fichier texte pour la license
2. un fichier texte 'readme.md' qui devra à terme contenir un bref descriptif de l'architecture de votre projet.

Le dépôt en ligne est prêt et vous n'avez plus vraiment besoin d'accéder à `Gitea` par son interface web à partir de maintenant.

## Cloner un dépôt localement

Pour cloner un dépôt localement sur votre machine, il vous suffit de regarder son adresse sur un serveur git et d'y avoir accès.

Par exemple, pour le dépôt du projet 'PouetPouet' de l'utilisateur 'toto' sur le serve"ur git de l'iut, il faut saisir 

```
    git clone gitea@dwarves.iut-fbleau.fr:toto/PouetPouet.git
```
Si il s'agit de votre dépôt que vous venez de crééer, vous allez voir si vous regardez tous les fichiers y compris les fichiers cachés (par exemple en faisant `ls -a`) :
* un répertoire '.git'
* un fichier texte '.gitignore'
* un fichier texte en markdown 'README.md'


Le dépôt local à proprement parler est dans le répertoire `.git`.
Vous n'avez pas besoin de le visiter, mais vous pouvez y jeter un coup d'oeil si vous êtes curieux. 
Le répertoire `PouetPouet` est le répertoire de la copie de travail des fichiers du dépôt.

Attention : il faut éviter de cloner un dépôt git dans un dépôt git. 
C'est possible de le faire, mais c'est un [mécanisme avancé de git](http://git-scm.com/book/en/v2/Git-Tools-Submodules).

## Alternative : créer un dépôt local et le pousser dans un dépôt distant vide.

Alternativement, vous pouvez utiliser `git init` pour créer un dépôt local.  
Ensuite il faut utiliser `git add` et `git commit` pour indexer des fichiers puis faire un point de sauveguarde local (comme expliqué ci-dessus).

Ensuite `git remote add origin` suivi de l'url du dépôt sur le serveur   
(NB : origin est le nom que git donne au dépôt du serveur distant par défaut; il est parfois possible d'avoir plusieurs dépôts distants)  
pour indiquer le nom du dépôt distant.

Finalement il faut faire `git push -u -f origin master`.
(NB : master est le nom de la branche principale, ici celle du dépôt local, l'option -u va éviter d'avoir à répéter origin et master par la suite dans les commandes).

___

# Utiliser Git au quotidien.

Dans cette partie nous illustrons l'usage de git à travers quelques exemples et exercices.

## Git en local
L'utilisation de base pour un travail seul en local avec git sans sauvegarde distante est la suivante :

* travail sur un fichier local `monFichier`
* ajout de ce fichier local dans l'index (`git add`)
* point de sauveguarde  (`git commit`)

### Exemple

```shell
emacs monFichier
git add monFichier
git commit -m "un message court expliquant le changement de monFichier
```

Si vous travaillez seulement en local, ceci suffit à avoir un sytème de version permettant de *revenir en arrière*.

Pour revenir en arrière sur 1 fichier il faut utiliser `git restore`.
En option vous indiquez la version `-- source <le numéro de version>` puis `monFichier`
(NB. c'est l'analogue d'un rollback de base de données).

Pour connaître le numéro d'une version, vous pouvez utiliser `git log`.

Si vous êtes un peu perdu `git status` donne beaucoup d'information et suggère des commandes.
Vous pouvez lire le man sur ces commandes ou regardez les docs en ligne.

Remarque : 'git restore' est relativement récent et avant il y avait 'git checkout'.
Cette seconde commande a beaucoup d'usage, ce qui contrevient à l'esprit unix une commande pour un usage. 
Sauf si vous maîtrisez bien 'git checkout', mieux vaut utiliser les divers nouvelles commandes qui la remplacent 
comme 'git restore' et 'git switch'. 


### Exercice

* éditez un nouveau fichier `Git.md`.
* y insérer le contenu ci-dessous.
         
         Git is a term of insult denoting an unpleasant, silly, incompetent, annoying, senile, elderly or childish person.         
         As a mild oath it is roughly on a par with prat and marginally less pejorative than berk. Typically a 
         good-natured admonition with a strong implication of familiarity, git is more severe than twit or idiot 
         but less severe than wanker, arsehole or twat when offence is intended. 
         
* sauver le fichier.
* saisir `git status`
* indexer ce fichier.
* créer un point de sauveguarde avec le message "git definition"
* saisir `git log`

En cette période de jubilée de platine de sa majesté, il est difficile de laisser visible un tel texte.
Nous allons maintenant faire un nouveau point de sauveguarde après avoir transformé le contenu du fichier en rot13.

* pour faire rot13 vous pouvez utiliser `cat Git.md | tr 'A-Za-z' 'N-ZA-Mn-za-m'`
* Ensuite refaites un nouveau point de sauveguarde avec le message "vg qrsvavgvba".

Le jubilé étant passé et Bojo étant toujours premier ministre, on décide de revenir à la version antérieure.

* restorer le contenu du fichier `Git.md`.

## Git en local et sur serveur distant.

Avec un serveur distant, vous pouvez normalement garantir de conserver tout votre travail qui est sauvé sur le serveur git, même si votre ordinateur personnel vous lâche.

Si vous avez plusieurs machines / comptes, il faut faire un `git pull` pour récupérer la version du serveur.

Ensuite vous travaillez en local comme ci-dessus (cycle édition, `git add`, `git commit`).

Dès que vous voulez sauver sur le serveur, il faut faire un `git push`.

Remarque : vous n'êtes pas obligé de faire un push pour chaque commit local.
Le commit local sert à faire des undo alors que le push sert à montrer des changements relativement conséquents (potentiellement utile pour d'autres lecteurs que vous le jour où vous travaillez à plusieurs).
Voir [cette question](https://stackoverflow.com/questions/10565217/do-you-push-every-single-commit) pour plus de détails.

### Exercice

* On reprend l'exercice précadent avec un fichier 'Git2.md' contenant 2 lignes de plus que précédemment.
   
    Git is a term of insult denoting an unpleasant, silly, incompetent, annoying, senile, elderly or childish person.         
    As a mild oath it is roughly on a par with prat and marginally less pejorative than berk. Typically a 
    good-natured admonition with a strong implication of familiarity, git is more severe than twit or idiot 
    but less severe than wanker, arsehole or twat when offence is intended. 
   
    The term is used by Graham Chapman in the Monty Python sketch "Argument Clinic".

    The term is also frequently used by the character Derek 'Del Boy' Trotter in TV series Only Fools and Horses. 

* indexer le fichier, sauver le fichier, faire un commit local.
* faire du rot13, indexer, sauver le fichier, faire un commit local.
* faire un push
* saisir `git log` en local
* allez voir les révisions sur l'interface web de gitea
* faites un restore en local.
* regardez si il y a des effets sur le dépôt local.
* regardez si il y a des effets sur le dépôt distant via l'interace web.

## Nous travaillons à deux

Vous n'êtes pas obligé de travailler à deux pour cet exercice, ni même d'avoir deux machines clientes.
Vous pouvez simuler la seconde personne en vous mettant dans un autre répertoire sur votre machine et en y clonant le dépôt.

Dans cet exercice nous simulons un conflit de version sur un fichier.
En gros vous faites un `git push` et le message vous indique qu'il y a un conflit et comment le résoudre.
Il faut faire un `git pull`. Git essaye de faire la fusion automatique et vous indique un conflit.
Vous devez éditer le fichier qui contient maintenant très clairement les lignes divergentes de l'autre version.
Vous faites votre selection, sauvez le fichier, l'indexer, commit puis pousser le changement.

Normalement ceci fonctionne assez bien pour des choses simples, en particulier si vous changez des lignes différentes d'un fichier.

### Exercice
* Ajouter les fichier du TP noté 'Memoire.java' et 'MaMemoire.java' (dans l'archive `Shadockv0.zip` sur `BUT1-Test-Public/`).
* Les indexer
* Faire un commit
* pousser sur origin
* Donner accès en écriture à un collègue.

En parallèle sur vos machines respectives
* Le premier corrige les bugs de 'add' et 'contain' (avec commentaire BUGFIX etc).
* indexer, commit.

* Le second procède de même sur 'remove' et 'size'.

Puis dans cet ordre:
* Le second pousse.
* Le premier essaye de pousser.
* Le premier résoud le conflit en faisant 
    * un `git pull`, 
    * édition de `MaMemoire.java` pour avoir bien version cohérente du fichier appliquant les changements des 2 personnes
    * un `git add`, `git commit` puis un `git push`

L'étape d'édition est probablement inutile si vous avez changé des lignes différentes du fichier.

### Exercice

Reprendre l'exo précédent, mais cette fois ajoutez chacun des changements sur des lignes communes.
Par exemple vous changez chacun la javadoc de la même méthode ou bien vous changez le nom de la même variable dans la même méthode.
Normalement la résolution automatique du conflit et la fusion des fichiers va échouer et vous allez être obligé de faire la fusion manuelle.


## Les branches

La spécificité de git par rapport à d'autres système de contrôle de versions est que git encourage très fortement l'utilisation très fréquente de branches et que sous le capot il s'agit de quelque chose d'assez léger.

Pour fabriquer une branche, on va faire   
`git branch LeNomDeMaBranche`  
Pour changer de branche, on peut utiliser `git checkout` mais je vous recommande la commande plus moderne qui ne sert qu'à changer de branche qui s'appelle `git switch`.
Par exemple, pour basculer sur cette branche qu'on vient de créer, on va faire  
`git switch  LeNomDeMaBranche`  

Notez que vous aviez avant d'ajouter cette nouvelle branche, une branche par défaut qui se nomme `master`.

Dans la suite je vais supposer que chaque branche est dans un état propre, à savoir tous les fichiers utiles ou changés sont indexés (`git add`) et il y a un point de sauveguarde local (`git commit`).
Ceci permet de ne jamais perdre son travail quand on bascule de branche en branche.

Vous pouvez changer à tout moment de branche avec `git switch`.

Pour fusionner le travail d'une branche dans une autre branche, il faut vous placer dans la branche dans laquelle vous souhaiter injecter le changement et utiliser `git merge`.
Par exemple, pour insérer les changements de `LeNomDeMaBranche` dans `master` il faut juste faire :

```git
    git switch master
    git merge LeNomDeMaBranche
```

Si `LeNomDeMaBranche` est juste un successeur de `master` la fusion est très simple pour git puisque c'est juste du déplacement de pointeur (en gros `master` va pointer sur `LeNomDeMaBranche`).
Dans son jargon, git évoquera un `fast-forward`.  
Si au contraire `LeNomDeMaBranche` et `master` on un ancêtre commun car il y a eu des changements dans la branche principale `master` depuis que la branche ``LeNomDeMaBranche` a poussé, alors la fusion est un peu plus compliquée à mettre en oeuvre, mais pourra probablement être faite automatiquement.
Dans son jargon, git évoquera un `three-way merge`.  
Si git n'arrive pas à résoudre tout automatiquement, alors il va évoquer un `merge conflict`. Vous pouvez utiliser `git status`, changer à la main ce qu'il faut. 

Si vous avez intégré le travail d'une branche qui ne sert maintenant plus à rien, vous pouvez l'effacer en faisant :
`git branch -d LeNomDeMaBranche`
Attention : ce n'est pas quelque chose qu'on peut annuler

Le livre en ligne sur le site de git (disponible en plusieurs langues dont le français) est très bien fait et vous pouvez avoir plus de détails en regardant (ici)[https://git-scm.com/book/fr/v2/Les-branches-avec-Git-Les-branches-en-bref] et (la)[https://git-scm.com/book/fr/v2/Les-branches-avec-Git-Branches-et-fusions%C2%A0%3A-les-bases].


# Pour aller plus loin.

## Fin cocasse

[Jeu en javascript pour s'entraîner](https://learngitbranching.js.org/)


## Quizz

1. Qui a commencé le développement de git?
2. Comment les fichiers d'un dépôt git sont-ils indexés?
3. Faut il un serveur git centralisé?
4. Quelle commande permet-elle d'indexer un fichier?
5. Quelle commande permet-elle de récupérer le dernier point de sauvegarde depuis le dépôt distant?
6. Que veut dire 'origin' dans le contexte de 'git'?
7. Que veut dire 'master' dans le contexte de 'git'?
8. Quelle commande permet-elle de transmettre le dernier point de sauvegarde dans le dépôt distant?
9. Comment connaître le nom des points de sauvegarde?
10. Comment savoir où on en est quand on ne sait plus trop quoi faire?
11. What is the difference between a git and a plonker?
