- les commits sont organisés en DAG (Direct Acyclic Graph), voir cette page.
- une branche n'est qu'un pointeur sur un commit, voir cette page.
-
Il existe un pointeur particulier,
HEAD
, qui pointe sur le commit correspondant à l'état courant du répertoire de travail. - Un bon article sur les branches
Pour la suite, on se place dans le dépôt
repo1/
créé dans la page Premiers pas.
Principales commandes pour manipuler les branches :
Commande | Signification |
---|---|
git branch |
List branches |
git branch testing |
Creates a new branch (doesn't switch to that branch) |
git checkout testing |
Switches to branch testing (the HEAD pointer goes to testing) |
git checkout -b testing |
Create a branch and switch to it |
La branche courante
Sous git, on est toujours sur une branche. Quand on initialise un dépôt, git crée une branche par défaut, appelée main (avant 2020, la branche par défaut s'appellait master).git branch
permet de lister les branches du dépôt courant :
git branch
* mainGit indique qu'il n'y a que la branche
main
; l'astérisque indique que c'est la branche sur laquelle on se trouve.
Créer une branche et faire un commit
On se place dans le dépôtrepo1/
créé dans la page Premiers pas.
# créer une branche nommée "branche1" git branch branche1 git branch
branche1 * main
# changer de branche git checkout branche1Les 2 étapes précédentes auraient pu être faites en une instruction :
git checkout -b branch1Dans les deux cas, on peut vérifier qu'on se trouve sur
branch1
git branch
* branche1 mainDans cette branche, on crée un nouveau fichier
fichier-branche1.txt
, on l'ajoute à git et on commit :
echo "Fichier créé dans branche1" > fichier-branche1.txt git add -A git commit -m "Création de fichier-branche1.txt"
git slog
donne des indications intéressantes :
git slog
* 052f4d7 - (2019-01-20 11:33:43 +0100) Création de fichier-branche1.txt - Thierry (HEAD -> branch1) | * 8c0ae1e - (2019-01-20 03:14:20 +0100) Ajout d'une troisième ligne dans test1.txt - Thierry (main) | * 20dae34 - (2019-01-20 03:14:20 +0100) Ajout d'une seconde ligne dans test1.txt - Thierry | * d3a94de - (2019-01-20 03:14:20 +0100) Création de test1.txt - ThierryLe pointeur
HEAD
pointe bien vers l'état courant du répertoire de travail.
Le pointeur
main
pointe vers le dernier commit fait sur la branche main
.
Le pointeur
branch1
pointe vers le dernier commit fait sur la branche branch1
Retour sur main
On retourne sur la branche main :git checkout mainOn peut s'apercevoir de deux choses :
- Le fichier
fichier-branche1.txt
a disparu : git a remis le répertoire de travail dans l'état correspondant àmain
. git slog
montre que le pointeurHEAD
se trouve sur le commit correspondant àmain
.
Stashing
Pour passer d'une branche à l'autre, il faut avoir son répertoire de travail propre, ne pas avoir de fichiers dans l'étatmodified
.
Pour changer de branche en laissant en l'état son travail courant, on utilise git stash
.
Très bien expliqué : https://git-scm.com/book/en/v2/Git-Tools-Stashing-and-Cleaning
git stash list # stash git stash git stash -u # --include-untracked git stash --keep-index # include staged content and leave it in the index. git stash -a # --all (include ignored files) git stash --patch # interactive - ask which files to stash git stash branch new_branch # create a branch, checks out the commit you were on when you stashed, # reapplies your work there, and then drops the stash if apply = ok # unstash git stash apply git stash apply stash@{2} # if several stashes - see git stash list git stash apply --index # reapply also the staged changes git stash drop stash@{0} git stash pop # apply the stash and then immediately drop it
Branch merge
Fast-forward merge
On va merger la branchebranch1
dans main
.
On est dans un cas particulièrement simple car on n'a effectué aucune modification dans
main
.
main
pointe donc sur un ancêtre direct de branch1
.
Git détecte cette situation et peut merger les deux branches simplement en déplaçant le pointeur
main
.
Ce type de merge s'appelle fast-forward.
git merge branch1
Mise à jour 8c0ae1e..052f4d7 Fast-forward fichier-branche1.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fichier-branche1.txtEn listant les fichiers, on peut voir que le fichier fichier-branche1.txt est présent ; on peut voir en faisant slog que le pointeur
main
a été modifié.
Three-way merge
On va créer une situation plus compliquée :- Faire un commit sur
main
- Aller sur
branch1
- Faire un commit sur branch1
A chaque étape, on peut vérifier comment les choses évoluent avec
git status
, git slog
, et en listant les fichiers du répertoire.
Sur la branche
main
:
echo "Fichier créé dans main" > fichier-main.txt git add -A git commit -m "Création de fichier-main.txt"On va sur la branche
branch1
et on modifie fichier-branche1.txt
:
git checkout branch1 echo "Une deuxième ligne ajoutée dans branch1" >> fichier-branche1.txt git commit -am "Ajout d'une 2ème ligne dans fichier-branche1.txt"
git slog
est intéressant :
git slog * 43bf826 - (2019-01-20 12:47:59 +0100) Ajout d'une 2ème ligne dans fichier-branche1.txt - Thierry (HEAD -> branch1) | | * 549446f - (2019-01-20 12:40:16 +0100) Création de fichier-main.txt - Thierry (main) |/ | * 052f4d7 - (2019-01-20 11:33:43 +0100) Création de fichier-branche1.txt - ThierryLes 2 branches ont divergé. L'ancêtre commun est le commit
052f4d7
.
Mais un même fichier n'a pas été modifié dans les 2 branches, donc git peut faire le merge automatiquement.
On est toujours sur
branch1
, on fait le merge :
git merge main Merge made by the 'recursive' strategy. fichier-main.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fichier-main.txtOn voit que git n'a pas fait un fast-forward merge mais a utilisé la stratégie 'recursive'.
Ce genre de merge donne lieu à un commit, et git ouvre un éditeur pour demander le message du commit, avec une valeur par défaut proposée.
L'éditeur utilisé correspond à celui spécifié par
core.editor
dans la config.
Comme indiqué dans le texte, seules les lignes ne commençant pas par
#
sont conservées dans le message du merge.
En enregistrant ce texte éventuellement modifié et en quittant l'éditeur, git fait le commit de merge, ce que l'on peut voir par
git slog
:
git slog * 723fe55 - (2019-01-20 17:08:21 +0100) Merge branch 'main' into branch1 - Thierry (HEAD -> branch1) |\ | | | * 549446f - (2019-01-20 12:40:16 +0100) Création de fichier-main.txt - Thierry (main) | | * | 43bf826 - (2019-01-20 12:47:59 +0100) Ajout d'une 2ème ligne dans fichier-branche1.txt - Thierry |/ | * 052f4d7 - (2019-01-20 11:33:43 +0100) Création de fichier-branche1.txt - Thierry
Merge avec conflit sur un fichier
On va maintenant modifier le même fichier dans deux branches différentes et merger.Sur
branch1
, modifier le fichier test1.txt
de manière à avoir :
Une première ligne modifiée dans branch1 Une seconde ligne Du contenu ajouté dans branch1 Une troisième ligneFaire le commit dans
branch1
et basculer sur main
git commit -am "Modification de test1.txt dans branch1" git checkout mainSur
main
, éditer le fichier test1.txt
. On voit que les modifications faites sur branch1
ne sont pas présentes.
Modifier ce fichier de manière à avoir :
Une première ligne modifiée dans main Une seconde ligne Du contenu ajouté dans main Une troisième ligneFaire le commit :
git commit -am "Modification de test1.txt dans main"On peut maintenant faire le merge avec conflit
git merge branch1
Fusion automatique de test1.txt CONFLIT (contenu) : Conflit de fusion dans test1.txt La fusion automatique a échoué ; réglez les conflits et validez le résultat.La phrase important du message est "réglez les conflits et validez le résultat".
git status
donne plus d'information :
git st Sur la branche main Vous avez des chemins non fusionnés. (réglez les conflits puis lancez "git commit") (utilisez "git merge --abort" pour annuler la fusion) Modifications qui seront validées : modifié : fichier-branche1.txt Chemins non fusionnés : (utilisez "git add..." pour marquer comme résolu) modifié des deux côtés : test1.txt
git merge --abort
permet de revenir à l'état d'avant la tentative de merge.
Pour régler le merge, il faut éditer le fichier conflictuel
test1.txt
Il contient :
<<<<<<< HEAD Une première ligne modifiée dans main Une seconde ligne Du contenu ajouté dans main ======= Une première ligne modifiée dans branch1 Une seconde ligne Du contenu ajouté dans branch1 >>>>>>> branch1 Une troisième ligneGit a donc modifié ce fichier et identifié les conflits.
Chaque conflit à régler (un seul conflit dans notre cas) est encadré par les lignes commençant par
<<<<<<<
et >>>>>>>
.
Git a mis les 2 versions en conflit à la suite, en les séparant par
=======
.
Les parties non conflictuelles du fichier sont recopiées telles quelles (
Une troisième ligne
dans notre cas).
Donc git nous dit :
-
Dans la branche courante (
HEAD
), je vois :Une première ligne modifiée dans main Une seconde ligne Du contenu ajouté dans main
Dans la branche à merger (branch1
), je vois :Une première ligne modifiée dans branch1 Une seconde ligne Du contenu ajouté dans branch1
La on peut par exemple faire :
Une première ligne modifiée dans main et dans branch1 Une seconde ligne Du contenu ajouté dans main Du contenu ajouté dans branch1 Une troisième ligne(noter qu'on a supprimé les lignes de marquage ajoutées par git).
On peut maintenant terminer la résolution du conflit comme un commit normal :
git commit -am "Résolution manuelle du conflit"
git slog
montre que le conflit est résolu :
* 4494d4b - (2019-01-20 18:00:30 +0100) Résolution manuelle du conflit - Thierry (HEAD -> main) |\ | | | * 8b04dc1 - (2019-01-20 17:24:35 +0100) Modification de test1.txt dans branch1 - Thierry (branch1) | | | * 99c8465 - (2019-01-20 17:14:52 +0100) Merge branch 'main' into branch1 - Thierry | |\ | | | | * | 43bf826 - (2019-01-20 12:47:59 +0100) Ajout d'une 2ème ligne dans fichier-branche1.txt - Thierry | | | * | | 66abec5 - (2019-01-20 17:29:21 +0100) Modification de test1.txt dans main - Thierry | |/ |/| | | * | 549446f - (2019-01-20 12:40:16 +0100) Création de fichier-main.txt - Thierry |/ | * 052f4d7 - (2019-01-20 11:33:43 +0100) Création de fichier-branche1.txt - Thierry
La suite est incomplète
Branches distantes
Par exemple, si on a l'autorisation de pusher sur le dépôtorigin
:
git push origin content-git
Renommer une branche distante
git push <REMOTENAME> <LOCALBRANCHNAME>:<REMOTEBRANCHNAME>
Effacer une branche distante
ATTENTION : similaire à la syntaxe pour renommer une branche distante mais sans<LOCALBRANCHNAME>
git push <REMOTENAME> :<BRANCHNAME>A comprendre comme : push rien du tout dans
<BRANCHNAME>
Par exemple
git push origin :content-gitTous les commits effectués dans
content-git
seront supprimés sur le dépôt distant, qui se retrouve dans le même état qu'avant avoir pushé la content-git
sur origin
.
(Source : https://help.github.com/articles/pushing-to-a-remote/)
Lier une branche distante par défaut à une branche locale
On se place sur la branchecontent-git
, on fait des modifications et on commit.
Si on veut pusher en faisant simplement :
git pushgit nous dit :
fatal: La branche courante content-git n'a pas de branche amont. Pour pousser la branche courante et définir la distante comme amont, utilisez git push --set-upstream origin content-gitOK, on fait :
git push --set-upstream origin content-gitgit affiche plusieurs lignes qui finissent par :
La branche 'content-git' est paramétrée pour suivre la branche distante 'content-git' depuis 'origin'.La prochaine fois qu'on fait
git pushdepuis la branche locale
content-git
, git pushera sur la branche distante origin content-git
De la même manière, si on retourne sur la branche
main
et qu'on fait
git push --set-upstream origin mainalors git pushera sur la branche distante
origin main
quand on est sur la branche locale main
.