#GIT: Utilisation de Git en ligne de commandes {{INLINETOC}} Il n’y a rien que les outils graphiques ne fassent que l’interface en ligne de commande ne puisse faire ; la ligne de commande reste l’interface qui donne le plus de puissance et de contrôle sur les dépôts. ##Mettre à jour son dépôt local $ git pull ou $ git pull ssh://username@host/var/cache/git/myproject/ ##Ajouter des fichiers $ git add * ##Commiter les changements Pour mettre en même temps un log et commiter, le tout en une seule ligne (sinon enlever -m...) : $ git commit -a -m "Mon premier ajout" Ceci va donc mettre à jour en local le repository (ne pas oublier le push ensuite si sur le serveur distant). ##Changer l'éditeur et le visualiseur par défaut Pour forcer par exemple vim et less : $ git config --global core.editor vim $ git config --global core.pager "less -FSRX" ##Obtenir les logs Cnsulter à n'importe quel moment les logs des anciens commits via cete commande : $ git log Pour obtenir les logs d'un fichier bien précis : $git log test Si le fichier a été renommé, utiliser l'option --follow pour voir également son nouveau nom. Pour voir les logs des 2 derniers jours : $ git log --since="2 days ago" ##Faire une recherche Il est possible de faire une recherche de contenu dans les fichiers comme ceci : $ git grep deimos * ##Ignorer des fichiers Pour ne pas autoriser certains fichiers tel que les fichiers temporaire de vim par exemple. Il va falloir placer un fichier .gitignore qui sera à la racine de la copie de travail qui sera en général ajouté lui même au dépôt, soit dans le fichier .git/info/exclude. Voici un exemple : Configuration File .gitignore *~ *.o ##S'assurer qu'il n'y a rien à commiter Il est possible de s'assurer qu'il n'y a rien a commiter de cette façon : $ git stash //// No local changes to save ##Configurer des alias Il est possible de configurer des alias pour les commandes un peu longues. Par exemple : $ git config --global alias.co 'checkout' $ git config --global alias.ci 'commit -a -m' Ici 'git co' correpond à la commande 'git checkout'. Cette configuration (si elle est globale ou non) peut être située à 2 endroits : * ~/.gitconfig pour en bénéficier dans tous les dépôts. * .git/config d’un projet pour restreindre son accès à cet unique projet. ##Revert : annuler un commit Git revert permet d'annuler le précédent commit en en introduisant un nouveau. Tout d'abord faire un git log pour récupérer le numéro du commit : $ git log //// commit : bfbfefa4d9116beff394ad29e2721b1ab6118df0 Puis faire un revert pour annuler l'ancien : $ git revert bfbfefa4d9116beff394ad29e2721b1ab6118df0 En cas de conflit, il suffit de faire les modifications à la main, de refaire un add puis un commit. Note : on peut aussi utiliser les quelques premiers caractère de l'identifiant (SHA-1) du commit tel que bfbfe. Git fera automatiquement la completion (a la condition qu'un autre commit ne commence pas avec les mêmes caractères). ##Reset : effacer tous les commits à partir d'un ancien Git reset va supprimer complètement tous les commits effectués après une certaine version : $ git reset bfbfefa4d9116beff394ad29e2721b1ab6118df0 Git reset permet de réécrire l'histoire. Celà peut avoir de grave conséquences si on travaille à plusieurs sur un projet. Non seulement parce que les repository en locale des autres personnes vont devoir être resynchroniser, mais en plus parce qu'il peut y avoir beaucoup de conflits. Il est donc conseiller d'utiliser cette commande avec précaution. Ensuite il sera nécessaire de refaire un 'git add' des fichiers, puis un 'git commit'. Si on veut que la copie de travail en local reflète le dépôt, ajouter l'option --hard lors du reset : $ git reset --hard bfbfefa4d9116beff394ad29e2721b1ab6118df0 En cas d'erreur pour revenir en arrire, git a conservé l'état précédent, donc : $git reset --hard ORIG_HEAD HEAD signifie le dernier commit par défaut. En cas de message du type : ! [rejected] master -> master (non-fast forward) Pour vraiment forcer le commit : $ git push origin +master ##Restaurer un fichier d'un ancien commit Pour restaurer uniquement un fichier bien particulier d'un ancien commit via la commande checkout : $ git checkout bfbfe test ##Supprimer un fichier Il existe 2 méthodes : $ git rm mon_fichier La 2ème méthode consite à faire un rm standart, puis un git commit -a, il détectera que ce fichier n'existe plus et le supprimera du dépôt. ##Déplacement d'un fichier Pour déplacer un fichier au sain du dépôt git, il faut utiliser cette commande : $ git mv test1 test2 ##Lister le contenu du dépôt On peut utiliser cette commande pour lister le contenu d'un dépôt : $ git ls-files **Note:** les dossiers vide ne seront pas afficher ##Créer une archive du dépôt Pour faire une archive au format tar gzip, nous allons utiliser l'option archive : $ git archive --format=tar --prefix=version_1/ HEAD | gzip > ../version_1.tgz * --prefix : permet de donner un dossier qui sera créer lors de la décompression. * HEAD : permet de spécifier le commit (ici le 1er) ##Voir un fichier en particulier d'un commit Il est possible de voir un fichier (README) d'un commit : $ git show 291d2ee31feb4a318f77201dea941374aae279a5:README ou voir tous les diffs d'un coup si on ne spécifie pas le fichier : $ git show 291d2ee31feb4a318f77201dea941374aae279a5 ##Réécrire l'histoire Pour revenir en arrière, il faut faire un rebase interactif en indiquant le commit le plus loin que l'on souhaite modifier: $ git rebase -i cbde26ad^ Dans l'éditeur, modifier 'pick' en 'edit' sur la ou les lignes à modifier. Enregistrer et quittez. Faire tous les changements et ensuite valider le commit: $ git commit -a --amend ou $ git commit -a --amend --no-edit Ensuite, pour passer au commit suivant: $ git rebase --continue Jusqu'à ce que ce soit terminé. ##Faire des diff entre différentes versions Il est très facile avec git de faire des diff entre différentes versions. Par exemple, de la version HEAD à 2 versions précédentes : $ git diff HEAD~2 HEAD ##Modifier le texte du dernier commit Pour voir l'état du dernier log, faire : $ git log -n 1 Pour modifier le texte de ce dernier log, utiliser l'option amend : $ git commit -a --amend Cela permet donc de changer juste le texte, il n'y aura pas de nouveau numéro commit. Seul le numéro d'identification changera. ##Modifier la date du dernier commit Il est possible de modifier la date du dernier commit: GIT_COMMITTER_DATE="`date`" git commit --amend --date "`date`" Ou sinon on peut spécifier la date en dur: GIT_COMMITTER_DATE="Fri Nov 17 12:00:00 CET 2014" git commit --amend --date "Fri Nov 17 12:00:00 CET 2014" ##Modifier l'auteur de commits Lorsque l'on utilise plusieurs comptes Git sur une même machine, il arrive facilement de se tromper lorsque l'on clone un repository et que l'on ne met pas le bon username et adresse mail. Du coup lorsque l'on s'en rend compte, il est trop tard et il faut réécrire une partie d l'histoire pour remettre les bonnes informations: $ git filter-branch --env-filter ' //// oldname="old username" oldemail="old email address" newname="new username" newemail="old username address" [ "$GIT_AUTHOR_EMAIL"="$oldemail" ] && GIT_AUTHOR_EMAIL="$newemail" [ "$GIT_COMMITTER_EMAIL"="$oldemail" ] && GIT_COMMITTER_EMAIL="$newemail" [ "$GIT_AUTHOR_NAME"="$oldname" ] && GIT_AUTHOR_NAME="$newname" [ "$GIT_COMMITTER_NAME"="$oldname" ] && GIT_COMMITTER_NAME="$newname" ' HEAD ##Synchroniser une partie d'un dépôt Pour n'avoir qu'une partie d'un dépôt (également appelé sparse), récupérer le dépôt dans son intégralité, puis lui spécifier que ce qu'il faut garder: $ git clone ssh://deimos@git/var/cache/git/git_deimosfr . $ git config core.sparsecheckout true $ echo configs/puppet/ > .git/info/sparse-checkout $ git read-tree -m -u HEAD Ici dans le dépôt git_deimosfr, seul le dossier "configs/puppet/" est conservé. On peut ajouter plusieurs dossiers dans le fichier ".git/info/sparse-checkout" ligne par ligne pour conserver plusieurs fichiers. ##Utiliser un repository git externe dans un git Pour intégrer un autre Git dans un dépôt existant, il vous faudra utiliser les submodules. Par exemple pour faire pointer certains modules du dossier local puppet vers les git externe: Ajouter la source externe à ce git : $ git submodule add git://git.black.co.at/module-common configs/puppet/modules/common //// Cloning into configs/puppet/modules/common... remote: Counting objects: 423, done. remote: Compressing objects: 100% (298/298), done. remote: Total 423 (delta 155), reused 203 (delta 70) Receiving objects: 100% (423/423), 53.96 KiB, done. Resolving deltas: 100% (155/155), done. Commiter et push pour rendre cette configuration valable sur le serveur. Si un client qui fait un pull. Il va récupérer toute l'arborescence, mais pas les liens externes, pour cela, il devra exécuter les commandes suivantes : $ git submodule init //// Submodule 'configs/puppet/modules/common' (git://git.black.co.at/module-common) registered for path 'configs/puppet/modules/common' > git submodule update Cloning into configs/puppet/modules/common... remote: Counting objects: 423, done. remote: Compressing objects: 100% (298/298), done. remote: Total 423 (delta 155), reused 203 (delta 70) Receiving objects: 100% (423/423), 53.96 KiB, done. Resolving deltas: 100% (155/155), done. Submodule path 'configs/puppet/modules/common': checked out 'ba28f3004d402c250ef3099f95a1ae13740b009f' Si lors d'un clone, vous souhaitez que les submodules soient également prit avec, vous devez rajouter l'option "--recursive". Exemple : $ git clone --recursive git://git.deimos.fr/git/git_deimosfr ##Les branches ###Création d'une branche Si par exemple, la version actuelle que vous avez sur git vous convient et que vous souhaitez la passer en stable, il vous faut utiliser les branches et alors créer une nouvelle branche en tant que branche de développement : $ git branch devel ###Envoyer une branche au serveur Pour envoyer une branche au serveur : $ git push origin ###Lister des branches Maintenant, la branche devel est créer, pour le vérifier : $ git branch -a //// devel * master L''*' signifie la branche courante (en cours d'utilisation). Lorsque vous utilisez des branches distantes, vous pouvez les lister via cette option : $ git branch -r //// devel * master ###Changement de branche Pour changer de branche, il suffit d'utiliser l'option checkout : $ git checkout devel //// Switched to branch "devel" Vérifier : $ git branch //// * devel master Pour repasser sur la branche précédente: $ git checkout -f master $ git branch //// * master devel L'option -f correspond à l'option --hard du reset qui permet de synchroniser en local les changements effectués sur le dépôt. Pour travailler sur une branche distante : $ git checkout deimos/myproject -b master $ git branch //// * master devel ###Récupérer une branche depuis un serveur distant Une fois que votre "pull" est fait, changez de branche comme ceci (vous pouvez la nommer différemment si vous le souhaitez) : Command git $ git checkout -b origin/ - ****: le nom de ma nouvelle branche locale - ****: le nom de la nouvelle branche distante (côté serveur) ###Supprimer une branche Pour supprimer une branche, rien de plus simple : $ git branch -d devel Si celà fonctionne parfait, si vous souhaitez forcer en cas de problème, utiliser '-D' à la place de '-d'. Pour appliquer les changements sur le serveur remote : Command git $ git push origin :devel Ceci supprimera la branche devel sur le serveur distant. ###Merger des branches L'option merge consiste à fusionner 2 branches complètement. Comme par exemple faire fusionner une branche devel avec une stable pour que la devel devienne stable : $ git checkout master $ git merge devel ###Rebase : Appliquer un patch sur plusieurs branches Par exemple si bug en master a été découvert, il est normal que si le master bénéficie du fix, la branche devel également. Se placer dans la branche master, appliquer le patch, puis merger avec la branche devel le patch. Se placer sur la branche devel, puis exécuter ces commandes : $ git checkout devel $ git rebase master Il est possible de le faire de façon interactive grâce à l’argument -i : $ git rebase -i master ###Créer une branche lors d'une restauration On peut créer une branche lors d'une restauration d'un fichier en se plaçant préalablement dans la branche et en utilisant l'option -b : Command git $ git checkout bfbfefa4d9116beff394ad29e2721b1ab6118df0 -b version_2 //// Switched to a new branch "version_2" ##Les Tags Les tags sont très pratiques pour marquer une version bien spécifique. Par exemple, pour releaser une version 1.0, il est possible de créer un tag et de s'en resservir par la suite pour télécharger cette version particulière. On peut tagger ce que l'on veut, ça permet de se repérer facilement. ###Créer un Tag $ git tag '1.0' Notes :Il est possible de signer les tag via GPG ###Lister les tags Pour lister les tags disponibles : $ git tag ###Supprimer un tag Pour supprimer un tag: $ git tag -d v0.1 $ git push origin :refs/tags/v0.1 ###Envoyer les tags sur le serveur Une fois les tags créés en local, il est possible de les pousser sur le serveur: $ git push --tags ##Gérer les conflits Il est possible de gérer les conflits de façon interactive : $ git mergetool ##Résolution automatique de conflits Il est possible de demander à git de résoudre automatiquement des problèmes sur lesquels il serait déjà tombé : $ git config --global rerere.enabled 1 ##Utiliser git pour trouver un bug Git-bisect permet de découvrir exactement à quelle moment elle est regression de code, quel est le commit correspondant, pour avoir une idée très précise de sa cause. ###Méthode empririque Indiquer un commit présentant le dysfonctionnement (HEAD), et un commit passé, n’importe lequel, qui ne la présente pas. Git se place alors dans l'historique de développement, pile au milieu entre les deux commit. Après vérification, ndiquer à git si la régression apparaît ou pas. Et on recommence. Chaque fois, on divise par 2 l’intervalle des commits ayant potentiellement introduit le bug, jusqu’à débusquer le fautif. Exemple: $ git bisect start $ git bisect bad $ git bisect good //// Bisecting: 8 revisions left to test after this 8 commits ont potentiellement introduit la régression. On indique si le commit actuel est correct ou pas : $ git bisect good # ou git bisect bad //// Bisecting : 4 revisions left to test after this Et on continue, jusqu’à ce qu’on se trouve le mauvais commit à l’origine de la regression : 37b4745bf75e44638b9fe796c6dc97c1fa349e8e is first bad commit A tout moment, on peut obtenir un historique de votre parcours : $ git bisect log Si a un moment précis, on ne souhaite ne pas tester un commit en particulier, pour n’importe quelle raison, utiliser la commande : $ git bisect skip ###Utilisation d'un script Il est possible d’automatiser la recherche en utilisant des scripts: $ git bisect start HEAD -- $ git bisect run script **Note :** le script doit renvoyer 0 si le code est correct, 1 s’il est incorrect, et 125 s’il est intestable (git skip). Pendant la bisection, git créé une branche spéciale dédiée. Repasser sur la branche de développement une fois la régression repérée. Il est possible de le faire simplement en tapant : $git bisect reset ##Connaître ligne par ligne les commits Ceci est la solution de taper sur les doigts d'une personne qui a réaliser un commit. Entre autre, vous avez la possibilité de connaître ligne par ligne qui a écrit quoi : Command git $ git blame //// ^a35889c (Deimos 2010-04-20 17:36:29 +0200 1) > $logfile for server in $(seq 0 $((${#servers[@]} - 1))) ; do echo "Deploying $tag" >> $logfile ssh $ssh_username@${servers[$server]} /usr/bin/git_deploy.sh $tag $folder 2>&1 >> $logfile done echo $(date +'%Y/%m/%d - %H-%M-%S ') "Deployment hook finished for $environment env" >> $logfile } # check if a tag has been pushed if [[ -n $(echo $refname | grep -Eo "^refs/tags/$environment") ]]; then echo "Git hook is called for $environment environment" if [ ${!environment[0]} ] ; then # Deploy the correct environment deploy_new_version $environment[@] else echo "Error, $environment is not recognized as an existing environment" exit 1 fi fi Modifier les environnements avec ceux voulu pour que quand un tag arrive avec le nom du tag en question, il y ai une action qui soit déclenchée derrière. Par exemple pour déployer sur les serveurs de prod, il faudra mettre un tag de type "prod-v1.0". Switcher avec l'utilisateur git (ou celui qui a les droits) et copiez la clé publique vers tous les serveurs sur lesquelles vont devoir agir les tags: $ su - git $ ssh-copy-id www-data@x.x.x.x Créer le script de déploiement: Configuration File /usr/bin/git_deploy.sh #!/bin/bash logfile='/var/log/git-deploy-hook.log' echo $(date +'%Y/%m/%d - %H-%M: ') 'Begin deployment hook' >>$logfile cd $2 git fetch origin -v >> $logfile 2>&1 git fetch origin -v --tags >> $logfile 2>&1 git stash drop >> $logfile 2>&1 git checkout -f tags/$1 >> $logfile 2>&1 echo $(date +'%Y/%m/%d - %H-%M: ') 'Deployment finished' >> $logfile Pour rajouter toutes les commandes Il faut copier ce script sur tous les serveurs cible et lui donner les droits d'exécution. Créer le fichier de logs: $ touch /var/log/git-deploy-hook.log $ chow www-data. /var/log/git-deploy-hook.log $ chmod 755 /usr/bin/git_deploy.sh Cloner les repository avec les utilisateurs finaux: $ cd /var/www $ git clone git@gitlab/cloned_repository.git $ chown -Rf www-data. /var/www/cloned_repository Pusher un tag sur un commit: $ git tag -a prod-v1.0 -m 'production version 1.0' 9fceb02 $ git push --tags