GIT Gud
Introduction
Git est un système de contrôle de version distribué et open source qui permet aux développeurs et aux équipes d'exploitation de collaborer et de suivre les modifications apportées à un projet.
En tant qu'outil DevOps, Git favorise la collaboration et l'accélération des cycles de publication. Toute personne désireuse de démarrer sa carrière DevOps ou de passer à un niveau supérieur doit commencer par les bases, et Git est l'exigence la plus fondamentale de toutes.
Bon nombre des projets open source les plus populaires aujourd'hui sont développés sur Github - Kubernetes, Ansible, TensorFlow, Rust, Node.js, Go, Terraform, Helm Charts étant quelques-uns des plus importants parmi les 100 millions de dépôts.
Repos locaux et distants
Git a 2 type de repos :
- Le repo local, qui se trouve sur votre machine, et auquel vous avez un accès direct,
- Le repo distant, qui se trouve généralement sur un serveur centralisé, et qui est optionnel.
Le repo distant est pensé comme un back-up de votre repo local.
Le repo local est divisé en 3 sections.
- La zone de travail où sont les fichiers sur lesquels vous travaillez, git ne fait rien avec ces fichiers, il sait juste que ces fichiers sont en train d'être modifiés.
- La zone de transit (staging area), contient les nouveaux changements qui seront bientôt versionnés.
- Les fichiers versionnés (committed files).
Installer git sur ubuntu
Initialiser un repo git
Pour initialiser un repo, placez vous, dans le terminal, dans le dossier dans lequel vous voulez versionner les changements et taper simplement git init
dans le terminal.
Maintenant que le repo est initialisé, Git surveille ce dossier et vois les changements qui y sont fait. On peut utiliser la commande git status
pour les voir.
Aucun fichiers n'est dans la zone des fichiers versionnés, pour versionner notre travail, on le fait en deux étapes.
- On envoie
story.txt
en zone de transit viagit add
, - On versionne
story.txt
avecgit commit
.
sequenceDiagram
participant "Working Area"
participant "Staging Area"
participant "Committed files"
Note over "Working Area": travail sur story.txt
activate "Working Area"
"Working Area"->>+"Staging Area": git add story.txt
deactivate "Working Area"
activate "Staging Area"
"Staging Area"->>"Committed files": git commit -m "commit story.txt"
deactivate "Staging Area"
Le -m
dans git commit -m "commit story.txt"
est l'argument pour ajouter un message au commit.
Revenir en arrière
Il est possible, lorsque l'on code, de vouloir revenir en arrière, soit parce que ce que l'on a écrit ne veut rien dire, ou alors parce que l'on a fait une erreur, ou autre.
Pour revenir en arrière il est possible d'utiliser la commande git restore
.
Il est possible de restaurer à deux moments :
- soit en zone de travail,
- soit en zone de transit.
Préliminaires
Définissons un répertoire suivi par git avec au moins un fichier dedans.
Question
Pourquoi au moins un fichier dedans ? Tout simplement par ce que git ne peut pas supprimer des fichiers d'un répertoire, la seule chose qu'il puisse faire est restaurer des fichiers à un état précedent, pourvu que ce fichier existe à l'état précédent.
Maintenant que l'on a un dossier suivi, voyons comment et ce que l'on peut restaurer.
En zone de travail
Ecrivons un poème.
Lorsque l'on tape la commande git status
, on peut voir alors la phrase (use "git restore <file>..." to discard changes in working directory)
.
Lorsque les fichiers modifiés sont encore en zone de travail, il est alors possible d'utiliser la commande git restore nom_du_fichier
pour annuler toutes les modifications faites à ce fichier depuis le dernier commit (ou git add ?).
Usage du restore sans commit | |
---|---|
En zone de transit
Si les modifications sont déjà en "staging area", prêtes à être versionnées. Il est encore possible de restaurer les fichiers mais en ajoutant cette fois ci l'argument --staged
.
Premier ajout | |
---|---|
Deuxième ajout | |
---|---|
Attention
Comme vous le voyez ici avec cat poeme.txt
, la commande git restore --staged poeme.txt
n'a pas supprimée la deuxième ligne de poeme.txt
. La commande git restore
, ne fait que restaurer l'état des fichiers avant la dernière commande git éxecutée. Ici dans notre cas, la dernière commande git éxecutée était git add .
, on est donc revenu à l'état d'avant cette commande, ie :
- le fichier
poeme.txt
avait 2 lignes, - il était prêt à être transféré en "staging area".
Pour supprimer la deuxième ligne, il faudrait alors de nouveau appliquer une commande git restore
.
git rm --cached
Ignorer
Il est possible de forcer git à ignorer des fichiers ou des répertoires en les ajoutant au fichier .gitignore
.
Les logs
Pour voir l'ensemble des commits d'un repo git, vous pouvez taper la commande suivante.
Structure d'un log git | |
---|---|
Un élément de log dans Git est constitué des éléments suivants :
- le hash du commit, qui est un identifiant alphanumérique unique au commit,
- l'auteur du commit,
- la date du commit,
- le texte écrit après l'argument
-m
.
git log --oneline
permet de voir l'ensemble de ces infos au format une seule ligne.
Remarque
Les fichiers versionnés n'apparaissent pas lorsque l'on utilise la commande git log
.
Pour les faire apparaître on peut utiliser la commande git log --name-only
Pour limiter l'affichage des logs aux k derniers commits, on utilise la commande git log -n k
.
Les branches Git
De façon basique, une branche est un pointeur vers un certain commit.
Crée une branche nommée dev | |
---|---|
Switcher sur la branche nommée dev | |
---|---|
Crée une branche nommée dev et switch dessus directement | |
---|---|
Supprime une branche nommée dev | |
---|---|
Liste toutes les branches | |
---|---|
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit
commit
branch dev
branch dev-mathieu
checkout dev
commit
commit id:"3-e2cf2c8"
checkout dev-mathieu
commit
commit
checkout dev
merge dev-mathieu
checkout main
merge dev
commit id:"mise en prod"
Qu'est ce que HEAD ?
Ce terme apparaît dans chaque commit que vous faites, par exemple dans l'exemple du dessus avec git log
, on a commit 83153b74fbedfde6c7bb6d6845ed56149829f86a (HEAD -> master)
.
HEAD
désigne votre position actuelle dans le graphe git. En prenant le graphe ci-dessus, si vous êtes positionné au dernier commit de la branche dev
, alors HEAD
sera 3-e2cf2c8
.
Pour voir sur quel commit est positionné HEAD
, vous pouvez taper la commande suivante.
Montre le commit où est HEAD | |
---|---|
HEAD -> master
désigne donc la branche (ici master
) dans le repo sur laquelle vous faites un git commit
. HEAD
pointe toujours vers le dernier commit sur la branche actuellement extraite.
git log --graph --decorate
permet d'avoir un graphe des différents commits, branch et merge de l'historique.
git merge
Pour fusionner deux branches, la marche à suivre est la suivante :
- se positionner sur la branche vers laquelle on veut fusionner,
- taper la commande
git merge branche_a_fusionner
.
Par exemple, supposons que je suis sur la branche dev-mathieu
, et que je souhaite fusionner mon travail vers la branche dev
.
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit
commit
branch dev
branch dev-mathieu
checkout dev
commit
commit
checkout dev-mathieu
commit
commit tag: "HEAD"
La marche à suivre est alors :
git checkout dev
,git merge dev-mathieu
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit
commit
branch dev
branch dev-mathieu
checkout dev
commit
commit
checkout dev-mathieu
commit
commit
checkout dev
merge dev-mathieu
On a deux type de merge
différents :
- fast-forward,
- no-fast-forward
Un fast-forward merge
se fait lorsque la branche vers laquelle on souhaite fusionner n'a pas de commits en dehors de celui créant la branche. Dans ce cas là, la fusion ne crée pas de nouveau commit mais fusionne les commits des deux branches.
fast-forward merge de dev-mathieu vers dev
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit
commit
branch dev
branch dev-mathieu
commit id:"2-6923bcf"
commit id:"3-9038f6c"
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit
commit
branch dev
commit id:"2-6923bcf"
commit id:"3-9038f6c"
Un no-fast-forward merge
se fait lorsque la branche vers laquelle on souhaite fusionner a au moins un commit en dehors de ceux présents sur la branche à fusionner. Dans ce cas là, la fusion créera un nouveau commit (un merge commit) en fusionnant les commits des deux branches.
no-fast-forward merge de dev-mathieu vers dev
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit
commit
branch dev
commit id:"2-aeb8393"
branch dev-mathieu
checkout dev
commit id:"3-e1bb5ee"
checkout dev-mathieu
commit id:"2-6923bcf"
commit id:"3-9038f6c"
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit
commit
branch dev
commit id:"2-aeb8393"
commit id:"3-e1bb5ee"
commit id:"2-6923bcf"
commit id:"3-9038f6c"
commit id:"Merge"
Initialiser et travailler avec un repo distant
Les solutions les plus connues pour héberger des repos distants sont les suivantes :
- GitHub,
- GitLab,
- Bitbucket,
Chacune des solutions ci-dessus, une fois un repo distant initialisé sur leur plateforme, founit une url de la forme :
https://github.com/my_organization/my_repo.git
,https://gitlab.com/my_organization/my_repo.git
,https://bitbucket.org/my_organization/my_repo.git
.
Pour ajouter ce repo distant à un projet local, on utilise la commande suivante.
Connexion repo local vers repo distant (github par exemple) | |
---|---|
Il est possibble de connecter plusieurs repos distants au même repo local.
Pour voir l'ensemble des repos distants connectés au repo local, on utlise git remote -v
.
Envoyer les commits vers le repo distant se fait via la commande git push origin main
.
sequenceDiagram
participant Working Area
participant Staging Area
participant Committed files
participant Repo distant
Note over Working Area: travail sur story.txt
activate Working Area
Working Area->>Staging Area: git add story.txt
deactivate Working Area
activate Staging Area
Staging Area->>Committed files: git commit -m "commit story.txt"
deactivate Staging Area
activate Committed files
Committed files->>Repo distant: git push origin main
deactivate Committed files
git clone
git fetch origin main
git pull origin main
git pull = git fetch + git merge
git fork
Différence git push
et git push origin main
Rebasing
Lorsque l'on travaille sur une branche distincte de la branche main
et que l'on souhaite mettre à jour sa branche avec les modifications apportées sur la branche main
, on a deux solutions.
- Faire un
merge
de la branchemain
vers notre branche.
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit id:"3-e1bb5ee"
branch dev
checkout main
commit id:"2-aeb8393"
checkout dev
commit id:"2-6923bcf"
commit id:"3-9038f6c"
git checkout dev
git merge main
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit id:"3-e1bb5ee"
branch dev
checkout main
commit id:"2-aeb8393"
checkout dev
commit id:"2-6923bcf"
commit id:"3-9038f6c"
merge main
2. Utiliser la commande rebase
.
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit id:"3-e1bb5ee"
branch dev
checkout main
commit id:"2-aeb8393"
checkout dev
commit id:"2-6923bcf"
commit id:"3-9038f6c"
git checkout dev
git rebase main
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'main'}} }%%
gitGraph
commit id:"3-e1bb5ee"
commit id:"2-aeb8393"
branch dev
checkout dev
commit id:"2-6893bcf"
commit id:"3-908526c"
La commande rebase
va modifier la base de la branche dev
pour la positionner au dernier commit de la branche main
, et donc mettre à jour les fichiers, sans pour autant modifier la position de la branche main
.
Une différence principale est que lors d'un merge
, les hash des commits ne changent pas. Lors d'un rebase, comme l'on copie les fichiers à une nouvelle place, cela compte comme une opération et les hash se mettent à jour.
En d'autres termes, faire un rebase
modifie l'historique Git, alors que ce n'est pas le cas pour un merge
.
git rebase -i HEAD~k
, permet de faire un rebase interactif des k derniers commits, pour par exemple faire du "squash commit".
git cherry-pick hash
Reset et revert
Les commandes git reset
et git revert
sont toutes les deux des commandes permettant de revenir en arrière dans le graphe git. Mais leurs utilisations changent.
git revert hash
La commande git revert
permet de supprimer toutes les modifications faites lors du commit désigné par son hash (généralement le commit précédent).
En d'autres termes, si un fichier à était ajouté lors du commit précédent, appliquer git revert
sur ce commit supprimera ce fichier du graphe git.
La commande git revert
crée un nouveau commit, ce qui permet de suivre les modifications.
Remarque
Pour faire un revert sur le commit le plus récent, on peut aussi utliser la commande git revert HEAD~0
.
git reset
La commande git reset
permet elle de supprimer directemnts des commit du graphe git. Selon l'argument qu'on lui assignera --soft
ou --hard
les modifications qui étaient présentes dans ces commits :
- retourneront en zone de transit (staging area), avec l'argument
--soft
, - seront purement et simplement supprimées, avec l'argument
--hard
.
HEAD~k
permet de déterminer sur combien de commits on doit revenir en arrière, à partir du pointeur HEAD
, ici on revient en arrière sur k commits.
git reset --soft HEAD~k
Remarquez que le commit be87880
, avant git reset --soft HEAD~1
, n'apparaît pas dans les logs.
note.txt est retourné en staging area | |
---|---|
git reset --hard HEAD~k
git stash
Permet de mettre en cache des documents qui sont dans la staging area pour par exemple eviter de les commit de façon accidentelle.
git stash
git stash pop
git stash pop stash_id
git stash show stash_id
sequenceDiagram
participant Working Area
participant Staging Area
participant Stash
Note over Working Area: travail sur story.txt
activate Working Area
Working Area->>Staging Area: git add story.txt
deactivate Working Area
activate Staging Area
Staging Area->>Stash: git stash
deactivate Staging Area
Les deux fichiers sans visibles dans stash | |
---|---|
git stash show
.
On peut lancer plusieurs commandes à ma suite :
git stash show stash@{0} ; git stash show stash@{1}; git stash show stash@{2}
stash
marche comme une pile, on est en mode LIFO : Last In First Out.
git reflog
tags et release
Commits Conventionnels
Le commit contient les éléments structurels suivants, permettant de communiquer à l’intention des consommateurs de votre bibliothèque:
fix
: un commit de typefix
corrige un bogue dans le code (cela est en corrélation avecPATCH
en versioning sémantique).feat
: un commit de typefeat
introduit une nouvelle fonctionnalité dans le code (cela est en corrélation avecMINOR
en versioning sémantique).BREAKING CHANGE
: un commit qui a dans le pied de page le mot clefBREAKING CHANGE
:, ou ajoute un!
après le type/scope, introduit un changement cassant l’API (cela est en corrélation avecMAJOR
en versioning sémantique). UnBREAKING CHANGE
peut faire partie des commits de n’importe quel type.- Les types autre que
fix:
etfeat:
sont autorisés, par exemple @commitlint/config-conventional (basé sur the Angular convention) recommandebuild:
,chore:
,ci:
,docs:
,style:
,refactor:
,perf:
,test:
, etc. - Les pieds de pages autre que
BREAKING CHANGE: <description>
peuvent être fourni et suivre une convention similaire à git trailer format.