-
1. DĂ©marrage rapide
-
2. Les bases de Git
-
3. Les branches avec Git
-
4. Git sur le serveur
- 4.1 Protocoles
- 4.2 Installation de Git sur un serveur
- 4.3 Génération des clés publiques SSH
- 4.4 Mise en place du serveur
- 4.5 DĂ©mon (Daemon) Git
- 4.6 HTTP intelligent
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Git hébergé
- 4.10 Résumé
-
5. Git distribué
-
6. GitHub
-
7. Utilitaires Git
- 7.1 SĂ©lection des versions
- 7.2 Indexation interactive
- 7.3 Remisage et nettoyage
- 7.4 Signer votre travail
- 7.5 Recherche
- 7.6 RĂ©Ă©crire lâhistorique
- 7.7 Reset démystifié
- 7.8 Fusion avancée
- 7.9 Rerere
- 7.10 DĂ©boguer avec Git
- 7.11 Sous-modules
- 7.12 Empaquetage (bundling)
- 7.13 Replace
- 7.14 Stockage des identifiants
- 7.15 Résumé
-
8. Personnalisation de Git
- 8.1 Configuration de Git
- 8.2 Attributs Git
- 8.3 Crochets Git
- 8.4 Exemple de politique gérée par Git
- 8.5 Résumé
-
9. Git et les autres systĂšmes
- 9.1 Git comme client
- 9.2 Migration vers Git
- 9.3 Résumé
-
10. Les tripes de Git
- 10.1 Plomberie et porcelaine
- 10.2 Les objets de Git
- 10.3 Références Git
- 10.4 Fichiers groupés
- 10.5 La refspec
- 10.6 Les protocoles de transfert
- 10.7 Maintenance et récupération de données
- 10.8 Les variables dâenvironnement
- 10.9 Résumé
-
A1. Annexe A: Git dans dâautres environnements
- A1.1 Interfaces graphiques
- A1.2 Git dans Visual Studio
- A1.3 Git dans Visual Studio Code
- A1.4 Git dans IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git dans Sublime Text
- A1.6 Git dans Bash
- A1.7 Git dans Zsh
- A1.8 Git dans PowerShell
- A1.9 Résumé
-
A2. Annexe B: Embarquer Git dans vos applications
- A2.1 Git en ligne de commande
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Commandes Git
- A3.1 Installation et configuration
- A3.2 Obtention et création des projets
- A3.3 Capture dâinstantanĂ© basique
- A3.4 Création de branches et fusion
- A3.5 Partage et mise Ă jour de projets
- A3.6 Inspection et comparaison
- A3.7 DĂ©bogage
- A3.8 Patchs
- A3.9 Courriel
- A3.10 SystĂšmes externes
- A3.11 Administration
- A3.12 Commandes de plomberie
7.8 Utilitaires Git - Fusion avancée
Fusion avancée
La fusion avec Git est gĂ©nĂ©ralement plutĂŽt facile. Puisque Git rend facile la fusion dâune autre branche plusieurs fois, cela signifie que vous pouvez avoir une branche Ă trĂšs longue durĂ©e de vie que vous pouvez mettre Ă jour au fil de lâeau, en rĂ©solvant souvent les petits conflits plutĂŽt que dâĂȘtre surpris par un Ă©norme conflit Ă la fin de la sĂ©rie.
Cependant, il arrive quelques fois des conflits compliquĂ©s. Ă la diffĂ©rence dâautres systĂšmes de contrĂŽle de version, Git nâessaie pas dâĂȘtre plus intelligent que de mesure pour la rĂ©solution des conflits. La philosophie de Git, câest dâĂȘtre malin pour dĂ©terminer lorsque la fusion est sans ambiguĂŻtĂ© mais sâil y a un conflit, il nâessaie pas dâĂȘtre malin pour le rĂ©soudre automatiquement. De ce fait, si vous attendez trop longtemps pour fusionner deux branches qui divergent rapidement, vous rencontrerez des problĂšmes.
Dans cette section, nous allons dĂ©tailler ce que certains de ces problĂšmes peuvent ĂȘtre et quels outils Git vous offre pour vous aider Ă gĂ©rer ces situations dĂ©licates. Nous traiterons aussi quelques types de fusions diffĂ©rents, non-standard, ainsi que la maniĂšre de mĂ©moriser les rĂ©solutions que vous avez dĂ©jĂ rĂ©alisĂ©es.
Conflits de fusion
Bien que nous avons couvert les bases de la résolution de conflits dans Conflits de fusions (Merge conflicts), pour des conflits plus complexes, Git fournit quelques outils pour vous aider à vous y retrouver et à mieux gérer les conflits.
PremiĂšrement, si câest seulement possible, essayer de dĂ©marrer dâun rĂ©pertoire de travail propre avant de commencer une fusion qui pourrait engendrer des conflits. Si vous avez un travail en cours, validez-le dans une branche temporaire ou remisez-le. Cela vous permettra de dĂ©faire tout ce que vous pourrez essayer. Si vous avez des modifications non sauvegardĂ©es dans votre rĂ©pertoire de travail quand vous essayez une fusion, certaines des astuces qui vont suivre risque de vous faire perdre ce travail.
Parcourons ensemble un exemple trÚs simple. Nous avons un fichier Ruby super simple qui affiche « hello world ».
#! /usr/bin/env ruby
def hello
puts 'hello world'
end
hello()
Dans notre dépÎt, nous créons une nouvelle branche appelée whitespace
et nous entamons la transformation de toutes les fins de ligne Unix en fin de lignes DOS, ce qui revient Ă modifier chaque ligne, mais juste avec des caractĂšres invisibles.
Ensuite, nous changeons la ligne « hello world » en « hello mundo ».
$ git checkout -b whitespace
Basculement sur la nouvelle branche 'whitespace'
$ unix2dos hello.rb
unix2dos: converting file hello.rb to DOS format ...
$ git commit -am 'converted hello.rb to DOS'
[whitespace 3270f76] converted hello.rb to DOS
1 file changed, 7 insertions(+), 7 deletions(-)
$ vim hello.rb
$ git diff -w
diff --git a/hello.rb b/hello.rb
index ac51efd..e85207e 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,7 @@
#! /usr/bin/env ruby
def hello
- puts 'hello world'
+ puts 'hello mundo'^M
end
hello()
$ git commit -am 'hello mundo change'
[whitespace 6d338d2] hello mundo change
1 file changed, 1 insertion(+), 1 deletion(-)
à présent, nous rebasculons sur master
et nous ajoutons une documentation de la fonction.
$ git checkout master
Basculement sur la branche 'master'
$ vim hello.rb
$ git diff
diff --git a/hello.rb b/hello.rb
index ac51efd..36c06c8 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
puts 'hello world'
end
$ git commit -am 'document the function'
[master bec6336] document the function
1 file changed, 1 insertion(+)
Et maintenant, nous essayons de fusionner notre branche whitespace
et nous allons générer des conflits dûs aux modifications de fins de ligne.
$ git merge whitespace
Fusion automatique de hello.rb
CONFLIT (contenu) : Conflit de fusion dans hello.rb
La fusion automatique a échoué ; réglez les conflits et validez le résultat.
Abandonner une fusion
Nous avons ici plusieurs options.
Une premiĂšre consiste Ă sortir de cette situation.
Vous ne vous attendiez peut-ĂȘtre pas Ă rencontrer un conflit et vous ne souhaitez pas encore le gĂ©rer, alors vous pouvez simplement faire marche arriĂšre avec git merge --abort
.
$ git status -sb
## master
UU hello.rb
$ git merge --abort
$ git status -sb
## master
Lâoption git merge --abort
essaie de vous ramener Ă lâĂ©tat prĂ©cĂ©dent la fusion.
Les seuls cas dans lesquels il nây parvient pas parfaitement seraient ceux pour lesquels vous aviez dĂ©jĂ auparavant des modifications non validĂ©es ou non remisĂ©es dans votre rĂ©pertoire de travail au moment de la fusion.
Sinon, tout devrait se passer sans problĂšme.
Si, pour une raison quelconque, vous vous trouvez dans une situation horrible et que vous souhaitez repartir à zéro, vous pouvez aussi lancer git reset --hard HEAD
ou sur toute autre rĂ©fĂ©rence oĂč vous souhaitez revenir.
Souvenez-vous tout de mĂȘme que cela va balayer toutes les modifications de votre rĂ©pertoire de travail, donc assurez-vous de nâavoir aucune modification de valeur avant.
Ignorer les caractĂšres invisibles
Dans ce cas spécifique, les conflits sont dûs à des espaces blancs. Nous le savons parce que le cas est simple, mais cela reste assez facile à déterminer dans les cas réels en regardant les conflits parce que chaque ligne est supprimée puis réintroduite modifiée. Par défaut, Git voit toutes ces lignes comme modifiées et il ne peut pas fusionner les fichiers.
La stratĂ©gie de fusion par dĂ©faut accepte quand mĂȘme des arguments, et certains dâentre eux traitent le cas des modifications impliquant les caractĂšres blancs.
Si vous vous rendez compte que vous avez de nombreux conflits de caractĂšres blancs lors dâune fusion, vous pouvez simplement abandonner la fusion et en relancer une en utilisant les options -Xignore-all-space
ou -Xignore-space-change
.
La premiĂšre option ignore complĂštement tous les espaces tandis que la seconde traite les sĂ©quences dâun ou plusieurs espaces comme Ă©quivalentes.
$ git merge -Xignore-all-space whitespace
Fusion automatique de hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
Puisque dans ce cas, les modifications rĂ©elles nâentraient pas en conflit, une fois les modifications dâespaces ignorĂ©es, tout fusionne parfaitement bien.
Ăa sauve la vie si vous avez dans votre Ă©quipe une personne qui reformate tous les espaces en tabulations ou vice-versa.
Re-fusion manuelle dâun fichier
Bien que Git gĂšre le prĂ©-traitement dâespaces plutĂŽt bien, il existe dâautres types de modifications que Git ne peut pas gĂ©rer automatiquement, mais dont la fusion peut ĂȘtre scriptable. Par exemple, supposons que Git nâait pas pu gĂ©rer les espaces et que nous ayons dĂ» rĂ©soudre le problĂšme Ă la main.
Ce que nous devons réellement faire est de passer le fichier que nous cherchons à fusionner à travers dos2unix
avant dâessayer de le fusionner rĂ©ellement.
Comment pourrions-nous nous y prendre ?
PremiĂšrement, nous entrons dans lâĂ©tat de conflit de fusion. Puis, nous voulons obtenir des copies de la version locale (ours), de la version distante (theirs, celle qui vient de la branche Ă fusionner) et de la version commune (lâancĂȘtre commun depuis lequel les branches sont parties). Ensuite, nous voulons corriger au choix la version locale ou la distante et rĂ©essayer de fusionner juste ce fichier.
Obtenir les trois versions des fichiers est en fait assez facile.
Git stocke toutes ces versions dans lâindex sous forme dâĂ©tapes (stages), chacune associĂ©e Ă un nombre.
Stage 1 est lâancĂȘtre commun, stage 2 est notre version, stage 3 est la version de MERGE_HEAD
, la version quâon cherche Ă fusionner (theirs).
Vous pouvez extraire une copie de chacune de ces versions du fichier en conflit avec la commande git show
et une syntaxe spéciale.
$ git show :1:hello.rb > hello.common.rb
$ git show :2:hello.rb > hello.ours.rb
$ git show :3:hello.rb > hello.theirs.rb
Si vous voulez rentrer un peu plus dans le dur, vous pouvez aussi utiliser la commande de plomberie ls-files -u
pour récupérer les SHA-1 des blobs Git de chacun de ces fichiers.
$ git ls-files -u
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1 hello.rb
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2 hello.rb
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb
La syntaxe :1:hello.rb
est juste un raccourcis pour la recherche du SHA-1 de ce blob.
Ă prĂ©sent que nous avons le contenu des trois Ă©tapes dans notre rĂ©pertoire de travail, nous pouvons rĂ©parer manuellement la copie distante pour rĂ©soudre le problĂšme dâespaces et re-fusionner le fichier avec la commande mĂ©connue git merge-file
dont câest lâexacte fonction.
$ dos2unix hello.theirs.rb
dos2unix: converting file hello.theirs.rb to Unix format ...
$ git merge-file -p \
hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb
$ git diff -w
diff --cc hello.rb
index 36c06c8,e85207e..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,8 -1,7 +1,8 @@@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
à ce moment, nous avons un fichier joliment fusionné.
En fait, cela fonctionne mĂȘme mieux que lâoption ignore-all-space
parce que le problĂšme dâespace est corrigĂ© avant la fusion plutĂŽt que simplement ignorĂ©.
Dans la fusion ignore-all-space
, nous avons en fait obtenu quelques lignes contenant des fins de lignes DOS, ce qui a mélangé les styles.
Si vous voulez vous faire une idĂ©e avant de finaliser la validation sur ce qui a rĂ©ellement changĂ© entre un cĂŽtĂ© et lâautre, vous pouvez demander Ă git diff
de comparer le contenu de votre rĂ©pertoire de travail que vous ĂȘtes sur le point de valider comme rĂ©sultat de la fusion avec nâimporte quelle Ă©tape.
DĂ©taillons chaque comparaison.
Pour comparer votre rĂ©sultat avec ce que vous aviez dans votre branche avant la fusion, en dâautres termes, ce que la fusion a introduit, vous pouvez lancer git diff --ours
$ git diff --ours
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index 36c06c8..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -2,7 +2,7 @@
# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
Donc nous voyons ici que ce qui est arrivĂ© Ă notre branche, ce que nous introduisons rĂ©ellement dans ce fichier avec cette fusion, nâest quâune ligne modifiĂ©e.
Si nous voulons voir le résultat de la fusion modifiée depuis la version distante, nous pouvons lancer git diff --theirs
.
Dans cet exemple et le suivant, nous devons utiliser -w
pour éliminer les espaces parce que nous le comparons à ce qui est dans Git et non pas notre version nettoyée hello.theirs.rb
du fichier.
$ git diff --theirs -w
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index e85207e..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
puts 'hello mundo'
end
Enfin, nous pouvons voir comment le fichier a été modifié dans les deux branches avec git diff --base
.
$ git diff --base -w
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index ac51efd..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,8 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
Ă ce point, nous pouvons utiliser la commande git clean
pour éliminer les fichiers supplémentaires maintenant inutiles que nous avons créés pour notre fusion manuelle.
$ git clean -f
Suppression de hello.common.rb
Suppression de hello.ours.rb
Suppression de hello.theirs.rb
Examiner les conflits
Peut-ĂȘtre ne sommes-nous pas heureux de la rĂ©solution actuelle, ou bien lâĂ©dition Ă la main dâun cĂŽtĂ© ou des deux ne fonctionne pas correctement et nĂ©cessite plus de contexte.
Modifions un peu lâexemple. Pour cet exemple, nous avons deux branches Ă longue durĂ©e de vie qui comprennent quelques commits mais crĂ©ent des conflits de contenu lĂ©gitimes Ă la fusion.
$ git log --graph --oneline --decorate --all
* f1270f7 (HEAD, master) update README
* 9af9d3b add a README
* 694971d update phrase to hola world
| * e3eb223 (mundo) add more tests
| * 7cff591 add testing script
| * c3ffff1 changed text to hello mundo
|/
* b7dcc89 initial hello world code
Nous avons maintenant trois commits uniques qui nâexistent que sur la branche master
et trois autres sur la branche mundo
.
Si nous essayons de fusionner la branche mundo
, nous obtenons un conflit.
$ git merge mundo
Fusion automatique de hello.rb
CONFLIT (contenu): Conflit de fusion dans hello.rb
La fusion automatique a échoué ; réglez les conflits et validez le résultat.
Nous souhaitons voir ce qui constitue le conflit de fusion. Si nous ouvrons le fichier, nous verrons quelque chose comme :
#! /usr/bin/env ruby
def hello
<<<<<<< HEAD
puts 'hola world'
======
puts 'hello mundo'
>>>>>>> mundo
end
hello()
Les deux cĂŽtĂ©s de la fusion on ajoutĂ© du contenu au fichier, mais certains commits ont modifiĂ© le fichier au mĂȘme endroit, ce qui a causĂ© le conflit.
Explorons quelques outils que vous avez Ă disposition pour dĂ©terminer comment ce conflit est apparu. Peut-ĂȘtre le moyen de rĂ©soudre nâest-il pas Ă©vident. Il nĂ©cessite plus de contexte.
Un outil utile est git checkout
avec lâoption --conflict
.
Il va re-extraire le fichier et remplacer les marqueurs de conflit.
Cela peut ĂȘtre utile si vous souhaitez Ă©liminer les marqueurs et essayer de rĂ©soudre le conflit Ă nouveau.
Vous pouvez passer en paramĂštre Ă --conflict
, soit diff3
soit merge
(le paramÚtre par défaut).
Si vous lui passez diff3
, Git utilisera une version différente des marqueurs de conflit, vous fournissant non seulement les versions locales (ours) et distantes (theirs), mais aussi la version « base » intégrée pour vous fournir plus de contexte.
$ git checkout --conflict=diff3 hello.rb
Une fois que nous lâavons lancĂ©, le fichier ressemble Ă ceci :
#! /usr/bin/env ruby
def hello
<<<<<<< ours
puts 'hola world'
||||||| base
puts 'hello world'
======
puts 'hello mundo'
>>>>>>> theirs
end
hello()
Si vous appréciez ce format, vous pouvez le régler comme défaut pour les futur conflits de fusion en renseignant le paramÚtre merge.conflictstyle
avec diff3
.
$ git config --global merge.conflictstyle diff3
La commande git checkout
peut aussi accepter les options --ours
et --theirs
, qui peuvent servir de moyen rapide de choisir unilatéralement une version ou une autre sans fusion.
Cela peut ĂȘtre particuliĂšrement utile pour les conflits de fichiers binaires oĂč vous ne pouvez que choisir un des cĂŽtĂ©, ou des conflits oĂč vous souhaitez fusionner certains fichiers depuis dâautres branches - vous pouvez fusionner, puis extraire certains fichiers depuis un cĂŽtĂ© ou un autre avant de valider le rĂ©sultat.
Journal de fusion
Un autre outil utile pour la résolution de conflits de fusion est git log
.
Cela peut vous aider à obtenir du contexte ce qui a contribué aux conflits.
Parcourir un petit morceau de lâhistorique pour se rappeler pourquoi deux lignes de dĂ©veloppement ont touchĂ© au mĂȘme endroit dans le code peut sâavĂ©rer quelque fois trĂšs utile.
Pour obtenir une liste complÚte de tous les commits uniques qui ont été introduits dans chaque branche impliquée dans la fusion, nous pouvons utiliser la syntaxe « triple point » que nous avons apprise dans Triple point.
$ git log --oneline --left-right HEAD...MERGE_HEAD
< f1270f7 update README
< 9af9d3b add a README
< 694971d update phrase to hola world
> e3eb223 add more tests
> 7cff591 add testing script
> c3ffff1 changed text to hello mundo
Voilà une belle liste des six commits impliqués, ainsi que chaque ligne de développement sur laquelle chaque commit se trouvait.
NĂ©anmoins, nous pouvons simplifier encore plus ceci pour fournir beaucoup plus de contexte.
Si nous ajoutons lâoption --merge
Ă git log
, il nâaffichera que les commits de part et dâautre de la fusion qui modifient un fichier prĂ©sentant un conflit.
$ git log --oneline --left-right --merge
< 694971d update phrase to hola world
> c3ffff1 changed text to hello mundo
Si nous lançons cela avec lâoption -p
Ă la place, vous obtenez les diffs limitĂ©s au fichier qui sâest retrouvĂ© en conflit.
Cela peut sâavĂ©rer vraiment utile pour vous donner le contexte nĂ©cessaire Ă la comprĂ©hension de la raison dâun conflit et Ă sa rĂ©solution intelligente.
Format de diff combiné
Puisque Git indexe tous les résultats de fusion couronnés de succÚs, quand vous lancez git diff
dans un Ă©tat de conflit de fusion, vous nâobtenez que ce qui toujours en conflit Ă ce moment.
Il peut sâavĂ©rer utile de voir ce qui reste Ă rĂ©soudre.
Quand vous lancez git diff
directement aprĂšs le conflit de fusion, il vous donne de lâinformation dans un format de diff plutĂŽt spĂ©cial.
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,11 @@@
#! /usr/bin/env ruby
def hello
++<<<<<<< HEAD
+ puts 'hola world'
++=======
+ puts 'hello mundo'
++>>>>>>> mundo
end
hello()
Ce format sâappelle « diff combiné » (combined diff) et vous fournit deux colonnes dâinformation sur chaque ligne. La premiĂšre colonne indique que la ligne est diffĂ©rente (ajoutĂ©e ou supprimĂ©e) entre la branche « ours » et le fichier dans le rĂ©pertoire de travail. La seconde colonne fait de mĂȘme pour la branche « theirs » et la copie du rĂ©pertoire de travail.
Donc dans cet exemple, vous pouvez voir que les lignes <<<<<<<
et >>>>>>>
sont dans la copie de travail mais nâĂ©taient dans aucun des deux cĂŽtĂ©s de la fusion.
Câest logique parce que lâoutil de fusion les a collĂ©s ici pour donner du contexte, mais nous devrons les retirer.
Si nous résolvons le conflit et relançons git diff
, nous verrons la mĂȘme chose, mais ce sera un peu plus utile.
$ vim hello.rb
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
hello()
Ceci nous montre que « hola world » Ă©tait prĂ©sent de notre cĂŽtĂ© mais pas dans la copie de travail, que « hello mundo » Ă©tait prĂ©sent de lâautre cĂŽtĂ© mais pas non plus dans la copie de travail et que finalement, « hola mundo » nâĂ©tait dans aucun des deux cĂŽtĂ©s, mais se trouve dans la copie de travail. Câest particuliĂšrement utile lors dâune revue avant de valider la rĂ©solution.
Vous pouvez aussi lâobtenir depuis git log
pour toute fusion pour visualiser comment quelque chose a été résolu aprÚs coup.
Git affichera ce format si vous lancez git show
sur un commit de fusion, ou si vous ajoutez une option --cc
Ă git log -p
(qui par défaut ne montre que les patchs des commits qui ne sont pas des fusions).
$ git log --cc -p -1
commit 14f41939956d80b9e17bb8721354c33f8d5b5a79
Merge: f1270f7 e3eb223
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Sep 19 18:14:49 2014 +0200
Merge branch 'mundo'
Conflicts:
hello.rb
diff --cc hello.rb
index 0399cd5,59727f0..e1d0799
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
hello()
DĂ©faire des fusions
Comme vous savez crĂ©er des commits de fusion Ă prĂ©sent, vous allez certainement en faire par erreur. Un des grands avantages de lâutilisation de Git est quâil nâest pas interdit de faire des erreurs, parce quâil reste toujours possible (et trĂšs souvent facile) de les corriger.
Les commits de fusion ne font pas exception.
Supposons que vous avez commencĂ© Ă travailler sur une branche thĂ©matique, que vous lâavez accidentellement fusionnĂ©e dans master
et quâen consĂ©quence votre historique ressemble Ă ceci :
Il existe deux façons dâaborder ce problĂšme, en fonction du rĂ©sultat que vous souhaitez obtenir.
Correction des références
Si le commit de fusion non dĂ©sirĂ© nâexiste que dans votre dĂ©pĂŽt local, la solution la plus simple et la meilleure consiste Ă dĂ©placer les branches pour quâelles pointent oĂč on le souhaite.
La plupart du temps, en faisant suivre le git merge
malencontreux par un git reset --hard HEAD~
, on remet les pointeurs de branche dans lâĂ©tat suivant :
git reset --hard HEAD~
Nous avons détaillé reset
dans Reset dĂ©mystifiĂ© et il ne devrait pas ĂȘtre trĂšs difficile de comprendre ce rĂ©sultat.
Voici néanmoins un petit rappel : reset --hard
réalise généralement trois étapes :
-
Déplace la branche pointée par HEAD ; dans notre cas, nous voulons déplacer
master
sur son point avant la fusion (C6
), -
Faire ressembler lâindex Ă HEAD,
-
Faire ressembler le rĂ©pertoire de travail Ă lâindex.
Le dĂ©faut de cette approche est quâelle rĂ©-Ă©crit lâhistorique, ce qui peut ĂȘtre problĂ©matique avec un dĂ©pĂŽt partagĂ©.
Reportez-vous Ă Les dangers du rebasage pour plus dâinformation ; en rĂ©sumĂ© si dâautres personnes ont dĂ©jĂ les commits que vous rĂ©-Ă©crivez, il vaudrait mieux Ă©viter un reset
.
Cette approche ne fonctionnera pas non plus si dâautres commits ont Ă©tĂ© crĂ©Ă©s depuis la fusion ; dĂ©placer les rĂ©fĂ©rences des branches Ă©liminera effectivement ces modifications.
Inverser le commit
Si les dĂ©placements des pointeurs de branche ne sont pas envisageables, Git vous donne encore lâoption de crĂ©er un nouveau commit qui dĂ©fait toutes les modifications dâun autre dĂ©jĂ existant. Git appelle cette option une « inversion » (revert), et dans ce scĂ©nario particulier, vous lâinvoqueriez comme ceci :
$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'"
Lâoption -m 1
indique quel parent est le principal et devrait ĂȘtre conservĂ©.
Si vous invoquez une fusion dans HEAD
(git merge topic
), le nouveau commit a deux parents : le premier est HEAD
(C6
), et le second est le sommet de la branche en cours de fusion (C4
).
Dans ce cas, nous souhaitons défaire toutes les modifications introduites dans le parent numéro 2 (C4
), tout en conservant tout le contenu du parent numéro 1 (C6
).
Lâhistorique avec le commit dâinversion ressemble Ă ceci :
git revert -m 1
Le nouveau commit ^M
a exactement le mĂȘme contenu que C6
, et partant de lĂ , câest comme si la fusion nâavait pas eu lieu, mis Ă part que les commits qui ne sont plus fusionnĂ©s sont toujours dans lâhistorique de HEAD
.
Git sera confus si vous tentez de re-fusionner topic
dans master
 :
$ git merge topic
Already up-to-date.
Il nây a rien dans topic
qui ne soit pas déjà joignable depuis master
.
Pire encore, si vous ajoutez du travail Ă topic
et re-fusionnez, Git nâajoutera que les modifications depuis la fusion inversĂ©e :
Le meilleur contournement de ceci est de dé-inverser la fusion originale, puisque vous voulez ajouter les modifications qui ont été annulées, puis de créer un nouveau commit de fusion :
$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic
Dans cet exemple, M
et ^M
sâannulent.
^^M
fusionne effectivement les modifications depuis C3
et C4
, et C8
fusionne les modifications depuis C7
, donc à présent, topic
est totalement fusionnée.
Autres types de fusions
Jusquâici, nous avons traitĂ© les fusions normales entre deux branches qui ont Ă©tĂ© gĂ©rĂ©es normalement avec ce qui sâappelle la stratĂ©gie « rĂ©cursive » de fusion. Il existe cependant dâautres maniĂšres de fusionner des branches. Traitons en quelques unes rapidement.
Préférence our ou theirs
PremiÚrement, il existe un autre mode utile que nous pouvons utiliser avec le mode « recursive » normal de fusion.
Nous avons déjà vu les options ignore-all-space
et ignore-space-change
qui sont passées avec -X
mais nous pouvons aussi indiquer Ă Git de favoriser un cĂŽtĂ© plutĂŽt que lâautre lorsquâil rencontre un conflit.
Par défaut, quand Git rencontre un conflit entre deux branches en cours de fusion, il va ajouter des marqueurs de conflit de fusion dans le code et marquer le fichier en conflit pour vous laisser le résoudre.
Si vous prĂ©fĂ©rez que Git choisisse simplement un cĂŽtĂ© spĂ©cifique et quâil ignore lâautre cĂŽtĂ© au lieu de vous laisser fusionner manuellement le conflit, vous pouvez passer -Xours
ou -Xtheirs
Ă la commande merge
.
Si une des options est spĂ©cifiĂ©e, Git ne va pas ajouter de marqueurs de conflit. Toutes les diffĂ©rences qui peuvent ĂȘtre fusionnĂ©es seront fusionnĂ©es. Pour toutes les diffĂ©rences qui gĂ©nĂšrent un conflit, Git choisira simplement la version du cĂŽtĂ© que vous avez spĂ©cifiĂ©, y compris pour les fichiers binaires.
Si nous retournons Ă lâexemple « hello world » prĂ©cĂ©dent, nous pouvons voir que la fusion provoque des conflits.
$ git merge mundo
Fusion automatique de hello.rb
CONFLIT (contenu): Conflit de fusion dans hello.rb
La fusion automatique a échoué ; réglez les conflits et validez le résultat.
Cependant, si nous la lançons avec -Xours
ou -Xtheirs
, elle nâen provoque pas.
$ git merge -Xours mundo
Fusion automatique de hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
test.sh | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 test.sh
Dans ce dernier cas, au lieu dâobtenir des marqueurs de conflit dans le fichier avec « hello mundo » dâun cĂŽtĂ© et « hola world » de lâautre, Git choisira simplement « hola world ». Ă part cela, toutes les autres modifications qui ne gĂ©nĂšrent pas de conflit sont fusionnĂ©es sans problĂšme.
Cette option peut aussi ĂȘtre passĂ©e Ă la commande git merge-file
que nous avons utilisée plus tÎt en lançant quelque chose comme git merge-file --ours
pour les fusions de fichiers individuels.
Si vous voulez faire quelque chose similaire mais indiquer Ă Git de ne mĂȘme pas essayer de fusionner les modifications de lâautre cĂŽtĂ©, il existe une option draconienne qui sâappelle la stratĂ©gie de fusion « ours ».
Cela rĂ©alisera une fusion factice. Cela enregistrera un nouveau commit de fusion avec les deux branches comme parents, mais ne regardera mĂȘme pas la branche en cours de fusion. Cela enregistrera simplement le code exact de la branche courante comme rĂ©sultat de la fusion.
$ git merge -s ours mundo
Merge made by the 'ours' strategy.
$ git diff HEAD HEAD~
$
Vous pouvez voir quâil nây a pas de diffĂ©rence entre la branche sur laquelle nous Ă©tions prĂ©cĂ©demment et le rĂ©sultat de la fusion.
Cela peut sâavĂ©rer utile pour faire croire Ă Git quâune branche est dĂ©jĂ fusionnĂ©e quand on fusionne plus tard.
Par exemple, disons que vous avez créé une branche depuis une branche « release » et avez travaillé dessus et que vous allez vouloir réintégrer ce travail dans master
.
Dans lâintervalle, les correctifs de master
doivent ĂȘtre reportĂ©s dans la branche release
.
Vous pouvez fusionner la branche de correctif dans la branche release
et aussi faire un merge -s ours
de cette branche dans la branche master
(mĂȘme si le correctif est dĂ©jĂ prĂ©sent) de sorte que lorsque fusionnerez plus tard la branche release
, il nây aura pas de conflit dĂ» au correctif.
Subtree Merging
LâidĂ©e de la fusion de sous-arbre est que vous avez deux projets, et lâun des projets se rĂ©fĂšre Ă un sous-dossier de lâautre et vice-versa. Quand vous spĂ©cifiez une fusion de sous-arbre, Git est souvent assez malin pour se rendre compte que lâun est un sous-arbre de lâautre et fusionner comme il faut.
Nous allons explorer Ă travers un exemple comment ajouter un projet sĂ©parĂ© Ă lâintĂ©rieur dâun projet existant et ensuite fusionner le code du second dans un sous-dossier du premier.
Dâabord, nous ajouterons lâapplication Rack Ă notre projet. Nous ajouterons le projet Rack en tant que rĂ©fĂ©rence distante dans notre propre projet puis lâextrairons dans sa propre branche :
$ git remote add rack_remote https://github.com/rack/rack
$ git fetch rack_remote
warning: no common commits
remote: Counting objects: 3184, done.
remote: Compressing objects: 100% (1465/1465), done.
remote: Total 3184 (delta 1952), reused 2770 (delta 1675)
Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done.
Resolving deltas: 100% (1952/1952), done.
From https://github.com/rack/rack
* [new branch] build -> rack_remote/build
* [new branch] master -> rack_remote/master
* [new branch] rack-0.4 -> rack_remote/rack-0.4
* [new branch] rack-0.9 -> rack_remote/rack-0.9
$ git checkout -b rack_branch rack_remote/master
Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master.
Switched to a new branch "rack_branch"
Maintenant nous avons la racine du projet Rack dans notre branche rack_branch
et notre propre projet dans la branche master
.
Si vous extrayez un projet puis lâautre, vous verrez quâils ont des racines de projet diffĂ©rentes :
$ ls
AUTHORS KNOWN-ISSUES Rakefile contrib lib
COPYING README bin example test
$ git checkout master
Switched to branch "master"
$ ls
README
Câest un concept assez Ă©trange. Toutes les branches de votre dĂ©pĂŽt nâont pas vraiment besoin dâĂȘtre des branches du mĂȘme projet. Câest inhabituel, parce que câest rarement utile, mais câest assez facile dâavoir des branches qui contiennent des historiques totalement diffĂ©rents.
Dans notre cas, nous voulons tirer le projet Rack dans notre projet master
en tant que sous-dossier.
Nous pouvons faire cela dans Git avec la commande git read-tree
.
Vous en apprendrez plus sur read-tree
et ses amis dans Les tripes de Git, mais pour lâinstant sachez quâelle lit lâarborescence dâune branche dans votre index courant et dans le rĂ©pertoire de travail.
Nous venons de rebasculer dans notre branche master
, et nous tirons la branche rack_branch
dans le sous-dossier rack
de notre branche master
de notre projet principal :
$ git read-tree --prefix=rack/ -u rack_branch
Quand nous validons, câest comme si nous avions tous les fichiers Rack dans ce sous-dossier â comme si les avions copiĂ©s depuis une archive. Ce qui est intĂ©ressant est que nous pouvons assez facilement fusionner les modifications dâune des branches dans lâautre. Donc, si le projet Rack est mis Ă jour, nous pouvons tirer en amont les modifications en basculant sur cette branche et en tirant :
$ git checkout rack_branch
$ git pull
Ensuite, nous pouvons fusionner les modifications dans notre brancher master
.
Nous pouvons utiliser git merge -s subtree
et cela marchera bien, mais Git fusionnera lui aussi les historiques ensemble, ce que nous ne voudrons probablement pas.
Pour tirer les modifications et préremplir le message de validation, utilisez les options --squash
et --no-commit
en complĂ©ment de lâoption de stratĂ©gie -s subtree
 :
$ git checkout master
$ git merge --squash -s subtree --no-commit rack_branch
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested
Toutes les modifications du projet Rach sont fusionnĂ©es et prĂȘtes Ă ĂȘtre validĂ©es localement.
Vous pouvez aussi faire lâinverse â faire les modifications dans le sous-dossier rack
de votre branche master
et ensuite les fusionner plus tard dans votre branche rack_branch
pour les soumettre aux mainteneurs ou les pousser en amont.
Ceci nous donne un moyen dâavoir un flux de travail quelque peu similaire au flux de travail des sous-modules sans utiliser les sous-modules (que nous couvrirons dans Sous-modules). Nous pouvons garder dans notre dĂ©pĂŽt des branches avec dâautres projets liĂ©s et les fusionner façon sous-arbre dans notre projet occasionnellement. Câest bien par certains cĂŽtĂ©s ; par exemple tout le code est validĂ© Ă un seul endroit. Cependant, cela a dâautres dĂ©fauts comme le fait que câest un petit peu plus complexe et câest plus facile de faire des erreurs en rĂ©intĂ©grant les modifications ou en poussant accidentellement une branche dans un dĂ©pĂŽt qui nâa rien Ă voir.
Une autre chose un peu étrange est que pour obtenir la différence entre ce que vous avez dans votre sous-dossier rack
et le code dans votre branche rack_branch
â pour voir si vous avez besoin de les fusionner â vous ne pouvez pas utiliser la commande diff
classique.
Ă la place, vous devez lancer git diff-tree
avec la branche que vous voulez comparer :
$ git diff-tree -p rack_branch
Ou, pour comparer ce qui est dans votre sous-dossier rack
avec ce quâĂ©tait la branche master
sur le serveur la derniÚre fois que vous avez tiré, vous pouvez lancer
$ git diff-tree -p rack_remote/master