TP Git
TP Git
Git
2 Principe de Git 4
2.1 Présentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.2 Quiz : Principe de Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3 Initialisation de Git 5
3.1 Création d’un dépôt local . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.2 Importation d’un dépôt distant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.3 Configuration du dépôt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.3.1 Identité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.3.2 Éditeur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.3.3 Couleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3.4 Gestion de l’authentification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.4 .gitkeep et .gitignore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.4.1 .gitkeep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.4.2 .gitignore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.5 Pour aller plus loin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.6 Quiz : Initialisation de Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
G. Subrenat - 1/70
TABLE DES MATIÈRES TABLE DES MATIÈRES
6 Branches 26
6.1 Préambule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
6.2 Création d’une branche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6.2.1 Point de départ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6.2.2 Création . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6.2.3 Changer la branche courante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
6.2.4 Nouveau commit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
6.2.5 Deuxième commit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
6.2.6 Retour sur la branche master . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.2.7 Un commit sur la branche master . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.3 Quelques commandes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
6.3.1 Commandes de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
6.3.2 Commandes risquées . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.4 Fusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.4.1 Fusion sans conflit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.4.2 Fusion sans conflit : fast forward . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.4.3 Fusion avec conflit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
6.4.4 Quelques commandes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6.5 J’ai une urgence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.6 Quiz : Branches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
7 Repositories distants 40
7.1 Créer et paramétrer un compte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.1.1 Création . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.1.2 Générer les clés SSH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.1.3 Enregistrer la clé SSH dans gitlab . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.2 Création et initialisation d’un projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.2.1 Repository distant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.2.2 Initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
7.3 Cloner un dépôt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.3.1 Clonage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.3.2 Configuration et fonctionnement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
7.3.3 Quelques commandes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
7.4 Principe de fonctionnement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
8 Conclusion 61
2 Principe de Git
2.1 Présentation
Git est un outil de versionnement. Autrement dit il garde une sauvegarde de toutes les versions d’un
projet et permet de visualiser à volonté les anciennes versions.
Il permet de travailler à plusieurs sur un projet et aide à gérer les conflits si plusieurs développeurs
modifient les mêmes fichiers.
Attention Git n’est pas un outil de gestion de projet, c’est uniquement un outil d’aide. En aucun il ne
remplace un chef de projet et une gestion rigoureuse.
Git utilise des branches. Cela permet de développer des versions “parallèles” du code (pour développer
et tester une fonctionnalité, pour explorer une solution, ...). Ensuite, si nécessaire, une nouvelle branche
est fusionnée avec la branche “principale”.
Git manipule des repositories (dépôts en français) distants ainsi qu’un repository local, ce qui sera étudié
plus tard dans le document.
Git peut tout à fait être utilisé pour un projet géré par un seul développeur, et c’est même recommandé.
Dans ce cas c’est la partie versionnement qui est utilisée (avec des branches si nécessaire).
Chaque version du projet concerne une seule fonctionnalité/action même si elle est minime. Il est fréquent
qu’un développeur crée plus de dix versions par jour.
On ne fait pas une version pour faire une sauvegarde ; chaque version doit être un code correct (dans la
mesure du possible).
Question 2
une seule réponse possible
Il est intéressant d’utiliser Git pour un projet avec un seul développeur.
- vrai
- faux
Question 3
une seule réponse possible
À quelle fréquence crée-t-on des versions ?
- très rarement : un logiciel a très peu de versions dans son existence
- chaque fois qu’un fichier source est définitivement terminé
- une fois par jour pour faire une sauvegarde
- toutes les heures pour ne pas perdre plus d’une heure de travail en cas de panne
- très souvent, chaque fois qu’une fonctionnalité est implémentée et testée
Question 4
une seule réponse possible
Git est très peu utilisé.
- vrai
- faux
Question 5
une seule réponse possible
Il existe des logiciels comparables à Git.
- vrai
- faux
3 Initialisation de Git
Hypothèses :
- Nous nous plaçons dans le cas où un seul utilisateur utilise Git et que la gestion est locale à la
machine de l’utilisateur (le dépôt est local et il n’y a pas de dépôt distant).
- Nous partons d’un projet vide.
La première étape est de créer un répertoire et de se placer dedans 2 .
$ mkdir PROJET_C
$ cd PROJET_C
$ pwd
/home/gilles/TP/PROJET_C
$ ls -lA
total 0
On peut alors créer le dépôt :
$ git init
Dép^
ot Git vide initialisé dans /home/gilles/TP/PROJET_C/.git/
$ ls -lA
drwxrwxr-x 7 gilles gilles 4096 oct. 24 20:48 .git
On note la création du répertoire .git qui contient toute la configuration du dépôt. Il contiendra aussi
tout l’historique du projet.
Il ne faut pas modifier directement ce répertoire sinon on risque des destructions de données et/ou des
dysfonctionnements graves. Toute action doit passer les commandes git.
2. histoire de commencer en douceur
Remarque : si l’on veut travailler sur un projet existant et hébergé sur une machine distante, il faut le
rapatrier sur la machine locale avec la commande “git clone”. Ensuite le fonctionnement que nous allons
décrire sera le même.
Note : pour avoir une aide sur une commande git, la syntaxe est la suivante :
$ git help <cmd>
Par exemple 3 :
$ git help help
ou encore 4 :
$ git help init
Ça y est, le dépôt est prêt, enfin presque.
Hypothèses :
- Nous voulons intégrer un projet, géré par Git, qui est présent sur internet (repository distant).
- Nous partons donc d’un projet en cours de développement.
La première étape est d’importer le repository distant en local. Prenons comme exemple un projet de
test de gitlab 5 :
$ git clone git@[Link]:gitlab-org/[Link]
Clonage dans ’gitlab-test’...
Permission denied (publickey).
fatal: Could not read from remote repository.
/home/gilles/TP/gitlab-test
$ ls -lA
drwxr-xr-x 8 gilles gilles 4096 2020-10-26 16:05 .git/
-rw-r--r-- 1 gilles gilles 100 2020-10-26 16:05 .gitattributes
-rw-r--r-- 1 gilles gilles 241 2020-10-26 16:05 .gitignore
-rw-r--r-- 1 gilles gilles 270 2020-10-26 16:05 .gitmodules
-rw-r--r-- 1 gilles gilles 22846 2020-10-26 16:05 CHANGELOG
...
drwxr-xr-x 2 gilles gilles 4096 2020-10-26 16:05 with space/
Pour envoyer des modifications sur le repository de gitlab il faut avoir des droits supplémentaires.
Que l’on ait créé un dépôt local ou importé un dépôt distant, le reste de la section s’applique in-
différemment.
La seule différence aura lieu lorsque des modifications seront apportées au projet : il y aura une commande
supplémentaire pour transmettre les changements au dépôt distant et ainsi les partager avec les autres
développeurs.
Reprenons les explications en se basant sur le dépôt local créé à la section précédente.
3.3.1 Identité
Lorsqu’une nouvelle version est créée 7 , elle est associée au développeur qui l’a faite afin que les autres
développeurs aient connaissance de l’auteur de chaque modification ; Git a donc besoin de connaı̂tre le
propriétaire du dépôt local 8 (nom et adresse mail).
Pour le montrer, lançons la commande suivante (qui sert à créer une nouvelle version 9 et que nous
détaillerons plus tard) :
$ git commit
*** Please tell me who you are.
Run
git config --global [Link] "you@[Link]"
git config --global [Link] "Your Name"
to set your account’s default identity.
Omit --global to set the identity only in this repository.
Il faut lancer les deux commandes proposées. Utiliser l’option --global indique que cette configuration
s’appliquera aux autres projets Git du compte utilisateur courant. Partons de l’hypothèse que nous
voulons une configuration locale au projet en cours :
$ git config [Link] "gilles@[Link]"
$ git config [Link] "Gilles"
Et maintenant :
$ git commit
Sur la branche master Validation initiale rien àvalider
Il n’y a plus d’erreur (et accessoirement Git nous indique qu’il n’est pas possible de créer une nouvelle
version puisqu’il n’y a eu aucune modification).
3.3.2 Éditeur
Chaque fois que l’on crée une nouvelle version, il faut saisir un message explicatif dans un éditeur de
texte. L’éditeur choisi par défaut par Git pourrait ne pas convenir ; il est possible d’en choisir un autre.
Première solution avec la variable d’environnement GIT EDITOR (à mettre dans le .bashrc) :
7. on parle de commit
8. Si le projet a été importé via un git clone, on parle malgré tout d’un dépôt local : le répertoire courant n’est qu’une
copie du dépôt distant, et donc est bien local. Il y aura une commande spéciale (git push) pour transmettre les modifications
locales vers le dépôt distant.
9. Notons que cela n’a pas de sens puisque nous n’avons encore rien rajouté dans le projet.
$ export GIT_EDITOR=[Link]
Seconde solution (que nous choisissons) avec le fichier de configuration de Git :
$ git config [Link] gedit
L’option --global est possible.
La première solution est prioritaire sur la seconde. Le fichier de configuration local est prioritaire sur le
fichier de configuration global.
3.3.3 Couleur
Pour activer la couleur dans les affichages de Git, le paramètre est [Link] qui peut prendre les valeurs
false, auto (valeur par défaut), true et always. Par exemple :
$ git config [Link] true
(attention le contenu de cette sous-section est incertain, à vérifier avec d’autres sources)
Lorsqu’on se connecte à des dépôts distants, ceux-ci demandent généralement une phase d’authentification
qu’il faut potentiellement renseigner à chaque accès au dépôt. Il peut rapidement être pénible de devoir
saisir fréquemment ses identifiants.
Git intègre un dispositif de gestion des identifiants avec le paramètre [Link] (toujours avec la
possibilité de l’option --global).
Pour Windows :
$ git config [Link] wincred
Pour Linux :
$ git config [Link] cache
Pour Mac :
$ git config [Link] osxkeychain
Beaucoup de serveurs Git (dont gitlab) autorisent l’authentification par clés SSH, ce qui dispense des
phases d’authentification explicites.
3.4.1 .gitkeep
Git ne gère que les fichiers mais pas les répertoires, autrement dit il ignorera les répertoires vides.
La seule solution pour traquer un répertoire vide est ... qu’il ne soit pas vide.
L’habitude est que l’on mette à l’intérieur un fichier vide nommé .gitkeep 10 .
Ce fichier peut être supprimé une fois que d’autres fichiers ont été ajoutés au répertoire.
Une autre solution est d’avoir un fichier vide nommé .gitignore avec le même effet. La différence est que
ce dernier fichier a une signification particulière pour Git (cf. ci-dessous).
10. N’importe quel autre nom de fichier aurait le même effet, ce n’est qu’une convention.
3.4.2 .gitignore
Tous les fichiers n’ont pas vocation à être gérés par Git, i.e. n’ont pas être sauvegardés dans les dépôts.
Par exemple :
- les produits d’une compilation : fichiers objets, exécutables
- les fichiers de sauvegarde temporaires des éditeurs
- ...
Toutes les règles décrivant les fichiers ignorés doivent se trouver dans des fichiers nommés .gitignore. Il
peut y avoir des fichiers .gitignore dans plusieurs répertoires ; la portée des règles d’un tel fichier est son
répertoire ainsi que les sous-répertoires descendants.
Voici un exemple simple de fichier .gitignore :
# tous les fichiers objets
*.o
4.1 Préambule
Nous somme dans l’hypothèse où il n’y a qu’une seule branche (généralement appelée master) dans
laquelle vont se succéder linéairement les différentes versions.
Nous nous plaçons dans la configuration suivante :
- Les données sont centralisées (cf. ci-dessous) dans un repository distant (par exemple hébergé par
gitlab) qui sert de référence.
- Nous avons une copie dans le repository local (obtenu par “git clone”).
- Nous sommes le seul développeur actif. Autrement dit le repository distant n’est pas modifié pendant
que nous travaillons en local. Ainsi nous ne gérons pas, dans un premier temps, les conflits 11 avec
les autres développeurs.
Nous parlons de repository central, mais Git est un outil décentralisé. Ceci dit pour simplifier les expli-
cations nous supposons que :
- Il y a un repository central et distant qui sert de référence.
- Chaque développeur travaille dans son repository local et se synchronise uniquement avec le repo-
sitory central.
Avertissement : pour l’instant nous avons uniquement un repository local (obtenu par “git init”) et
nous ne pourrons pas faire les instructions manipulant le repository distant. Elles seront tout de même
indiquées pour information.
La démarche pour créer une nouvelle version est la suivante :
11. au sens Git du terme
Rappel il n’y a pas de repository distant, donc les affichages des commandes Git seront plus courts qu’avec
un projet partagé.
Le dépôt est complètement vide, mais il faut prendre tout de suite les bonnes habitudes et on vérifie que
l’état du dépôt est “propre”.
$ git status
Sur la branche master
Validation initiale
rien à valider (créez/copiez des fichiers et utilisez "git add" pour les suivre)
12. Même si un développeur doit savoir s’il a un développement en cours.
13. cf. explication ci-dessus sur la notion de central et décentralisé
14. Avec nos hypothèses, comme nous sommes le seul développeur, nous devons avoir de fait la dernière version en local.
En effet, il faut d’abord indiquer à Git l’ensemble des fichiers, parmi ceux qu’il propose, qu’il doit intégrer
à la nouvelle version. En l’occurrence, comme on les veut tous (il n’y en a qu’un !), on indique la commande
globale :
$ git add .
Mais la nouvelle version n’est toujours pas créée, voyons ce qu’il dit de l’état du repository :
Le fichier .gitignore est maintenant indexé et nous pouvons enregistrer, en local, la nouvelle version :
$ git commit
[master (commit racine) b1bea7d] Création du fichier .gitignore : non prise en compte des .o
1 file changed, 2 insertions(+)
create mode 100644 .gitignore
15. et bien entendu ... il ne faut pas confondre vitesse et précipitation !
Git a lancé l’éditeur choisi lors de la configuration (gedit dans le cas présent) pour écrire un message
expliquant la nouvelle version. Il est indispensable de saisir un message le plus clair et complet possible,
et il ne faut pas hésiter à mettre plusieurs lignes.
Git interdit les messages vides, et des messages du style “ ” ou “modif” sont à proscrire.
Voici ce qu’a proposé l’éditeur de texte. Seule la première ligne a été saisie :
Et on obtient bien un message d’erreur adéquat puisque nous n’avons pas de repository distant.
Pour la suite, tant que nous n’utiliserons pas de repository distant, nous passerons sous silence les phases
de pull et de push.
Il faut s’assurer que le repository local est propre et qu’on a bien rapatrié la dernière version si on utilise
un repository distant (cf. section précédente).
Nous allons programmer un “hello world !” et le tester avant d’en faire une nouvelle version.
Rappelez-vous qu’on crée une nouvelle version avec un code correct et testé.
Voici ce que peut donner une session de travail :
Une fois la fonctionnalité terminée et testée 16 , il faut créer une nouvelle version. Mais avant cela, il faut
16. manifestement ce n’était pas inutile
Si on avait créé la nouvelle version sans vérification, l’exécutable y aurait été inclus. On s’aperçoit donc
que le fichier hello ne doit pas intégrer la nouvelle version. Pour cela trois possibilités :
- on le supprime avant le commit
- on le rajoute au .gitignore
- on fait un “git add” sur mesure.
Nous choisissons la troisième solution, suivie d’un commit :
Par la même occasion, nous voyons qu’il est possible d’intégrer directement un message court lors du
commit, sans passer par l’éditeur de texte.
Notons également le numéro associé à ce commit : d88c921.
Et lorsqu’il y aura un dépôt distant, il faudra penser au push.
Nous allons étoffer notre projet avec un module manip (couple manip.h et manip.c) et un Makefile.
Tout d’abord le codage et les tests :
Quelques remarques :
- On voit bien la différences entre les fichiers modifiés et les fichiers nouvellement créés.
- Les .o n’apparaissent pas dans l’état du repository : c’est grâce au .gitignore créé plus tôt.
Nous allons supprimer tous les fichiers inutiles :
Nous arrivons dans une partie particulièrement puissante de Git : consulter l’historique et naviguer dans
les versions.
Dans la suite du document, nous utiliserons fréquemment cette option afin de limiter la taille des captures
d’écran.
L’option --decorate permet d’afficher sur l’historique :
- le nom des branches (une seule pour l’instant pour nous : master)
- sur quelle version on travaille : HEAD (lorsqu’on saura se déplacer dans l’historique)
Nous allons rapidement voir que cette notion de nom de branche et de HEAD est primordiale : ne pas
parfaitement les comprendre peut conduire à des pertes de données (notion de commit orphelin).
L’option --branches avec sa compagne --graph permettent de visualiser :
- la totalité de la branche courante lorsqu’on se déplace dedans
- toutes (ou presque) les autres branches du projet.
Ce sera plus clair avec les exemples qui ne tarderont pas à venir. Dans l’état actuel, elles n’apportent pas
de nouvelles informations.
La commande est :
$ git checkout <sha-1>
Pour être précis, la commande “git checkout” permet aussi de naviguer dans les branches.
Par exemple nous allons revenir à la première version (celle où il n’y avait que le fichier .gitignore) qui a
pour numéro b1bea7d.
Dans un premier temps, vérifions que nous sommes sur la version la plus récente :
Analyse :
- C’est la référence (on parle aussi de pointeur 18 ) HEAD qui indique sur quelle version nous sommes.
- Comment savoir que nous sommes sur la version la plus récente ? C’est la référence master qui nous
l’indique. La référence master pointe toujours (par définition) sur la version la plus récente. Comme
HEAD et master pointent sur la même version 19 , alors la version courante est bien la plus récente.
- Le listing du répertoire montre bien les derniers fichiers.
Changeons maintenant de version :
Analyse :
- Pour l’instant nous laissons de côté la notion de “detached HEAD” ou de création d’une nouvelle
branche. Disons juste que cela signifie qu’il ne faut pas faire des modifications et des commit, à
moins de créer une branche.
- La dernière ligne nous rappelle le message associé à la version que nous venons de restaurer.
Le contenu du répertoire est maintenant :
Nous ne voyons que l’historique jusqu’à la version choisie, ce qui nous permet d’être dans les conditions
de l’époque. Ceci dit il est souvent préférable de situer la version courante dans l’ensemble des versions ;
une première utilité de l’option --branches (que nous associerons systématiquement à l’option --graph)
apparaı̂t alors.
18. Le terme de pointeur n’est pas un hasard comme nous le verrons dans une prochaine section.
19. Si on regarde attentivement, HEAD ne pointe pas au même endroit que master, mais HEAD pointe sur master ;
HEAD est donc ici un pointeur sur un pointeur. Cette distinction est importante comme nous le verrons par la suite.
20. cf. malgré tout, la commande : git stash
Nous voyons que notre version courante (marquée par HEAD) est remontée de deux positions dans le
temps par rapport à la version la plus récente de la branche courante 21 (marquée par master).
Après avoir regardé le code source de la version 1 de notre projet, il est temps de revenir à la version la
plus récente pour continuer à développer.
Il y a alors deux commandes possibles :
$ git checkout ffcfa7e # NON
ou
$ git checkout master # OUI
La première est à proscrire 22 . Pour revenir en haut d’une branche, on passe toujours le nom de la branche
à la commande checkout et jamais le SHA-1 (nous expliquerons en détail ces subtilités dans une prochaine
section) :
Pourquoi la commande “git add” est nécessaire et pourquoi tous les fichiers créés ou modifiés ne sont pas
directement intégrés à une nouvelle version ?
- Cela réduit le trafic réseau.
- Cela réduit la place occupée sur disque.
- Cela permet au développeur d’avoir une double validation avant de créer la version.
- Des informations inutiles perturberaient les autres développeurs et généreraient des versions inutiles.
Question 5
une seule réponse possible
Les SHA-1 pour identifier les versions sont trop risqués car il y a des risques de conflits (i.e. que deux
versions aient le même identifiant).
- vrai
- faux
Question 6
une seule réponse possible
On peut identifier une version par autre chose qu’un SHA-1.
- vrai
- faux
Question 7
plusieurs réponses possibles
La commande “git checkout” permet de :
- naviguer dans l’historique des versions d’une branche
- changer la branche active
- supprimer physiquement une version devenue inutile
- créer une nouvelle branche
Question 8
une seule réponse possible
On peut modifier une version ancienne et faire des commit ?
- vrai
- faux
5.1 Préambule
L’identification des versions avec des SHA-1 est assez claire, mais il reste des zones d’ombre au sujet des
références que sont HEAD et master (et les noms des branches en général).
Les SHA-1 sont “physiquement” (i.e. indéfectiblement) liés à un commit 23 : il est impossible de changer
le SHA-1 d’un commit.
Un nom de branche (master pour l’instant) n’est qu’un pointeur sur un commit. Et comme tout pointeur
il est susceptible de se déplacer le long des commit.
Dans une utilisation normale 24 , ce pointeur se déplace automatiquement et désigne toujours le commit
le plus récent de la branche.
HEAD est un pointeur particulier :
- il se déplace à volonté
- il désigne la version de travail courante
- il peut pointer sur un SHA-1 (comme le fait un nom de branche)
- et plus perturbant (pour l’instant) il peut pointer sur un nom de branche (donc un pointeur sur un
pointeur)
23. commit ≡ version et de fait commit ≡ version ≡ SHA-1
24. qu’il est fortement conseillé de suivre
Vraisemblablement ce n’est pas clair, et nous allons expliciter chaque point dans la suite.
Sur une branche, les versions/commit sont reliés entre eux avec un chaı̂nage arrière simple : la version la
plus récente pointe sur l’avant-dernière qui elle-même pointe sur l’antépénultième et ainsi de suite jusqu’à
la première version du projet.
Une autre façon est de dire que les versions sont gérées comme une pile.
Quand nous gérerons plusieurs branches, nous verrons que les versions forment un arbre 25 chaı̂né “à
l’envers”, i.e. un fils pointe sur son père, et la racine de l’arbre est le premier commit.
Nous avons tout d’abord ajouté deux commit pour arriver à un total de 5 versions et rendre ainsi les
schémas plus clairs :
da57328
b1bea7d
ffcfa7e
Si l’on pointe sur la version la plus récente, alors grâce au chaı̂nage on peut revenir à n’importe quelle
version de la branche.
En revanche, si on pointe sur une version intermédiaire, il n’est plus possible de revenir à la version la
plus récente en se servant du chaı̂nage.
Une solution serait de connaı̂tre le SHA-1 de la version la plus récente. Mais :
- c’est particulièrement pénible à se souvenir
- et il change à chaque nouvelle version.
- si on l’oublie, le projet est perdu 26 .
Bref ce n’est pas la solution.
Aussi chaque branche a un pointeur dédié qui pointe en permanence sur le SHA-1 de la version la plus
récente. Ce pointeur a pour nom le nom de la branche 27 : dans notre cas c’est master.
D’une part il est plus facile de se souvenir du nom des branches, d’autre part il existe une commande (git
branch) qui donne la liste de toutes les branches.
Si on utilise correctement Git ce pointeur se déplace automatiquement chaque fois qu’une nouvelle version
est créée.
25. ou plus exactement un DAG
26. Ce n’est pas tout à fait vrai, il est possible d’obtenir la liste de tous SHA-1, y compris les orphelins.
27. En fait c’est le contraire : le nom de la branche est déterminé par le nom du pointeur.
74d1180
d88c921
da57328
b1bea7d
ffcfa7e
master
Détruire un branche revient juste à supprimer le pointeur et en aucun cas les données.
Cependant si on détruit la branche (et c’est assez facile à faire), on ne peut plus accéder aux versions (à
part des manipulations peu aisées, cf. commande “git fsck”). On parle alors de SHA-1 orphelins.
Il reste à désigner la version courante de travail. Pour cela il y a un pointeur dédié nommé HEAD.
En règle général HEAD pointe sur la version la plus récente d’une branche, autrement dit au même
endroit que le pointeur de branche (master pour l’instant dans notre cas) ; tout simplement parce que
habituellement on travaille sur la dernière version d’un projet.
Mais nous avons vu, avec la commande “git checkout”, que HEAD pouvait pointer sur n’importe quel
autre commit plus ancien.
Nous allons commencer par expliciter ce dernier cas, même si ce n’est pas le plus fréquent. Supposons
que nous ayons tapé la commande :
On note à nouveau un message anxiogène avec cette notion ésotérique de “detached HEAD”.
Nous arrivons alors dans la configuration :
74d1180
d88c921
da57328
b1bea7d
ffcfa7e
HEAD master
Rappelons que tous les nouveaux fichiers et toutes les modifications faits après cette version n’apparaissent
plus dans les répertoires de travail.
A priori on ne peut pas faire de commit puisqu’il y a déjà une version après la version courante. Aussi
généralement, lorsqu’on se positionne sur une version passée, on se contente de consulter.
Voyons cependant ce qu’il se passerait si on apportait une modification suivie d’un commit : il y aurait
bien une nouvelle version qui n’écraserait pas la version ffcfa7e mais qui apparaı̂trait à côté avec son
propre SHA-1 (inventé pour l’occasion) :
74d1180
d88c921
da57328
b1bea7d
ffcfa7e
master
8c31f5a
HEAD
On voit que le pointeur HEAD s’est naturellement mis sur la nouvelle version. Mais le problème principal
apparaı̂t : lorsqu’on remettra le pointeur HEAD en bout de branche master, ce nouveau SHA-1 sera
inaccessible (à moins de noter le SHA-1 sur un document extérieur ce qui n’est pas raisonnable).
L’expression “detached HEAD” est un peu plus claire : tous les commit créés seront détachés, autrement
dit inaccessibles.
Lorsque nous aborderons les branches multiples, nous verrons qu’il est possible de rendre ces nouveaux
commit accessibles.
Revenons à la situation d’origine :
74d1180
d88c921
da57328
b1bea7d
ffcfa7e
HEAD master
Il reste à revenir sur la dernière version de la branche master pour continuer à développer le projet. et il
y a deux possibilités.
Commençons par la mauvaise :
Pourtant aucun message ne nous alerte. En réalité nous étions déjà dans le mode “detached HEAD” et
nous le restons : Git ne nous met pas des messages alarmistes chaque fois.
Regardons de près la commande “git log” :
HEAD et master sont séparés par une virgule ce qui signifie qu’ils sont indépendants bien que pointant
sur le même commit. Nous sommes dans la configuration suivante :
74d1180
d88c921
da57328
b1bea7d
ffcfa7e
master HEAD
Si nous faisons un nouveau commit, nous arrivons sur la configuration (avec un SHA-1 inventé pour
l’occasion) :
74d1180
d88c921
da57328
184de39
b1bea7d
ffcfa7e
master HEAD
Un premier problème est que master ne pointe pas sur la dernière version de la branche ce qui est
incohérent avec un pointeur de branche.
Le deuxième problème (lié au premier) est que si on déplace HEAD sur une version passée, il n’y a plus
moyen de revenir sur la dernière version (à moins de noter ce SHA-1 sur un document extérieur) : nous
sommes exactement dans le même cas du commit fait précédemment à partir d’une version ancienne.
Bref pour revenir à la dernière version d’une branche, il ne faut pas utiliser le SHA-1 mais le pointeur de
branche (c’est la deuxième et bonne solution).
HEAD et master sont séparés par une flèche (et non plus une virgule) ce qui signifie que HEAD pointe
sur master qui lui-même pointe sur la dernière version. Nous sommes dans la configuration suivante :
74d1180
d88c921
da57328
b1bea7d
ffcfa7e
master
HEAD
Et voyons le log :
da57328
b1bea7d
2201dbf
ffcfa7e
master
HEAD
Et tout se passe bien car HEAD a transmis l’ordre au pointeur master de faire le commit ; master s’est
déplacé sur la nouvelle version entraı̂nant avec lui le pointeur HEAD.
En conclusion, pour éviter d’arriver à des configurations non voulues 28 , on peut se fixer la règle suivante :
“lorsqu’on fait un commit, le pointeur HEAD doit désigner un pointeur de branche (tel master) et jamais
directement un SHA-1”.
Question 2
une seule réponse possible
Détruire une branche supprime définitivement toutes les versions qui y sont rattachées.
- vrai
- faux
Question 3
une seule réponse possible
Un pointeur de branche (comme master) désigne toujours le même commit, sinon on risquerait de perdre
des données.
- vrai
- faux
Question 4
une seule réponse possible
Le pointeur HEAD, comme son nom l’indique, désigne toujours le premier commit d’une branche.
- vrai
- faux
Question 5
une seule réponse possible
Sur une branche le chaı̂nage des commit est à l’envers : le premier commit de la liste chaı̂née est le dernier
à avoir été créé.
- vrai
- faux
Question 6
une seule réponse possible
HEAD peut être un pointeur sur un pointeur, mais ça n’a pas de sens.
- vrai
- faux
Question 7
une seule réponse possible
Une mauvaise utilisation du pointeur HEAD peut conduire à un état incohérent du repository.
- vrai
- faux
6 Branches
6.1 Préambule
Le principe des branches est de pouvoir mener de front plusieurs versions du logiciel sans qu’elles influent
l’une sur l’autre. Voici quelques exemples pour en appréhender l’intérêt :
- explorer une extension sans connaı̂tre a priori sa faisabilité, et donc sans “polluer” le code principal
- avoir une version de production et une version de développement
- avoir une branche par fonctionnalité, i.e. un espace indépendant pour la développer et la tester
- ...
Voir aussi la notion de workflow.
En règle générale, lorsque le développement d’une branche est terminée, cette dernière est fusionnée avec
une autre, par exemple la branche principale.
À l’issue d’une fusion, si les deux branches ont modifié la même partie d’un fichier, un conflit peut
apparaı̂tre ; c’est alors au programmeur effectuant la fusion de gérer les codes contradictoires.
2201dbf
...
master
HEAD
6.2.2 Création
Créons une branche par exemple pour faire une version C++ de hello world. Et appelons cette branche
cplus :
cplus
da57328
2201dbf
...
master
HEAD
Une branche est tout simplement une nouvelle étiquette, mais qui pourra avoir ses propres commit.
Mais nous sommes toujours sur la branche initiale ; voici la commande pour changer la branche courante :
HEAD
cplus
da57328
2201dbf
...
master
Il s’agit de la même commande que pour revenir à un commit antérieur : checkout a pour utilité générale
de se déplacer dans l’arbre des versions.
Une dernière vérification :
avec les mêmes informations : la branche courante (HEAD) est bien cplus, et pour l’instant la branche
master est au même endroit.
et ajoutons la au repository :
HEAD
cplus
649ac99
da57328
2201dbf
...
master
Pour faire bonne mesure dans cette branche, rajoutons un commit par exemple pour faire un Makefile :
HEAD
cplus
2b786e4
649ac99
da57328
2201dbf
...
master
Avec le schéma :
cplus
2b786e4
649ac99
da57328
2201dbf
...
master
HEAD
Et une deuxième :
Notamment on s’aperçoit que le répertoire C++ n’est pas visible dans cette version.
Ajoutons une nouvelle fonctionnalité dans la branche master, par exemple pour avoir une fonction qui
souligne un texte :
Avec le schéma :
cplus
2b786e4
649ac99
da57328
7b6e894
2201dbf
...
master
HEAD
On note le rendu graphique en mode ASCII de l’arborescence. Et voici un rendu avec gitkraken 29 :
$ git checkout -b <nom branche>
Ces commandes peuvent mener à des pertes d’informations ou à des commit orphelins.
Elles sont particulièrement risquées 30 si plusieurs programmeurs travaillent sur le même projet.
Renommer la branche courante :
$ git branch -m <nouveau nom>
Note : on peut aussi renommer une branche distante.
Supprimer une branche fusionnée :
$ git branch -d <nom branche>
Note : la branche supprimée ne doit pas être la branche courante.
Note : avec l’option -D, il est possible de supprimer une branche non fusionnée ; on crée alors des orphelins.
La commande fsck permet de lister les commit orphelins. La commande checkout permet de les récupérer.
6.4 Fusion
Lorsqu’une branche arrive à son terme, i.e. que la programmation de la fonctionnalité est terminée, il est
nécessaire de la réintégrer dans une branche que l’on qualifiera de “principale” : c’est la fusion.
Si les deux branches ont modifié des fichiers différents, Git gère de manière autonome la fusion. Même si
les deux branches ont modifié le même fichier, mais à des endroits différents, Git reste autonome.
En revanche, si les deux branches ont modifié la même partie du fichier, Git est incapable de prendre une
décision : c’est au développeur faisant la fusion de gérer le conflit.
La démarche classique d’une fusion est la suivante :
- on choisit la branche qui va recevoir la fusion et donc être modifiée ; généralement on choisit la
branche dite “principale” (master ou develop par exemple).
- on indique le nom de la branche distante dont on veut récupérer les modifications.
- un nouveau commit est créé sur la branche courante.
- la branche distante n’est pas modifiée.
- on vérifie que le projet fonctionne toujours.
30. et donc déconseillées
cplus
2b786e4
649ac99
da57328
7b6e894
2201dbf
...
master
HEAD
Les deux fichiers créés dans la branche cplus sont intégrés dans la branche master. Le schéma devient :
cplus
2b786e4
649ac99
da57328
7b6e894
2201dbf
27fd381
...
master
HEAD
La branche master a été complétée avec un nouveau commit, alors que la branche cplus est restée in-
changée.
Avec une vérification textuelle :
Même s’il n’y a pas eu de conflit, il faut vérifier que le projet est toujours opérationnel.
Pour résumer le cas d’une fusion sans conflit :
- la fusion s’effectue dans la branche courante avec la commande :
$ git merge <nom branche distante>
- un nouveau commit est créé sur la branche courante
- la branche distante reste inchangée
Il y a un cas où aucun nouveau commit n’est créé lors de la fusion : lorsque la branche courante n’a pas
progressé depuis la création de la branche distante. Dans ce cas l’étiquette de la branche courante est
simplement déplacée au même niveau que la branche distante.
Supposons que l’on ait le schéma suivant :
cplus
2b786e4
649ac99
da57328
2201dbf
...
master
HEAD
cplus
2b786e4
649ac99
master
da57328
2201dbf
...
HEAD
cplus
2b786e4
649ac99
da57328
2201dbf
5aa33ae
...
master
HEAD
On peut vérifier que le nouveau commit a les mêmes informations que la branche cplus :
Lorsqu’il y a un conflit, le problème doit être résolu manuellement par le développeur faisant la fusion :
- soit il privilégie une des deux branches
- soit il mélange les deux
typo
2e0fb2c
...
5aa33ae
2ccf231
...
master
HEAD
Il est à noter qu’un message est proposé par défaut pour le commit.
Nous avons alors le schéma suivant :
typo
2e0fb2c
...
6c3b4ea
5aa33ae
2ccf231
...
master
HEAD
Il est nécessaire d’avoir au préalable installé un outil (tel kdiff3 par exemple).
Fusionner deux branches en gardant une structure linéaire (rejouer des commit) :
$ git rebase <nom branche distante>
C’est extrêmement dangereux, particulièrement dans un travail à plusieurs.
Renommer la branche courante :
$ git branch -m <nouveau nom>
Note : on peut aussi renommer une branche distante.
Voici le scénario :
- vous avez commencé à travailler sur une branche
- votre patron arrive avec une urgence à développer ... sur la branche courante
- il n’est pas possible de faire de commit
- que faire ?
La commande stash 31 est faite pour cela :
$ git stash
Cette commande sauvegarde et met de côté tous les changements depuis le dernier commit, puis revient
à l’état initial du commit.
Attention les nouveaux fichiers non traqués sont exclus de cette sauvegarde.
Pour récupérer les changements :
$ git stash pop
Il est possible qu’il y ait des conflits
Les commandes “git stash” sont empilables.
7 Repositories distants
Avertissement : le discours de cette section est orienté par une vision centralisée de l’utilisation de Git.
Dès que l’on veut travailler à plusieurs, il faut avoir un site d’hébergement commun (dit repository distant)
où chaque développeur va pouvoir déposer ses contributions et récupérer celles des autres.
Un repository local (a priori un seul par développeur) est une copie du repository distant afin de pouvoir
travailler sans perturber les autres développeurs.
Un repository local peut être, par rapport au repository distant, à la fois :
- en retard car les autres développeurs ont ajouté des contributions non encore rapatriées en local,
- en avance car les contributions locales n’ont pas encore été envoyées sur le repository distant.
Un repository local doit donc faire des synchronisations régulières avec le repository distant... avec les
conflits qui vont avec.
Il existe quantité d’hébergeurs de projets Git, généralement ils proposent plus qu’un dépôt Git : tickets,
intégration continue, ... Certains sont gratuits, d’autres sont gratuits uniquement en version limitée.
Nous allons illustrer la création d’un compte avec le site [Link] Si vous choisissez un autre
hébergeur la démarche sera similaire.
7.1.1 Création
Via l’entrée “Sign in”, puis “Register now”, créer un compte est simple et ne nécessite de fournir que peu
d’informations.
La page de configuration est accessible par l’icône en haut à droite et l’entrée “Edit profile”.
On peut, dans cette page, noter les entrées suivantes :
- “Profile” qui permet de paramétrer le compte avec notamment :
- l’email utilisé pour les commit (une adresse privée est fournie)
- quelles informations sont visibles des autres internautes
- SSH Keys qui permet de gérer les clés SSH (cf. ci-dessous)
Dans le menu accessible par l’icône en haut à droite, si on choisit l’entrée avec son nom, on arrive sur
page de statistiques sur son activité.
Pour accéder à son compte via le protocole SSH, il faut créer une paire de clés et déposer la clé publique
sur le site de gitlab.
Voici la démarche, sous Linux, pour créer une paire de clés (la démarche sur les autres systèmes 32 est
similaire).
Pour créer une paire de clés :
32. Sous Windows il existe l’application “Git Bash” qui est une console de commandes améliorée, ou encore “putty”.
Dans le répertoire $HOME/.ssh les fichiers id rsa (clé privée) et id [Link] (clé publique) ont été créés.
C’est le fichier id [Link] 33 qu’il faut déposer sur le compte gitlab. Voici le contenu du répertoire .ssh :
On note que le répertoire .ssh et le fichier id rsa ne sont lisibles que par le propriétaire du compte.
Dans la génération des clés, la passphrase a été laissée vide. Cela nous facilitera la vie par la suite, mais
on supprime une couche de sécurité.
Pour gérer de manière quasi-transparente une passphrase, reportez-vous aux commandes ssh-agent et
ssh-add.
Chez l’hébergeur (gitlab pour nous), il suffit de créer un projet blanc (i.e. vide) : “Create blank project”.
Pour la configuration, il faut :
- donner un nom
- donner une description
- indiquer s’il est public ou privé
33. et surtout pas l’autre qui doit rester secret pour éviter des usurpations d’identité
34. cf. note précédente
35. C’est un abus de langage, on entend par là “sans taper de mot de passe”.
Il contient toutes les opérations pratiquées jusqu’ici avec les mêmes sha1 (cf. ci-dessous).
7.2.2 Initialisation
Cette section est juste présente pour information et n’est pas utile pour la suite.
En règle générale la génération d’un projet vide est suffisante.
Mais dans notre cas nous avons travaillé avec un repository purement local, et il sera utile qu’il soit relié
à un repository distant 36 .
Il n’y a que deux commandes 37 à saisir (après avoir déposé la clé ssh), commandes fournies par gitlab à
la suite de la création d’un projet vide :
La première commande crée le repository distant, et la seconde le synchronise, pour les branches, avec le
repository local.
Ce qui nous intéresse, c’est que nos trois branches ont bien été prises en compte dans le repository distant
(que l’on a appelé, par convention et par défaut, origin).
Voici l’interface de gitlab :
36. en l’occurrence pour qu’il soit accessible par les lecteurs de ce document.
37. On rajoute “git push -u origin –tags” si des tags ont été créés.
7.3.1 Clonage
Cette opération est à faire une seule fois, pour un développeur, lorsque celui-ci rejoint un projet. Il doit
rapatrier le repository distant pour en faire une version locale : le clonage.
Syntaxe :
$ git clone <url repository distant> [<répertoire local>]
Il existe plusieurs protocoles pour l’url du repository distant : ssh, git, https, ...
Si le nom du répertoire n’est pas fourni, il est déduit du nom du repository.
Pour la suite, nous aurons deux utilisateurs pour illustrer les interactions multiples :
- Gilles travaillant dans le répertoire local PROJET C
- Sellig travaillant dans le répertoire local TEJORP C
Pour le premier utilisateur, son dépôt local est déjà configuré. car c’est lui qui nous a servi d’exemple
jusqu’à présent et qui nous a permis d’initialiser le repository distant.
Pour le deuxième, il faut faire un clone :
Pour les captures d’écran des consoles de commandes, la charte graphique est la suivante :
- utilisateur gilles travaille dans le répertoire PROJET C et le prompt est vert.
- utilisateur sellig travaille dans le répertoire TEJORP C et le prompt est bleu.
Lorsqu’on regarde l’état du dépôt, il y a une information supplémentaire.
Avant, lorsqu’il n’y avait pas de repository distant :
et :
et :
Avec l’affichage du status, il y a une ligne supplémentaire indiquant si on est synchronisé avec le repository
distant.
Dans le graphe, on s’aperçoit que chaque branche locale (notée en vert) est associée à une branche distante
(notée en rouge).
Et voyons le nouvel utilisateur, avec le repository distant :
et :
Le graphe est légèrement différent : toutes les branches distantes n’ont pas (encore) leurs équivalents
locaux.
Répétons l’hypothèse simplificatrice : nous nous plaçons dans un modèle centralisé : il y a un repository
distant qui sert de référence et qui est alimenté par les repositories locaux des développeurs.
Mais Git peut être décentralisé et donc avoir plusieurs repositories distants.
Un développeur suit le scénario général suivant :
- Il récupère la dernière version du logiciel en provenance du repository distant (commande “git pull”)
dans son repository local.
- Il effectue des modifications en local
- Il crée une nouvelle version (commande “git commit”) toujours en local
- Éventuellement il fait plusieurs cycles modification/commit.
- Il envoie ses nouvelles versions sur repository distant (commande “git push” 39 ) pour les rendre
disponibles à la communauté
- Ce scénario est répété continûment.
Mais plusieurs problèmes viennent se greffer :
- il y a plusieurs branches à gérer
- pendant qu’un développeur fait des modifications en local, le repository distant peut évoluer par le
fait d’autres développeurs :
- ajout de nouvelles version, possiblement sur la branche que le développeur modifie : risque de
conflits
- ajout ou suppression de branches
- ...
7.5.1 Introduction
En règle générale, à une branche locale correspond une branche sur le repository distant qui a le même
nom précédé du nom du repository. Par exemple, en prenant le nom classique “origin” pour le repository
distant, voici ce qu’on peut avoir :
- Repository local, il y a les branches : master et develop (c’est un choix “arbitraire” 40 pour illustrer)
- Repository distant il y a les branches : “origin/master” et “origin/develop”
- Repository local il y a également les branches : “origin/master” et “origin/develop” qui sont une
copie locale des mêmes branches distantes en mode read only.
Pourquoi n’y a-t-il pas uniquement un seul jeu de branches ?
On pourrait imaginer que la branche “develop” (par exemple) soit partagée par tous les développeurs.
Un premier soucis purement technique est qu’il faudrait une connexion internet permanente.
Mais surtout le deuxième soucis est que la branche changerait en permanence si un grand nombre de
développeurs sont simultanés dessus. On ne peut pas imaginer travailler dans un répertoire évoluant en
permanence avec des codes extérieurs en cours de débugage.
Donc il faut d’une part la branche “develop” en local pour travailler dans un environnement stable et
une branche sur le repository distant pour partager le code. Lorsque le travail en local est terminé on le
commit sur le branche locale, et on fusionne cette dernière dans la branche distante.
Pourquoi a-t-on une copie de la branche distante en local ?
C’est à étudier, il y a plus que certainement de bonnes raisons !
Revenons sur la commande push où on met à jour le repository distant. Le problème est que le temps
de développer sa fonctionnalité, la branche distante a pu changer, par un autre développeur, rendant
l’injection des commit problématique.
Aussi généralement refait-on un pull pour fusionner les dernières modifications distantes dans sa branche
locale. Les conflits éventuels réglés, le push est alors possible 41 .
39. nous verrons qu’il est préférable de faire un “pull” avant le push
40. pas vraiment en fait, cf. section sur le workflow
41. En espérant qu’entre temps la branche distante n’est pas été modifiée encore une fois
Quelques commandes sont présentées dans la section suivantes. S’ensuivront quelques exemples illustratifs.
a) branches distantes
Pour visualiser les branches sur le repository distant :
$ git branch -r
Attention, il peut être nécessaire de faire un pull au préalable pour récupérer les dernières informations
du repository distant : en effet cette commande liste les copies locales des branches distantes.
Si le nom du dépôt n’est pas précisé, c’est celui par défaut qui est choisi (généralement “origin”).
Si aucun nom de branche n’est précisé, c’est la branche courante qui va être renvoyée sur le repository
distant.
La commande :
$ git push origin --tags
envoie tous les tags sur le repository distant (cf. “git help tag”).
Le but est de choisir quelques scénarios typiques de travail et de les illustrer pas à pas.
Intégration :
On note que que le repository local n’est pas au courant des changements distants tant que la commande
suivante n’est pas lancée :
Le repository local du deuxième développeur en est au même point que celui du premier développeur.
f ) le second ajoute une fonctionnalité
Tout se passe bien car la branche distante n’a pas été modifiée depuis le dernier pull.
f ) le second veut à son tour mettre à jour la branche distante :
Une fusion est effectuée entre la nouvelle version distante et la version locale (avec un nouveau sha1) :
h) un nouveau push :
Comme la fusion ne pose pas de problème, un push est désormais possible 43 :
On voit que la branche distante est en avance d’un cran sur la branche locale.
e) le second développeur se déplace sur develop :
On note que le message indique que le pull précédent n’a pas mis à jour la branche locale.
f ) le second développeur fait un nouveau pull pour mettre à jour la branche develop :
Donc les commit existent toujours mais sont orphelins. Seule la branche master est désormais visible :
d) La démarche effectuée jusque là est relativement intuitive. Mais les autres développeurs doivent faire
des actions particulières, sinon on court le risque de recréer par inadvertance la branche distante.
45. avant la version 1.7, la syntaxe était “git push origin:develop”
Pour résumer :
- le développeur qui détruit la branche :
$ git push origin --delete <nom branche>
$ git branch -D <nom branche>
- les autres développeurs :
$ git fetch --prune
$ git branch -D <nom branche>
Créer une nouvelle branche est plus intuitif que la suppression. Temporellement :
- le premier développeur crée la branche (versions locale et distante).
- le second développeur la récupère.
a) Les deux développeurs sont sur la branche master (la seule qui existe).
La nouvelle branche apparaı̂t bien en local, mais la branche distante n’existe pas encore.
c) le premier développeur crée la branche distante correspondante :
Pour résumer :
- le développeur qui crée la branche :
$ git checkout -b <nom branche>
$ git push --set-upstream origin <nom branche>
l’option “-u” est un raccourci de ‘--set-upstream”‘
- les autres développeurs :
$ git pull
$ git checkout <nom branche>
7.7 Workflow
Le but est d’avoir une stratégie pour utiliser des branches avec des rôles précis,
Un modèle à été proposé par :
[Link]
Branches principales à durée de vie infinie :
- master : branche de production. Elle contient les versions successives livrées.
- develop : branche d’intégration. Elle contient le code destiné à une prochaine release. Lorsque le
code est stable, il est fusionné (merge) dans la branche master, ce qui correspond alors à un nouveau
numéro de version.
Branches secondaires :
- features : branches pour ajouter des fonctionnalités
- release : branche de préparation à une nouvelle version de production
- hotfix : branche de correction des bugs majeurs de la branche de production
Des extensions à la commande git existent pour faciliter la manipulation du workflow :
[Link]
Quelles commandes peuvent donner des informations sur un projet sans le modifier ?
- init
- clone
- status
- log
- push
- pull
- config
- branch
- fetch
- merge
Question 4
une seule réponse possible
Git est un modèle centralisé reposant sur un dépôt distant principal ?
- vrai
- faux
Question 5
une seule réponse possible
Du point de vue d’un développeur, en général une branche est gérée en combien d’exemplaires ?
- 1
- 2
- 3
- 4
Question 6
plusieurs réponses possibles
Quelle(s) commande(s) permet(tent) de lister les branches locales ?
- git branch
- git branch -r
- git branch --verbose
- git branch --list
Question 7
plusieurs réponses possibles
Quelle(s) commande(s) permet(tent) de lister les branches distantes ?
- git branch
- git branch -r
- git branch --verbose
- git branch --list
Question 8
une seule réponse possible
Un tag ressemble à une branche si ce n’est qu’il ne peut pas bouger (on ne peut pas faire de commit
dessus) ?
- vrai
- faux
Question 9
une seule réponse possible
Créer une branche dans le repository local la crée automatiquement dans le repository distant ?
- vrai
- faux
Question 10
une seule réponse possible
Supprimer une branche dans le repository distant la supprime automatiquement dans le repository local ?
- vrai
- faux
8 Conclusion
Il y a beaucoup de chose à dire, mais pour être succinct :
- Git est un outil indispensable à connaı̂tre pour un informaticien.
- Git est un outil complexe.
- Ne jamais dire que l’on maı̂trise Git 46 , c’est le meilleur moyen pour échouer à un entretien d’em-
bauche.
- Plus on progresse dans Git 47 , plus on est conscient de la taille de l’univers 48 .
- Quelle est la première instruction dans un projet ?
Réponse : git init
- vrai
- faux
Le répertoire de travail peut être le seul dépôt géré par Git, il est alors réservé à un seul utilisateur.
Question 2
plusieurs réponses possibles
Quelle(s) commande(s) permet(tent) d’enregistrer son nom dans Git ?
- git config [Link] ”mon nom”
enregistrement uniquement pour le projet en cours
- git init [Link] ”mon nom”
non, git init sert à créer un projet vide
- git config --global [Link] ”mon nom”
enregistrement pour tous les projets de l’utilisateur
- git init --global [Link] ”mon nom”
cf. deux réponses plus haut
- git --global config [Link] ”mon nom”
non, ”--global” est une option de la sous-commande config, pas de la commande git.
- git --global init [Link] ”mon nom”
deux fois non
- export GIT AUTHOR NAME=”mon nom”
c’est une autre manière de faire qui est prioritaire sur les paramètres des fichiers de configuration
Question 3
une seule réponse possible
Je ne renseigne pas mon nom et mon email dans la configuration de Git.
- vrai : si je fais une bêtise, les autres développeurs ne pourront pas me retrouver
no comment
- faux : ainsi les autres développeurs sauront sur quelles parties je suis intervenu
Question 4
plusieurs réponses possibles
Je veux enregistrer un répertoire vide dans Git. Je mets dans le répertoire le fichier suivant :
- .gitkeep vide
toutes les réponses fonctionnent, mais celle-ci est la plus communément utilisée.
- .gitkeep avec un espace dedans
- .gitignore vide
l’avantage est que ce fichier a un réel sens dans Git
- .empty vide
- .vide vide
Question 5
une seule réponse possible
Les fichiers .gitignore sont inutiles, autant sauvegarder un maximum d’informations dans les repositories
Git.
- vrai
- faux
Des informations inutiles perturberaient les autres développeurs et génèreraient des versions inutiles.
En général les fichiers générés automatiquement (compilation, cache) ne sont pas stockés dans les
repositories, ainsi que les données personnelles (configuration d’une IDE) ou sensibles (mot de passe)
du développeur.
- vrai
- faux
Git est bien décentralisé ce qui le rend à la fois complexe et puissant, mais nous n’étudierons pas
cet aspect.
Question 2
plusieurs réponses possibles
La commande “git status” ?
- elle affiche le copyright du logiciel Git
- elle indique les changements effectués depuis la dernière version enregistrée
- elle indique les fichiers qui seront intégrés à la nouvelle version et ceux qui ne le seront pas
- elle indique si le projet est public ou privé et les coordonées du propriétaire
Question 3
plusieurs réponses possibles
Pourquoi la commande “git commit” crée une nouvelle version en local et ne la propage pas directement
sur le repository distant ?
- Il n’existe pas obligatoirement un repository distant.
- Le système étant décentralisé, c’est au développeur de choisir sur quels dépôts distants il désire
propager la nouvelle version.
- Le développeur peut ainsi annuler des commit, ce qui ne sera plus possible une fois la version
transmise.
À utiliser avec circonspection ! Par exemple on supprime son dépôt local 49 et en refaisant un “git
clone” afin d’oublier les dernières versions locales. Autre exemple, on utilise la commande “git
commit --amend”. Il est bon de répéter que c’est à éviter au maximum.
- Le développeur n’a pas obligatoirement accès à internet.
Question 4
plusieurs réponses possibles
Pourquoi la commande “git add” est nécessaire et pourquoi tous les fichiers créés ou modifiés ne sont pas
directement intégrés à une nouvelle version ?
- Cela réduit le trafic réseau.
Pour une nouvelle version, on choisit les fichiers selon des critères d’utilité et non de taille.
- Cela réduit la place occupée sur disque.
idem
- Cela permet au développeur d’avoir une double validation avant de créer la version.
Il se trouve que c’est une conséquence, mais ce n’est pas la raison.
- Des informations inutiles perturberaient les autres développeurs et génèreraient des versions inutiles.
En général les fichiers générés automatiquement (compilation, cache) ne sont pas stockés dans les
repositories, ainsi que les données personnelles (configuration d’une IDE) ou sensibles (mot de passe)
du développeur.
Question 5
une seule réponse possible
Les SHA-1 pour identifier les versions sont trop risqués car il y a des risques de conflits (i.e. que deux
versions aient le même identifiant).
- vrai
Le terme “trop risqué” n’est pas correct. Il y a théoriquement un risque mais il tellement faible
qu’on le considère comme nul.
- faux
Question 6
une seule réponse possible
On peut identifier une version par autre chose qu’un SHA-1.
- vrai
On peut apposer un (ou plusieurs) tag à une version particulière. Notons aussi l’identifiant particu-
49. À ne faire que si il existe un dépôt distant !
lier qu’est le nom d’une branche (master par exemple) et qui permet d’identifier la version la plus
récente de la branche.
- faux
Question 7
plusieurs réponses possibles
La commande “git checkout” permet de :
- naviguer dans l’historique des versions d’une branche
- changer la branche active
- supprimer physiquement une version devenue inutile
C’est impossible de supprimer physiquement une version : c’est l’essence de Git de garder une trace
de tous les états et de pouvoir revenir dessus.
- créer une nouvelle branche
Oui c’est possible avec l’option “-b” de checkout même si ce n’est pas la fonction première de cette
commande.
Question 8
une seule réponse possible
On peut modifier une version ancienne et faire des commit ?
- vrai
Mais à condition de créer une branche pour éviter les versions orphelines.
- faux
Réponse incorrecte (cf. ci-dessus) mais acceptée au vu des connaissances acquises jusqu’à présent.
Le pointeur HEAD, comme son nom l’indique, désigne toujours le premier commit d’une branche.
- vrai
- faux
Le premier commit est la racine de l’arbre et donc désigne toutes les branches du projet : avoir un
pointeur dessus n’a pas beaucoup d’intérêt. HEAD pointe sur le commit de travail courant qui est
potentiellement n’importe quel commit.
Question 5
une seule réponse possible
Sur une branche le chaı̂nage des commit est à l’envers : le premier commit de la liste chaı̂née est le dernier
à avoir été créé.
- vrai
C’est bien le cas. À terme nous manipulerons des arbres et, avec un chaı̂nage “à l’envers”, chaque
noeud n’a qu’un seul suivant (qui est donc le précédent dans l’ordre chronologique). En outre ce
sont les feuilles de l’arbre qui nous intéressent et donc avoir un accès direct dessus est intéressant ;
en revanche il faut avoir autant de pointeurs qu’il y a de feuilles.
- faux
Question 6
une seule réponse possible
HEAD peut être un pointeur sur un pointeur, mais ça n’a pas de sens.
- vrai
- faux
En l’occurrence c’est un point clé de Git, c’est ainsi que le pointeur de branche et HEAD peuvent
se déplacer en ensemble.
Question 7
une seule réponse possible
Une mauvaise utilisation du pointeur HEAD peut conduire à un état incohérent du repository.
- vrai
Et c’est même assez facile, soit en créant de nouveaux commit à partir de commit intermédiaires,
soit en positionnant mal le pointeur HEAD lorsqu’on le remet sur la dernière version d’une branche.
- faux
Question 3
une seule réponse possible
Quelle commande permet de changer la branche courante ?
- git branch --change ...
- git branch --checkout ...
- git checkout ...
Cette commande permet de se déplacer dans l’arbre en précisant un nom de branche, un sha1 ou
un tag.
- git checkout --branch ...
Question 4
une seule réponse possible
On peut créer une nouvelle branche uniquement à partir du commit le plus récent d’une branche existante ?
- vrai
- faux
On peut créer une branche à partir de n’importe quel commit (y compris le tout premier) ; il faut
cependant réfléchir à l’utilité de faire partir une branche d’un commit passé.
Question 5
plusieurs réponses possibles
Sachant qu’une branche est identifiée par une étiquette/pointeur sur son commit le plus récent, supprimer
ce pointeur est catastrophique.
- oui
Si la branche n’est pas encore fusionnée, supprimer l’étiquette rend tous les commit non visibles
(orphelins).
- oui mais non
Toujours dans le cas où la branche n’est pas fusionnée, il est malgré tout possible de récupérer les
commit orphelins avec la commande fsck.
- non
Si la branche est fusionnée à une autre, il n’y pas de conséquence à supprimer l’étiquette, si ce
n’est de perdre un nom indiquant le contenu de la branche ; et supprimer l’étiquette permet de la
réutiliser pour une autre branche.
Question 6
une seule réponse possible
Pour supprimer le dernier commit d’une branche, il suffit de reculer l’étiquette portant le nom de la
branche d’un cran.
- vrai
Même si c’est techniquement faisable, la réponse est refusée : c’est pervertir la notion de branche.
- faux
On part du principe que ce n’est pas possible (cf. l’autre réponse). En revanche il est possible de
faire un commit qui annule le dernier, ou plutôt ramène à l’avant dernier (commande revert).
Question 7
une seule réponse possible
Supprimer une branche permet de libérer de la place disque ?
- vrai
- faux
Les commit de la branche deviennent orphelins mais en aucun cas ne sont effacés du disque. Il n’est
pas possible de supprimer des commit 50 .
Question 8
une seule réponse possible
Lors d’une fusion, Git arrive souvent à rassembler les codes des deux branches de manière autonome ?
- vrai
il faut que ce soit la même partie d’un fichier qui soit modifiée pour que la charge de la fusion
50. À moins de modifier le répertoire .git à la main, ce qui est interdit et considéré comme une faute lourde dans une
entreprise.
incombe au développeur.
- faux
Question 9
une seule réponse possible
On peut fusionner 3 branches ou plus simultanément ?
- vrai
mais on peut se demander si la fusion de deux branches n’est pas déjà suffisament complexe.
- faux
Question 10
plusieurs réponses possibles
Dans une fusion, à quelle branche rajoute-t-on un commit ?
- la branche courante
- la branche distante
la branche distante n’est pas modifiée.
- aucune
dans le cas du “fast forward”
Question 11
une seule réponse possible
En cas de conflit, le développeur doit choisir une des deux branches et éliminer l’autre ?
- vrai
c’est bien entendu une possibilité, mais il peut également faire un mixte des deux ou encore faire
un code complètement différent.
- faux
Question 12
une seule réponse possible
La commande “git stash” permet de :
- supprimer les modifications apportées depuis le dernier commit
- sauvegarder les modifications apportées depuis le dernier commit et revenir à la situation de celui-ci.
- clone
copie d’un projet en local
- status
elle sert uniquement à donner des informations sur les modifications en cours et non enregistrées
par un commit
- log
elle sert uniquement à donner des informations sur les différents commit
- push
modifie le repository distant
- pull
met à jour le repository local
- config
peut donner des informations ou modifier la configuration
- branch
peut donner des informations sur les branches ou les modifier
- fetch
met à jour le repository local
- merge
fusionne deux (ou plus) branches
Question 4
une seule réponse possible
Git est un modèle centralisé reposant sur un dépôt distant principal ?
- vrai
- faux
L’utilisation d’un repository centralisé et la plus commune, mais Git est prévu pour utiliser plusieurs
dépôts.
Question 5
une seule réponse possible
Du point de vue d’un développeur, en général une branche est gérée en combien d’exemplaires ?
- 1
- 2
- 3
la version locale, la version distancielle et la copie de la version distancielle en local.
- 4
Question 6
plusieurs réponses possibles
Quelle(s) commande(s) permet(tent) de lister les branches locales ?
- git branch
c’est le comportement par défaut
- git branch -r
ne liste que les branches distantes
- git branch --verbose
comme la première réponse avec plus d’information sur chaque branche
- git branch --list
liste également les branches distantes, mais les branches locales sont bien listées
Question 7
plusieurs réponses possibles
Quelle(s) commande(s) permet(tent) de lister les branches distantes ?
- git branch
cf. réponse précédente
- git branch -r
cf. réponse précédente
- git branch --verbose
cf. réponse précédente