Git - noelno/dovelei GitHub Wiki

  • Historique des modifications
  • Fusion des modifications faites simultanément
  • Branches : développer sans risque de tout casser en production, avec possibilité de laisser tomber la branche à tout moment.

Git est distribué (décentralisé) : l'historique des modifications est d'abord local (commits) puis centralisé - alors que sur Mercurial par exemple tout est centralisé.

À chaque modification il y a une requête serveur, donc c'est plus long.

Installation

Windows

Télécharger l'installateur.

L'option "Windows Explorer Integration" permet de lancer des commandes git à partir de l'explorateur de fichiers, dans le menu contextuel du clic-droit.

On peut choisir dans quel environnement fonctionnera la commande git : seulement dans Git Bash, dans Git Bash et CMD/Powershell, ou dans Git Bash et CMD/Powershell, en ajoutant et en surchargeant certaines commandes de CMD/Powershell avec des commandes Bash utiles.

Les sauts de lignes Windows (LF) sont différents des sauts de lignes Unix (CRLF). L'option recommandée est de convertir ces sauts de lignes en CRLF au moment du commit.

Si après installation la commande git --help retourne une erreur, il faudra ajouter manuellement au PATH système l'adresse vers le sous-répertoire bin dans le dossier d'installation de Git (par défaut dans Program Files (x86)), puis fermer le terminal CMD/Powershell et le relancer.

Linux

Saisir la commande apt-get install git dans le terminal, puis git --help pour vérifier que tout fonctionne (il n'est pas nécessaire de redémarrer le terminal)

MacOS

Télécharger l'installateur. Contrairement à la version Windows aucune option d'installation n'est proposée.
Après installation, ouvrir un terminal et faire git --help pour vérifier que tout fonctionne.

Premiers pas

git init | initialise le versioning git dans le répertoire courant. Crée un dossier .git dans le répertoire courant qui contiendra toute la configuration et l'historique git.
git help config | affiche la liste des options de configuration
git config --global user.email "[email protected]" | email identifiant (obligatoire)
git config --global user.name "noelno" | nom identifiant (obligatoire)
git status | voir le statut du repo (fichiers trackés, status du commit…)
git add monfichier.md | ajoute le fichier monfichier.md à la liste des fichiers à versionner

Stage : zone intermédiaire avant commit.
Quand un fichier est crée, modifié ou supprimé, pour que l'existence, le changement ou la non-existence de ce fichier soit pris en compte et apparaîsse dans les versions, il faut les "stager". On peut donc faire des modifications sur plusieurs fichiers mais n'en committer que certains.

git add *.html | "stage" tous les fichiers html non stagés
git add . | "stage" tous les fichiers du répertoire. Avant de commit, penser à faire un git status pour vérifier si des fichiers ou répertoires n'ont pas été stagés par erreur.
git add --all | "stage" tous les fichiers du répertoire, en incluant les fichiers supprimés.

git commit -m "Mon premier commit" | crée un commit des fichiers "stagés" ajoutés, modifiés ou supprimés depuis le dernier commit.
On peut aussi écrire son message dans l'éditeur Vim en faisant simplement git commit
git commit -a -m "Mon commit" | équivalent de git add --all + git commit -m

Si des fichiers ou dossiers que l'on ne souhaite pas tracker apparaîssent dans le projet (par exemple des fichiers de configuration Windows/Mac), il est possible de les lister dans un fichier texte que l'on devra nommer .gitignore, à la racine du repo.

*.tmp
.DS_store
temp/

Ces fichiers n'apparaitront plus dans git status.

git log | Affiche la liste des commits
git log --oneline | Affiche la liste des commits en abrégé (un commit par ligne)
git log -p *.md | Affiche la liste des commits concernant des fichiers .md
git log -n 3 -p *.md | Affiche la liste des trois derniers commits concernant des fichiers .md
git diff | Affiche le différentiel des modifications depuis la dernière version stagée / committé. Si le fichier dans son état actuel a déjà été stagé, git diff ne fait rien.

Retour en arrière

git checkout <numero_de_commit> | retourne en arrière vers le commit séléctionné, en lecture seule.
Les fichiers seront consultables dans leur ancien état, mais les modifications ne pouront être conservés même en les commitant (ces commits seront bazardés)
git checkout master | pour revenir dans le présent
git checkout <numero_de_commit> index.html | restaure le fichier à l'état du commit. Le fichier sera automatiquement "stagé".
git revert <numero_de_commit> | défait un commit, puis commite cette modification. Contrairement à checkout, on ne défait pas tous les commits jusqu'à celui séléctionné, on défait uniquement celui séléctionné en conservant les modifications faites dans les commits suivants.

On peut revert un git revert en faisant git revert <numero_de_commit_du_revert>

git reset HEAD index.html ou git reset -- index.html | "unstage" le fichier (le retire du HEAD courant).
git reset HEAD --mixed ou git reset HEAD ou git reset | vide le HEAD courant de tous les fichiers stagés.
git reset --hard | annule toutes les modifications faites depuis le dernier commit. Attention on ne peut pas annuler un reset--hard, toutes les modifications seront définitivement perdues !
git reset <numero_de_commit> | supprime de l'historique tous les commits plus récents que le commit cible, sans impacter les fichiers du répertoire. On peut ensuite re-commiter toutes les modifications faites depuis le commit cible en un seul nouveau commit
git reset HEAD^ --soft | supprime de l'historique le dernier commit, sans impacter les fichiers du répertoire et en les stagant automatiquement
git reset HEAD^^^^ --soft | supprime de l'historique les quatre derniers commits, sans impacter les fichiers du répertoire et en les stagant automatiquement
git reset HEAD^ --mixed | supprime de l'historique le dernier commit, sans impacter les fichiers du répertoire et sans les stager.

Branches

git branch <nom_de_la_branche> | crée une nouvelle branche (sans basculer automatiquement dessus) partant de la branche courante et du commit courant.
On ne peut pas changer de branche tant que l'on a pas commité ou stashé les modifications courantes.
git checkout <nom_de_la_branche> | bascule sur la nouvelle branche.
On pourra alors commiter sur la nouvelle branche sans affecter la branche master.
git merge <nom_de_la_branche_cible> | rapatrie les modifications faites sur une branche cible vers la branche courante.

Si aucune modification n'a eu lieu sur la branche courante depuis la création de la branche cible, il y aura un fast-forward : tous les commits de la branche cible seront déplacés sur la branche courante, et la branche cible sera vidée (mais toujours existante).
On n'aura alors plus aucune trace du fait que ces modifications aient été faites sur la branche cible et non sur master à l'origine.

En revanche s'il y a eu des modifications sur la branche courante depuis, la branche cible rejoindra la branche courante dans un nouveau commit, sans fast-forward, c'est à dire sans déplacer ses commits. Il est aussi possible d'empêcher le fast-forward grâce au drapeau --no-ff (git merge --no-ff master).

Pour info, faire un git reset n'impacte que la branche courante. On peut donc créer une nouvelle branche et revenir en arrière sur la branche master sans que cela n'impacte la nouvelle branche.

Quand il n'y a pas de fast-forward, les branches se rejoignent dans un commit de fusion qui peut être :

  • automatique (auto-merging) si les modifications dans la branche A portait sur des fichiers ou des blocs de codes différents de ceux modifiés dans la branche B.
  • manuel (fix conflict) si l'auto-merging échoue (les modifications portaient sur à peu près les mêmes zones de code).

Quand l'auto-merging échoue, un message indiquant qu'il y a conflit apparaît. Git crée quand même un fichier fusionné mais qui contient les deux versions du code conflictuel, encadrés par des commentaires. C'est au développeur de modifier ce fichier afin de supprimer les commentaires git et de garder les parties de chaque code qui l'intéresse.

git status permettra de voir qu'une fusion est en attente. Une fois le conflit corrigé, on pourra faire un nouveau commit pour finaliser le merging.

Une branche sans fast-forward peut être supprimée. Git conservera son historique séparément de la branche d'accueil, mais on ne pourra plus faire de checkout sur cette branche.

git branch -d <nom_de_la_branche> | supprime la branche (si aucune modification en attente de commit sur cette branche)
git branch | affiche la liste des branches

Manipulation de l'historique

La manipulation de l'historique ne devrait être fait qu'en local (avant publication) pour éviter de semer la confusion auprès des autres utilisateurs du dépôt.

git commit --amend | envoie les modifications du commit courant vers le commit précédent.
git rebase master | déplace la base de la racine de la branche courante vers le dernier commit de la branche master. Le but est de forcer un fast-forward : on a vu plus tôt que le fast-forward (rapatriement de tous les commits d'une branche A vers une branche B) n'avait lieu que si la branche B n'avait pas eu de nouveau commit depuis la création de la branche A.
rebase permet donc, dans le cas où il y aurait eu de nouveaux commits sur la branche B, de les placer avant tous les commits de la branche A dans l'historique.
git rebase -i master | permet d'éditer textuellement l'historique afin de sélectionner quels commits garder tels quels (pick), quels commits renommer (reword), quel commit fusionner avec le commit qui le précède (squash et fixup) ou fractionner (edit)
git rebase -i HEAD^^^^ ou git rebase -i HEAD~4 | permet d'éditer textuellement l'historique des quatre derniers commits

Remisage

git stash | revient en arrière jusqu'au dernier commit, mais garde en mémoire les modifications faites depuis dans les fichiers trackés (on dit qu'elles sont remisées).
git stash -u | idem que git stash, sauf qu'il ajoute avant les fichiers non trackés au HEAD. Sans cette option -u, les nouveaux fichiers non stagés ne seraient pas ajoutés au stash.
Utile lorsque l'on a fait plusieurs modifications mais que l'on ne veut pas les publier tout de suite, et que l'on a ensuite une autre modification à publier en urgence.
git stash save ma description de stash | idem que git stash, mais attribue une description de stash. Notons l'absence de quotes pour encadrer la description.
git stash list | affiche la liste des modifications gardées en mémoire (une ligne par appel de git stash) git stash apply | récupère les modifications remisées et les merge aux fichiers locaux.
git stash drop | supprime les dernières modifications remisées.
git stash show stash@{0} -p | affiche le différentiel entre le stash 0 et le répertoire courant. git stash pop stash@{0} | combinaison de git stash apply et git stash drop.
Pas forcément recommandé, car le apply donne des indications qui peuvent être importantes à connaître avant de droper.
git stash branch <nom_de_la_branche> | crée une nouvelle branche, récupère le stash dedans et le drop.

Remote

git init --bare | initialise un repo vide
git remote -v | affiche la liste des différents remotes
git remote add <nom_du_dépôt_distant> <chemin_du_depot_distant> | ajoute un nouveau dépôt distant au dépôt courant. Par convention on nommera ce dépôt origin.
git remote rename <nom_actuel> <nouveau_nom> | renomme le dépôt distant
git remote remove <nom_du_dépôt_distant> | supprime le dépôt distant
git push <nom_du_dépôt_distant> <branche_du_dépôt_courant> | copie une branche précise du dépôt local vers le dépôt distant
git branch -r | affiche la liste de branches disponibles sur le dépôt distant
git push <nom_du_dépôt_distant> --delete <branche_du_dépôt_distant> | supprime la branche du dépôt distant (la supprimer du dépôt courant ne la supprime pas automatiquement à distance)
git pull <nom_du_dépôt_distant> <branche_du_dépôt_courant> | copie une branche précise du dépôt distant sur le dépôt local
git clone <chemin_du_depot_distant> <nom_du_nouveau_répertoire> | copie un dépôt complet dans un nouveau répertoire, historique des commits inclus
git clone <chemin_du_depot_distant> <nom_du_nouveau_répertoire> --depth 2 | le flag --depth permet de spécifier le nombre de commits à récupérer. Ici on récupèrerait les deux derniers.
Si le dépôt distant a changé entre le dernier pull local et le push courant, il faudra refaire un pull avant de pusher. Ce pull n'écrasera pas les dernières modifications mais les mergera automatiquement.
git pull --rebase origin master | contrairement à un git pull classique, les commits locaux seront placés après ceux du distant dans l'historique des commits, ce qui provoquera un fast-forward au moment du pull et pas un auto-merging.
Il est possible de configurer tous les pull en rebase grâce à l'option de configuration git config --global branch.autosetuprebase always.

Github & Bitbucket

Clés SSH

Pour éviter d'avoir à donner son mot de passe à chaque pull et push sur Github et Bitbucket, il est possible de générer une clé SSH sur son ordinateur, et de la spécifier dans les options du compte Github ou Bitbucket en ligne.

Les clés SSH sont généralement stockées à la racine du dossier de l'utilisateur (home sur Mac et Linux). Elles sont accessibles via le chemin d'accès ~/.ssh sur Mac, Linux ou Git Bash for Windows, ou dans C:\Users\<nom_utilisateur>\.ssh sur Windows.

Après s'être placé en ligne de commande dans le dossier .ssh (cd ~/.ssh) générer une nouvelle clé dans le dossier courant : ssh-keygen -t rsa -C "<[email protected]>".

S'il est prévu d'utiliser plusieurs comptes sur le même poste, ne pas garder le nom proposé par défaut (id_rsa), lui donner un nom plus explicite comme github_rsa_monnomutilisateur. Choisir et mémoriser une passphrase différente par clé. Générer une clé par compte.

Une fois que toutes les clés sont générées, créer un nouveau fichier config dans le répertoire .ssh, et décrire la configuration pour chaque compte :

# GITHUB Home @monnomutilisateur
Host monnomutilisateur.github.com
   HostName github.com
   PreferredAuthentications publickey
   IdentityFile ~/.ssh/github_rsa_monnomutilisateur

# BITBUCKET Home @monnomutilisateur
Host monnomutilisateur.bitbucket.org
   HostName bitbucket.org
   PreferredAuthentications publickey
   IdentityFile ~/.ssh/bitbucket_rsa_monnomutilisateur

# GITLAB Company @monnomutilisateurpro
Host gitlab.monentreprise.com
   HostName gitlab.monentreprise.com
   PreferredAuthentications publickey
   IdentityFile ~/.ssh/gitlab_rsa_monnomutilisateurpro

ssh-add -D vide le cache des clés mémorisées.
Sur Windows il faudra peut-être lancer manuellement ssh-agent avec la commande

eval `ssh-agent -s`   

Vérifier les clés existantes avec ssh-add -l.
Rajouter les clés nouvellement créées avec ssh-add ~/.ssh/nom_de_la_cle (sans l'extension .pub). La passphrase de chaque clé sera demandée.

Tester le bon fonctionnement avec ssh -T [email protected].

id_rsa : clé secrète personnelle, à ne jamais communiquer.

Issues

Système de tickets disponibles sur Github et Bitbucket. Pour fermer automatiquement un ticket avec un fix, le commit doit avoir un message au format suivant : "fixes #<numero_de_lissue>, mon message"

Fork et Pull Request

On ne peut pas éditer le projet d'un autre, par contre on peut le forker, c'est à dire en faire une copie distante que l'on pourra modifier.

Ensuite il est conseillé de créer une nouvelle branche sur ce dépôt, d'y faire ses modifications, et une fois qu'on a terminé, de faire une "pull request", c'est à dire d'accepter les modifications faites sur la branche directement sur son dépôt d'origine.

Pour faire une pull request il faut passer par l'interface graphique du projet forké sur Github, il devrait y avoir un bouton "pull request".

Une fois la pull request ouverte, un fil de conversation s'ouvre.

Ce fil permet de discuter de la pull request avec le propriétaire du dépôt d'origine, et de suivre l'historique des modifications faites après l'ouverture de la pull request.

Il reste possible de faire des commits sur le fork, et ces commits apparaitront dans la conversation.

Si le dépôt d'origine est modifié entre temps, il est possible de récupérer les modifications dans le fork sans avoir à le recréer :

  • en créeant un nouveau remote pointant vers le dépôt d'origine et que l'on nommera "upstream" par convention (git remote upstream master <adresse_du_depot_distant>)
  • en faisant un git fetch de ce remote (donc git fetch upstream) pour récupérer la branche remotes/upstream/master dans la liste des branches disponibles
  • en faisant un git merge de ce remote avec la branche master.

Hooks

Github et Bitbucket proposent tous deux un système de hook qui permet d'automatiser certaines actions quand le code change.

⚠️ **GitHub.com Fallback** ⚠️