La gestion de versions

IUT de Nantes – Département Informatique

Cours de Licence professionnelle 2022-2023

Copyright (C) 2012-2021 Bertrand Florat ( contact), licence CC-BY-SA V4

https://cours-git.florat.net/ / Feuille de TP / Sources / (version sur une page)

Source : https://pbs.twimg.com

Agenda

1- Concepts généraux 2- Présentation de Git 3- Pratique de Git 4- Travail collaboratif avec Git
  • TP1 : Motivations
  • Rôle dans l'usine logicielle
  • Notions et terminologie
  • Règles de l'art VCS
  • Concepts Git
  • TP2: Exercice concepts Git
  • Commandes de base
  • Règles de l'art Git
  • TP3 : commandes de base en équipe
  • Outils
  • Les différents types de workflows
  • TP4 : utilitaires Git

Pour optimiser ce cours

Comment bien apprendre ?

Note sur Subversion

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]
  • Librairie : [compatibilite API].[évolution].[fix]
  • Voir le semantic versionning http://semver.org/
Bonnes pratiques / convention message de commit
  • 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 :

$ git config --global user.name "John Doe"  //Obligatoire !
$ git config --global user.email johndoe@example.com   //Obligatoire !
					

Autres exemples de configuration : core.editor ou merge.tool

Les alias
git config --global alias.co checkout

A mettre dans la section [alias] de ~/.gitconfig

Les plus courants :

cp = cherry-pick
st = status -s
cl = clone
ci = commit
co = checkout
br = branch
po = push origin
Création d'un nouveau dépôt

Note : un seul projet par dépôt pour raison d'isolation et de performances

$ cd ~/depots/my-app
$ git init

Le contenu du répertoire (projet Maven/Eclipse) sera :

~/depots/my-app
                   /.git  //dossier des meta-données git
                   /.project
                   /.classpath
                   /pom.xml
                   /src/main/java
                   /...

Dépôt nu (sans copie locale) :

  • Créé avec : git init --bare
  • Convention nommage : repertoire my-app .git
Récupération d'un dépôt existant
$ git clone url

Juste le dernier commit de chaque branche : --depth 1

Format des URLs :

ssh://[user@]host.xz[:port]/path/to/repo.git/
git://host.xz[:port]/path/to/repo.git/
http[s]://host.xz[:port]/path/to/repo.git/
ftp[s]://host.xz[:port]/path/to/repo.git/
rsync://host.xz/path/to/repo.git/
file:///path/to/repo.git/  =  /path/to/repo.git/

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)
Copyright CC BY-SA-NC Scott Chacon

Branching

  • Pseudo-branche = ref (pointeur) vers un commit
  • Créer une branche (fork) et s'y positionner :
    $ git branch mabranche
    $ git checkout mabranche
    ou : $ git checkout -b mabranche
    ou (nouvelle commande) : $ git switch mabranche 
    
  • Note : si changement de branche avec des modifications non commitées, git fusionne le contenu des copies locales sauf si conflit detecté.

Branching (suite)

  • Branche 'HEAD' = ref vers branche courante
  • Images Copyright CC BY-SA-NC Scott Chacon
  • Conventions :
    • master : branche par défaut
    • develop : branche de développment (instable)
    • feat-ma-fonctionnalite : une branche topic de nouvelle fonctionnalité
    • fix-1234 : une branche topic de correctif

Merge de branches

  • Merge branche develop dans la branche master (courante):
    $ git merge develop
  • Applique tous les commits de develop dans master depuis le dernier merge entre ces deux branches.
  • Si la branche courante n'a pas évolué depuis dernier merge, fast-forward (ff)
  • Si divergence (non-ff), merge automatique. Conflits possibles
Images Copyright CC BY-SA-NC Scott Chacon

Branches remote

  • Branche remote (distante) : branche locale cache d'une branche d'un dépôt distant

    Attention ! ce n'est qu'une image, un proxy de la branche distante, elle n'est pas rafraichie tant qu'on n'a pas fait un git fetch

  • Exemples :
    remotes/origin/HEAD -> origin/master
    remotes/origin/develop
    remotes/origin/master
    remotes/bob/feature300
  • 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)
    [remote "origin"]
    url=git@gitorious.org:/my-app.git
    fetch=+refs/heads/*:refs/remotes/origin/*
    pull=refs/heads/*:refs/remotes/origin/*
    push = refs/heads/master:refs/heads/qa/master
  • Après un git clone, branches tracking remote automatiquement créées : refs/origin/ branche

Branches remote

Tags

  • Un tag est une référence sur un commit + un auteur + un message + une signature numérique optionnelle
  • Vrai élement du dépôt
  • Créer un tag (option -a : "annoted"):
    $ git tag -a 1.0.0 -m 'Version  1.0'
  • Attention, tags non poussés par défaut :
    $ git push --tags
  • Lister les tags :
    git tag -l

Git supporte aussi des tags légers locaux (lightweight) qui sont simplement des branches en lecture seule. A éviter pour les livraisons officielles.

Cycle de vie d'un fichier dans la copie locale

  • Index (ou "staging area") : sas (facultatif) de "pré-commit"
  • Permet de sélectionner les modifications à commiter
  • Débrayable avec l'option -a de git commit
  • Attention : il faut obligatoirement faire git add pour un nouveau fichier
  • Status des fichiers : git status
  • Une fois prêt, commit : git commit
  • Commit interactif de partie de fichier avec l'option -p (patch)
TP2 Exercice concepts Git

Voir la feuille de travaux pratiques

Commandes git de base

Voir aussi la cheat sheet [5]


  • Commandes de changement de statut de la copie locale : checkout [-m], add, rm, mv, commit, stash
  • Commandes de correction : revert, reset (soft|mixed|hard), commit --amend, clean (supprime aussi les fichiers untracked)
  • Commandes de branching : branch, merge, cherry-pick, rebase
  • Commandes d'informations : log, status, diff, show, blame, reflog
  • Commandes de travail sur branches distantes : remote, push, fetch, pull

Historique

  • Commande de base: git log ou souvent (avec le sha) git log --oneline
  • Voir les diff de chaque révision : option git log -p
  • Visualiser le contenu complet d'un fichier f dans une ref x : git show x:f
  • Spécifier des intervales de temps
    git log --since=2.weeks
  • Voir les diff de chaque révision : option git show
  • Voir un graphe des révisions : option git log --graph
  • git log branche1..branche2 : tous les nouveaux commits de la branche2 uniquement
  • Recherche d'une chaîne de caractère dans tous les fichiers de tout l'historique : git rev-list --all | xargs git grep [chaîne ou expression régulière]

Comparaison de branches

  • Comparer deux références ('..' et 'HEAD' optionnels) :
    git diff HEAD..origin/hotfix/release-1_0
    git diff ..origin/hotfix/release-1_0
    git diff origin/hotfix/release-1_0
  • 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

Rebasing
Images Copyright CC BY-SA-NC Scott Chacon
$ git checkout experiment; git rebase master
$ git checkout master; git merge experiment
  • Sérialise deux branches, nos commits à la fin
  • 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
    TP3 Commandes Git de base

    Voir la feuille de travaux pratiques

    Les forges Git

    GitLab

    Les pull requests (PR)

    • On créé une branche et on y commit ses modification
    • On pousse les commits vers notre dépot personnel (Fork and pull) ou le dépot central (shared repository) sur la nouvelle branche
    • On créé une PR (Pull Request)
    • On en discute, si besoin on pousse de nouveaux commits. La PR continue à être alimentée.
    • La PR est acceptée manuellement ou automatiquement (CI) dans le dépot de référence
    • Voir ce manuel Github

    Les workflows les plus courants

    • Gitflow
    • Trunked-based

    Voir aussi man gitworkflows

    Gitflow (1/2)

    Copyright Vincent Driessen

    Gitflow (2/2)

    • Feature branches + branche de release et hotfix
    • Chaque commit de master = une version de production
    • Puissant mais complexe
    • Pas adapté pour gérer les branches de maintenance

    Trunked-based Developement (TBD)

    TP4 Utilitaires Git

    Voir la feuille de travaux pratiques


    Non abordé dans ce cours

    Voir [4] pour approfondir

    • Détail des différents types de protocoles
    • Les commandes plumbing
    • L'administration server-side (gitolite, gitweb etc.)
    • Les hooks client et serveur
    • La gestion des contributions exterieures ( git format-patch, git request-pull et git am)
    • Les attributs (comportement spécifique par chemin)
    • Les sous-modules
    • L'adaptateur git-svn
    • ...

    Références