Ce cours a intégré SVN en plus de Git de 2012 à 2016.
En cas de besoin,
l'ancien cours est toujours disponible.
Motivations
TP1 : Que serait notre vie sans VCS ?
Vous faites partie d’une équipe de développement qui intervient sur la réalisation d’une application
Comment s'assurer de ne pas perdre de sources ?
Comment conserver l’historique et revenir en arrière ?
Comment partager le développement entre plusieurs personnes ?
avec des contributeurs externes ?
Comment gérer plusieurs variantes à la fois ?
Comment tracer les modifications ?
Comment valider les modifications et faire de la revue de code ?
Comment fusionner les modifications ?
En utilisant un VCS !
Le VCS au sein de l'usine logicielle
Usage
Utilisateurs : les développeurs et les intégrateurs
Le VCS est avec l'IDE l'outil principal du développeur
Principalement stockage de fichiers
texte
Quels fichiers dans le dépot ?
Régle no 1 : le projet doit être auto-porteur
Régle no 2 : on ne commit pas de fichiers dérivés
A commiter
Fichiers source (.java, .c, .html, .css...)
Fichiers binaires non dérivés des sources, images par exemple
Jeux de données des tests
Fichiers de build (maven: pom.xml, npm: package.json, Jenkinsfile ...)
A ne pas commiter :
Fichiers temporaires, générés ou compilés
Librairies (utiliser un dépot Maven)
Selon les politiques :
Fichiers projets de l'IDE (.project, .idea ...)
Personnalisation et historique de l'IDE
Modeles centralisés / client-serveur
Copyright CC-BY-NC-SA Mathieu Nebra (M@teo21)
Exemples : CVS, SVN, ClearCase, Perforce
Un serveur gère l'intégralité des révisions (le dépôt), les développeurs récupèrent les modifications des autres et y
ajoutent les leurs
Modeles distribués
Copyright CC-BY-NC-SA Mathieu Nebra (M@teo21)
Exemples : Git, Mercurial, Baazar, BitKeeper
Chaque développeur possède un dépôt entier mais les dépôts peuvent s'échanger des modifications
Concepts généraux
SCM,
VCS, outil de versioning, GCL
Source Control Management, Version Control System : outils permettant de gérer plusieurs versions de sources
dépôt, référentiel [repository, depot]
Un dépôt est une sortie de base de données de sources contenant toutes les révisions (tout l'historique) des fichiers
ainsi que des données de gestion (méta-données) associées.
On ne peut rien perdre dans un dépot.
Concepts généraux
Copie locale, espace de travail [working copy/directory/workspace]
Copie locale éditable d'une révision du dépôt et dont les modifications peuvent ensuite être validées (commitées) dans
le dépôt. Sert de 'bac à sable'.
Commit, validation,
"mettre en conf", [commit,checkin]
(verbe) Enregistrer des modifications de la copie locale vers dépôt.
(nom) modifications elles-mêmes.
Concepts généraux
Branche [branch]
Une ligne de développement d'un projet. Sert par exemple à
Gérer une version spécifique pour un client
Gérer des branches de maintenance corrective
Ecrire une fonctionnalité ou un correctif en isolation
Tester une idée, un refactoring sans risques
Attention, les branches ne sont pas toujours adaptées.
Une alternative à certains types de branches se développe : Le Trunk-Based Developement
dans lequel les developpeurs envoient tous leur code dans une seule et unique branche
mais dont certaines fonctionnalités sont désactivées par
feature flags (ou "feature toggles").
Concepts généraux
Merge, fusion [merge]
Fusion des modifications de deux branches
Tag, étiquette [tag, label]
Photo du dépôt à un moment précis. Etiquette d'un ensemble cohérent de sources.
Un tag peut présenter un aspect contractuel (signature numérique)
Concepts généraux
Modification concurrente
Tentative de mise à jour de sources ayant divergées
CC-BY-SA Ben Collins-Sussman, Brian W. Fitzpatrick, C. Michael Pilato
Concepts généraux
Conflit [conflict]
Modification concurrente d'une même zone de texte. La résolution est manuelle.
Exemple de conflit :
<<<<<<< commit A'
int i = 0;
=======
int i = 1;
int j=0;
>>>>>>> commit A''
Bonnes pratiques
Le code commité doit toujours compiler
Commiter et merger souvent, mettre à jour régulièrement
Utiliser avec un outil de gestion de tickets (Issues GitLab / GitHub, Jira, Trac, Mantis...)
Grouper les modifications en commits cohérents
Ne jamais commiter de secrets
Ne jamais commiter du code mort (en commentaire ou pas)
Formatage coercitif des sources ASAP
Bonne pratiques / numéros de versions
Version scheme :
[X].[Y].[Z], exemple :
1.2.3
Programme : [major (refonte complète)].[minor (évolution)].[no fix (correction) en partant de zéro]
Un titre (max 50 caractères) sous la forme
type de commit
: contenu
Un paragraphe détaillé (largeur 72 caractères max)
Rédaction du contenu:
Lignes vides entre paragraphes
Si clos un ticket, l'indiquer, exemple :
Closes #1234
Présent : "corrige", pas "a été corrigé"
Donner toute information importante à destination d'un futur mainteneur
Exemple :
fix: problème fichier bootstrap vide
Closes #1456
Corrige une NPE se produisant quand le fichier bootstrap existe
mais est vide. Reproduit uniquement par TU, non reporté par les
utilisateurs à ce point.
Voir aussi le bug #1674.
Note: pour faire un message de commit multiligne sous Linux/OSX en ligne de commande : utiliser l'options -m " puis "
à la fin de la dernière ligne)
Introduction à Git
I'm an egotistical bastard, and I name all my projects after myself. First Linux, now Git. (Linus Torvalds)
Linus Torvalds + 3 semaines = Git (utilisé pour le kernel Linux dès avril 2005)
VCS le plus puissant et le plus performant mais non trivial
Open Source (licence GPL)
VCS de type distribué et contrôle optimiste uniquement
Programme "Unix-like" : commandes de haut niveau (porcelain) utilisant commandes bas niveau (plumbing)
Quelques clients Git
Tous OS: egit/jgit, IDEA, ATOM, Visual Studio Code, GitKraken, ungit (node.js)...
Linux: git (ligne de commande) + gitk (GUI pour l'historique), tig (ncurses),
Microsoft Windows: Git for Windows (git en ligne de commande), TurtoiseGit (éditeur graphique intégré dans l'OS)
Mac OSX : Tower, GitBox
Web : GitHub, GitLab, Gitea
Configurer git
Attention! ne surtout pas oublier de configurer Git avant tout commit, plus possible ensuite de changer nom/e-mail (sauf
perte historique)
Trois niveaux de configuration :
/etc/gitconfig : configuration multi-dépôt pour tous les utilisateurs de la machine
~/.gitconfig : configuration multi-dépôt pour l'utilisateur (en général, seul ce fichier est à modifié)
/
chemin dépôt/.git/config
: configuration du dépôt
Configuration globale (multi-dépôts) de l'utilisateur courant :
Le dépôt que l'on vient de cloner est spécial : il est appelé
origin (
upstream repository)
Structure de base de Git
Git stocke des :
trees,
blobs,
tags et
commits, tous référencés par un hash SHA-1 unique (intégrité)
Les références (refs) sont des noms symboliques (signets) des hashs des commits
Copyright CC BY-SA-NC Scott Chacon
SHA-1 transforme une suite de caractères de 1 à 2^64 bits en un nombre de 160 bits. Non réversible. Exemple :
a6e757a90e389270e75428473858e04f8c71121b. Versions réduites :
a6e757a. Risque collisions infinitésimaux.
Les commits dans Git
Un commit est un instantané de l'ensemble du dépot
(contrairement à certains VCS comme SVN où un commit est un diff depuis le commit précédent)
Chaque commit possède [1..n] parents
Donc l'historique forme un graphe (orienté et acyclique ou 'DAG')
Le commit est de niveau dépôt (pointe sur le tree racine)
Notation :
ref^n (nième parent) et
ref~n (nième premier parent)
Lecture seule, se resynchronise avec son dépôt distant avec
git fetch et
git push
Branches remote (suite)
Par défaut, push refusé si la branche distante a divergé
git pull =
git fetch
+ git merge
git pull --rebase =
git fetch
+
git rebase
Tracking remote branch : branche remote avec refspec, push/fetch/pull sans arguments (le '+' signifie qu'on met à jour
la référence même si ce n'est pas un ff)
Options
-w pour ignorer les différences de formatage
Toutes les différences entre deux branches :
$ git diff branche1..branche2
Seulement les différences de la branche2 avec le dernier commit commun entre branche1 et branch2 (qu'ai-je fait dans
la branche2 depuis que mes deux branches ont divergées ?) :
$ git diff branche1...branche2
Attention !
git diff sans arguments compare copie locale et index, pas HEAD
Liste des commits pas encore poussés :
git cherry -v
Merging
$ git checkout master
$ git merge iss53
C'est la copie locale de la branche courante qui est modifiée
Si conflit, plus possible de commiter :
Soit résolution manuelle des conflits
Soit
git checkout --ours
fichier
Soit
git checkout --theirs
fichier
puis (dans les trois cas),
add
et
commit
Revenir à l'état antérieur :
git reset --merge
Bonne pratique : option
--no-ff pour créer un commit de merge même en cas de FF
Se "rebase" sur une référence, c'est à dire repart de cette ref
Quels avantages sur merge ?
Rend l'historique beaucoup plus lisible (linéaire)
La résolution de conflit se fait commit par commit
Plus facile de debugguer en navigant sur un historique linéaire
Tests plus fiables car on integre le code des autres dans nos tests
Attention : n'utiliser rebase qu'avec ses branches locales, ne jamais rebaser un commit qui a déjà été poussé.
Rebase interactif
rebase -i [commit]
Permet au passage de modifier l'historique (squashing, spliting, modification
messages, changement d'ordre ...)
Le commit cible est le parent du premier commit à modifier. Réorganiser les 5 derniers commits :
rebase -i HEAD~5 (5éme ancêtre du commit où on se trouve= 6éme commit dans le passé)
Actions courantes :
reword (r) : réécrire le message de commit
edit (e) : modifier le commit
squash (s) : fusionner le commit avec le précédent
Supprimer des commits/changer leur ordre en jouant sur les lignes pick (p)
Cherry-picking
git cherry-pick commit1 commit2...
Applique les commits selectionnés comme des patchs sur la branche courante
Utilitaires
Rebase interactif pour réécrire son historique local
git rebase -i
debugage multi-révisions avec bisect
git bisect [good|bad|start|skip|run|reset]
Commit partiel dans un même fichier :
git add -p
(option patch)
L'autocomplétion [7]. Ajouter
. git-completion.bash dans
/etc/profile
L'IHM gitk
Changelog avec shortlog :
$ git shortlog
Fancy CLI
Coloriser :
git config --global ui.color true
son
.bashrc
Templates de messages de commit
git config --global commit.template fichier
Ignorer des fichiers
Fichier
.gitignore à la racine de la copie locale (lui-même commité). Exemples :
# a comment - this is ignored
# no .a files
*.a
# but do track lib.a, even though you're ignoring .a files above
!lib.a
# only ignore the root TODO file, not subdir/TODO
/TODO
# ignore all files in the build/ directory
build/
# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt
# Exemple réél :
.metadata
bin
build
.settings
# Ignore recursively all .class files
*.class
Possible de positionner un fichier
.gitignore
n'importe où
Peut être défini au niveau utilisateur dans le fichier donné par
~/.gitconfig propriété
core.excludesfile
Bonnes pratiques Git, à faire
Configurer Git (nom et e-mail) avant tout commit
Utiliser les commandes
git rm, git mv au lieu des commandes sytème (git fait le 'add' pour vous)
Bloquer les push forcés et les suppressions coté serveur publique avec les options
receive.denyNonFastForwards=true
et
receive.denyDeletes=true
Nettoyer (avec rebase -i) son historique avant de le pousser si confus
... ou encore mieux : pusher un seul commit par branche (squash)
N'utiliser rebase que sur des commits pas encore poussés
Créer une branche topic (vie courte) pour chaque unité de travail (bug, évolution...)
Protéger (empècher les push directs sans MR) les branches à vie longue (master, develop...)
Les branches topic ne doivent pas vivre plus de un à deux jours et doivent être supprimées dès que mergées
Travailler avec des Merge Requests si vous disposez d'une forge (ex: Gitlab)
Squasher les commits de MR (une MR = 1 commit)
Bonnes pratiques Git, à éviter
Pas de merge avec modifications non commitées (retour arrière difficile)
Ne pas forcer les push (avec push -f ou via l'option + des refspecs), risque de perte de commits
Ne pas modifier (avec rebase ou --ammend) un commit publié
Ne pas mixer merges et rebases, rend les résolutions de conflits beaucoup plus difficiles