Skip to content

quick start 02 collab

Julien Castiaux edited this page Mar 1, 2023 · 3 revisions

Démarrage - Collaboration sur une branche unique

Ce tutoriel vous accompagne dans la collaboration à plusieurs sur un projet hébergé sur Github. Au long de ce tutoriel nous verrons comment récupérer du code écrit par d'autres membres du projet, comment combiner son travail avec celui des autres et pour finir comment résoudre les éventuels conflits.

Ce tutoriel n'explique pas comment utiliser les branches ni comment faire des pull-request. Il n'explique par non plus les différentes techniques de fusions tel que les différences entre merge, rebase, fast-forward et cherry-pick. Ces thématiques sont abordées dans le prochain tutoriel.

Vous devez avoir suivi le premier tutoriel avant d’entamer celui-ci.

Collaboration (sans divergence) en ligne de commande

Préparation

Pour cet exercice, nous continuons d'utiliser le projet que vous avez créé dans le tutoriel précédent. Pour simuler que plusieurs personnes travaillent sur ce même projet nous allons le cloner une seconde fois sur le même ordinateur.

Entrez dans le premier projet et assurez vous d'être correctement synchronisé avec Github. Le git status doit dire "On branch main. Your branch is up to date with 'origin/main'. nothing to commit, working tree clean". Si ce n'est pas le cas, suivez les instructions qui sont affichés dans le résultat de la commande.

En guise d'exemple, nous avions cloné le projet la première fois dans le dossier ~/Ephec/sandbox, nous allons cloner le projet une seconde fois cette fois-ci dans le dossier ~/Ephec/bacasable. Le nom des dossiers importent peu.

~/Ephec $ git clone https://github.com/julien00859/sandbox bacasable
Cloning into 'bacasable'...
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (9/9), done.
remote: Total 11 (delta 4), reused 6 (delta 2), pack-reused 0
Receiving objects: 100% (11/11), done.
Resolving deltas: 100% (4/4), done.

~/Ephec $ ls
bacasable sandbox

Vous pouvez vérifier que les deux projets ont le même status et le même historique. Nous utilisons l'option --oneline avec git log pour avoir un affichage plus court.

$ cd ~/Ephec/sandbox
~/Ephec/sandbox$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

~/Ephec/sandbox$ git log --oneline
daaec07 (HEAD -> main, origin/main, origin/HEAD) doc(README): greet new users
0efccf0 Initial commit

$ cd ~/Ephec/bacasable
~/Ephec/bacasable$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean
~/Ephec/bacasable$ git log --oneline
daaec07 (HEAD -> main, origin/main, origin/HEAD) doc(README): greet new users
0efccf0 Initial commit

Rappelons que les hash des commits sont calculés aussi sur base du nom et de l'email de l'auteur ainsi que de la date et de l'heure. Vous devriez donc avoir des hash de commit différents. Vérifiez juste que ces hash concordent entre eux dans vos dossiers.

Commit et push depuis sandbox

Nous allons créer un commit du côté sandbox, l'envoyer sur Github et ensuite le récupérer sur bacasable.

Commençons par créer un commit, nous allons encore une fois modifier le fichier README.md. Ouvrez votre éditeur préféré, modifiez le fichier. Vérifiez que les changements ont été détecté avec git status et git diff. Créez un nouveau commit avec git add et git commit (vous pouvez utiliser l'option -a de git commit pour automatiquement ajouter tous les fichiers modifiés). Envoyez le commit sur Github avec git push.

~/Ephec/sandbox$ vim README.md  # modifier "Bonjour" par "Bonjour tout le monde"

~/Ephec/sandbox$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

~/Ephec/sandbox$ git diff
diff --git a/README.md b/README.md
index 51a4dfc..ece96c9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
 # sandbox
 Un bac-à-sable pour s'entrainer avec git et github

-Bonjour
+Bonjour tout le monde

~/Ephec/sandbox$ git commit -am "doc(README): better greeting"
[main b76619e] doc(README): better greeting
 1 file changed, 1 insertion(+), 1 deletion(-)

~/Ephec/sandbox$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 316 bytes | 316.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/Julien00859/sandbox.git
   daaec07..b76619e  main -> main

Vous pouvez ouvrir Github pour constater que le README a bel et bien changé et qu'il y a bel et bien eu un nouveau commit.

Fetch depuis bacasable (cli)

Déplacez-vous maintenant dans le dossier bacasable et entrez les commandes git status et git log.

~/Ephec/bacasable$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

~/Ephec/bacasable$ git log --oneline
daaec07 (HEAD -> main, origin/main, origin/HEAD) doc(README): greet new users
0efccf0 Initial commit

Le git status semble prétendre que nous sommes à jour avec Github, en étudiant le git log on constate qu'il n'a pas trace du nouveau commit que nous venons d'envoyer sur Github: nous somme en fait désynchronisé.

Pour demander à git de se resynchroniser nous pouvons utiliser la commande git fetch suivi de git status pour constater les changements:

~/Ephec/bacasable$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 2), reused 3 (delta 2), pack-reused 0
Unpacking objects: 100% (3/3), 296 bytes | 296.00 KiB/s, done.
From https://github.com/julien00859/sandbox
   daaec07..b76619e  main       -> origin/main

~/Ephec/bacasable$ git status
On branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

Nous attirons votre attention sur le résultat du git status:

On branch main. Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. Use git pull to update your local branch.

Ce message signifie qu'il existe un commit sur 'origin/main' (la branche main sur Github) qui n'est pas présent sur la branche main de notre ordinateur. Git nous informe aussi que nous pouvons fast-forward, c'est le jargon de git pour dire que notre historique est juste en retard sur l'historique de Github et qu'il n'y aura pas de conflit si on git pull.

Nous pouvons constater la même chose que git status en affichant l'historique des deux branches:

~/Ephec/bacasable$ git log main --oneline
daaec07 (HEAD -> main) doc(README): greet new users
0efccf0 Initial commit

~/Ephec/bacasable$ git log origin/main --oneline
b76619e (origin/main, origin/HEAD) doc(README): better greeting
daaec07 (HEAD -> main) doc(README): greet new users
0efccf0 Initial commit

Pull depuis bacasable (cli)

Maintenant que notre git est au courant des changements, nous pouvons télécharger les changements depuis Github avec la commande git pull.

~/Ephec/bacasable$ git pull
Updating daaec07..b76619e
Fast-forward
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Nous pouvons aussi constater les changements avec git status, git log et git show.

~/Ephec/bacasable$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

~/Ephec/bacasable$ git log --oneline
b76619e (HEAD -> main, origin/main, origin/HEAD) doc(README): better greeting
daaec07 doc(README): greet new users
0efccf0 Initial commit

~/Ephec/bacasable$ git show
commit b76619ed0dc71d824749f672c0d59e6343584ab5 (HEAD -> main, origin/main, origin/HEAD)
Author: Julien Castiaux <j.castiaux@ephec.be>
Date:   Sun Feb 12 16:32:29 2023 +0100

    doc(README): better greeting

diff --git a/README.md b/README.md
index 51a4dfc..ece96c9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
 # sandbox
 Un bac-à-sable pour s'entrainer avec git et github

-Bonjour
+Bonjour tout le monde

Nous pouvons aussi constater que le README a bel et bien été modifié, nous avons correctement récupérer les changements faits de l'autre côté.

~/Ephec/bacasable$ cat README.md
# sandbox
Un bac-à-sable pour s'entrainer avec git et github

Bonjour tout le monde

Collaboration (avec divergence) en ligne de commande

Préparation

Nous venons de voir le cas simple où un des PC était strictement en retard par rapport au travail effectué sur l'autre PC. Maintenant nous allons étudier la situation où du travail a été effectué sur chacun des PC et qu'il faut mettre ce travail en commun. Nous allons aussi voir comment résoudre des conflits.

Reprenez vos deux dossiers et assurez vous qu'ils soient synchronisés. Modifiez ensuite les fichiers README pour ajouter une ligne vide suivi de "depuis sandbox" dans le dossier sandbox et "depuis bacasable" dans le dossier bacasable, commitez les changements avec comme message "doc(README): greeting from sandox" et "doc(README): greeting from bacasable" respectivement. N'envoyez pas encore ces commits sur github.

Vous devriez avoir ceci dans le dossier sandbox (avec les options pretty=oneline et abbrev-commit à git show pour avoir un affichage plus courts) :

~/Ephec/sandbox$ git show --pretty=oneline --abbrev-commit
6d47ef0 (HEAD -> main) doc(README): greeting from sandbox
diff --git a/README.md b/README.md
index ece96c9..65266f2 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,5 @@
 Un bac-à-sable pour s'entrainer avec git et github

 Bonjour tout le monde
+
+depuis sandbox

~/Ephec/sandbox$ git log --oneline
6d47ef0 (HEAD -> main) doc(README): greeting from sandbox
b76619e (origin/main, origin/HEAD) doc(README): better greeting
daaec07 doc(README): greet new users
0efccf0 Initial commit

Et ceci dans le dossier bacasable :

~/Ephec/bacasable$ git show --pretty=oneline --abbrev-commit
45e6af4 (HEAD -> main) doc(README): greeting from bacasable
diff --git a/README.md b/README.md
index ece96c9..a7c5268 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,5 @@
 Un bac-à-sable pour s'entrainer avec git et github

 Bonjour tout le monde
+
+depuis bacasable

~/Ephec/bacasable$ git log --oneline
45e6af4 (HEAD -> main) doc(README): greeting from bacasable
b76619e (origin/main, origin/HEAD) doc(README): better greeting
daaec07 doc(README): greet new users
0efccf0 Initial commit

Vous pouvez maintenant envoyer les changements depuis le dossier sandbox avec git push.

(echec de) Push depuis bacasable (cli)

Déplacez-vous dans le dossier bacasable et tentez d'envoyer les changements, la commande va échouer avec un message d'erreur.

~/Ephec/bacasable$ git push
To https://github.com/julien00859/sandbox
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://github.com/julien00859/sandbox'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Encore une fois, nous attirons votre attention sur le message d'erreur:

Updates were rejected because the remote contains work that you do not have locally. This is usually caused by another repository pushing to the same ref. You may want to first integrate the remote changes (e.g., git pull ...) before pushing again.

Github a refusé notre push parce que notre historique ne contient pas tous les commits que Github connait. Il nous invite à récupérer ces changements (avec un git pull) d'abord et de ressayer de git push après.

Nous sommes dans une situation où les historiques ont divergé. Nous pouvons d'ailleurs le lire avec git status.

~/Ephec/bacasable$ git status
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

nothing to commit, working tree clean

Pull depuis bacasable et (résolution de) conflit (cli)

Nous allons suivre la recommandation et pull les changements. Notez qu'il n'est pas nécessaire de fetch, git a profité du push pour échanger avec Github quant à l'état des branches.

~/Ephec/bacasable$ git pull
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

(Si vous n'avez pas le message précédent mais plutôt un message vous demandant de choisir une configuration parmi trois options, faites git config --global pull.rebase false et recommencez)

Le message d'erreur nous apprend qu'il y a eu un conflit lorsque git a tenté de fusionner les changements. Il nous demande à nous humain beaucoup plus intelligent de lui bête programme de corriger ce conflit. Les instructions ne sont pas très claires dans ce message-ci mais elles le sont beaucoup plus dans le git status

~/Ephec/bacasable$ git status
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
    both modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

Le début est identique, le statut nous informe que notre branche a divergé de Github. Les deux paragraphes suivants sont nouveaux, on apprend que nous sommes dans en train de merge et qu'il y a un conflit dans le fichier README.md qui empêche git de continuer. Nous avons la possibilité soit de git merge --abort pour tout annuler et revenir à l'état où nous étions avant de git pull. Nous avons aussi la possibilité de résoudre le conflit et de continuer. Nous allons résoudre le conflit.

Ouvrez le fichier README.md avec votre éditeur préféré, le fichier devrait contenir ceci :

# sandbox
Un bac-à-sable pour s'entrainer avec git et github

Bonjour tout le monde

<<<<<<< HEAD
depuis bacasable
=======
depuis sandbox
>>>>>>> 6d47ef0ec538547de5c00f36b9094b5e3a9b034d

On retrouve le contenu du README, le début est normal mais la fin est bizarre avec <<<<<<< HEAD, =======, >>>>>>> 6d47ef0ec... et le texte des deux commits au milieu.

C'est un conflit.

Cette ligne de texte est différente entre les deux commits et git nous demande quoi faire. git est incapable de comprendre le texte, il est encore moins capable de savoir quoi faire. Il nous demande à nous autre, humain super intelligent, de bien vouloir résoudre ce conflit pour qu'il puisse continuer.

Un conflit arrive donc lorsque des lignes de code (ou dans le cas présent, des lignes de textes) sont modifiés dans deux commits séparés de manières indépendantes. On se rappelle que la ligne vide (celle entre "Bonjour tout le monde" et "depuis ...") avait aussi été ajouté de manière indépendante dans chacun des deux commits, git n'a pas bronché et a été capable de fusionner la ligne vide de chaque commit en une seule. Un conflit se produit uniquement lorsque les changements ne semblent pas compatibles.

Les balises <<<<<<< x, =======, >>>>>>> y permettent de séparer et d'identifier où se situe le conflit et de déterminer de quel commit provient chaque ligne. Dans le cas présent, on doit lire que "depuis bacasable" provient du commit HEAD et est en conflit avec "depuis sandbox" qui vient du commit 6d47ef0.

Résoudre le conflit consiste à réécrire le code (ici le texte) pour prendre en considération chaque commit. Il n'y a pas de solution universelle, il faut étudier chaque commit et comprendre comment combiner le code (ici le texte) ensemble pour que le programme reste juste. Vous êtes payés aussi pour ça. Ici après avoir utilisé beaucoup de jus de cerveau, nous décidons de résoudre le problème comme ceci :

# sandbox
Un bac-à-sable pour s'entrainer avec git et github

Bonjour tout le monde

depuis sandbox et bacasable

Vous devez maintenant signaler à git que vous avez résolu le problème en l'ajoutant (git add). Un nouveau tour de git status devrait vous dire que tous les conflits ont été résolus et que vous pouvez continuer avec un git commit.

~/Ephec/bacasable$ git add README.md
~/Ephec/bacasable$ git status
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
    modified:   README.md

Attention, faire un nouveau commit n'est nécessaire que lors d'un merge, si vous faites un rebase vous devrez alors git rebase --continue après avoir marqué la résolution des conflits. Lisez le statut attentivement et agissez en conséquence.

~/Ephec/bacasable$ git commit
[main bd8f074] Merge branch 'main' of https://github.com/julien00859/sandbox

Une fois fait, vous pouvez constater le résultat sur l'historique avec l'option --graph.

~/Ephec/bacasable$ git log --oneline --graph
*   bd8f074 (HEAD -> main) Merge branch 'main' of https://github.com/julien00859/sandbox
|\
| * 6d47ef0 (origin/main, origin/HEAD) doc(README): greeting from sandbox
* | 45e6af4 doc(README): greeting from bacasable
|/
* b76619e doc(README): better greeting
* daaec07 doc(README): greet new users
* 0efccf0 Initial commit

On constate bien une divergence entre les commits 6d47ef0 doc(README): greeting from sandbox et 45e6af4 doc(README): greeting from bacasable, on constate aussi bien que cette divergence avec le commit bd8f074 Merge branch... qui a fusionné les deux chemin.

Push depuis bacasable (après merge) (cli)

Nous sommes maintenant dans une situation où tous les commits présents sur Github on été incorporés avec succès sur notre PC, il est alors possible d'envoyer notre travail sur Github... :

~/Ephec/bacasable$ git push
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 647 bytes | 647.00 KiB/s, done.
Total 6 (delta 4), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (4/4), completed with 2 local objects.
To https://github.com/julien00859/sandbox
   6d47ef0..bd8f074  main -> main

... et de le récupérer dans l'autre dossier :

$ cd Ephec/sandbox
~/Ephec/sandbox$ git pull
Updating 6d47ef0..bd8f074
Fast-forward
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Aller plus loin

Cette méthode de travail, tout sur une seule branche, s'appelle dans la littérature centralized workflow et fonctionne bien pour les très petites équipes (maximum 3 personnes) ou bien pour initialiser un projet avant de bifurquer pour la feature-branch workflow.

L'avantage principale de cette méthodologie est de pouvoir rapidement collaborer sans trop se prendre la tête tout en profitant de Github pour facilement visualiser/partager son code.

Les désavantages sont par contre nombreux :

  • Il n'est pas possible de travailler sur plusieurs choses à la fois sur un même PC. (ou bien vous devez cloner plusieurs fois)
  • Il n'y a pas moyen de commencer à travailler sur une nouvelle fonctionnalité sur un PC et de la continuer sur un autre PC. (ou bien vous devez envoyer des commits incomplet)
  • Il n'y a pas moyen de demander à ses collègues de review notre travail avant de l'envoyer sur Github. (ou bien vos collègues doivent venir voir votre code sur votre ordinateur)
  • Il n'y a pas moyen d'empêcher des commits disfonctionnels (des commits qui contiennent des bugs révélés par les tests automatiques) d'être push sur Github. (à moins que tout le monde a configuré des pre-push hooks)
  • Il y a prolifération de merge commits dans l'historique ce qui le rend illisible. (à moins que tout le monde fassent des rebase à la place de merge i.e. git pull --rebase)