On Github tdd / devoxx-git-protips
Christophe Porteneuve @porteneuve Delicious Insights
const christophe = { age: 38.459958932238195, family: { wife: 'Élodie', son: 'Maxence' }, city: 'Paris, FR', company: 'Delicious Insights', trainings: ['Git Total', 'JS Total', 'Node.js'], gitSince: '2008-03-28', claimsToFame: [ 'Git Attitude', 'Learning GitHub (O’Reilly)', 'Mastering GitHub (O’Reilly)' ] }
Qui parmi vous… (main levée)
…n’a jamais fait de Git ?
…en ligne de commande ?
…ne fait pas de Git au boulot / en prod ?
…a surtout fait du Subversion ?
…n’a jamais fait de gestion de sources ?
…n’a pas Git installé sur sa machine ici ?
…n’a jamais fait de rebase ?
L’éternelle histoire du changement de paradigme…
Ou : trimballer sa culture existante dans son nouvel environnement.
Erreur tragique mais ô combien courante
Assurez-vous d’avoir une 2.0 minimum…
…ou en tout cas, vraiment, rien en-dessous de 1.8.5 !
La configuration de Git : globale vs. locale
Globale : ~/.gitconfig, ou ~/.config/git/config
Locale : ~/.git/config
git config [--global] [--replace-all|--unset-all] name [value] git config [--global] --list
Ah, et juste un mot sur les GUI…
Votre meilleur ami. Affiche plein de trucs utiles.
Réflexe absolu avant un commit, suite à un conflit…
git config --global alias.st status git config --global status.showUntrackedFiles all git config --global color.ui auto git st
L’outil fondamental, mais grave sous-utilisé.
Intégré à de nombreux autres, options réutilisables (log, show…)
git diff git config --global diff.mnemonicPrefix true git config --global diff.renames true git diff -w git diff --word-diff git config --global diff.wordRegex . git diff --staged git add -N path git show :0:path git diff --stat git diff --dirstat[=n]
Allez, on y va on se lâche.
Z’avez remarqué comme c’est pas pratique pour le copier-coller ? Comme on voit mal les limites de fichier ?
Custom diffing to the rescue!
npm install -g diff-so-fancy # ou, sur OSX avec Homebrew mais sans npm : brew update + brew install diff-so-fancy git config --global color.diff always git config --global color.diff.meta yellow bold git config --global color.diff.frag magenta bold git config --global color.diff.whitespace red reverse git config --global pager.diff "diff-so-fancy | less --tabs=2 -RFX" git config --global pager.show "diff-so-fancy | less --tabs=2 -RFX"
Y’a tellement de motifs à ignorer sur un projet…
Rien qu’avec Visual Studio, JetBrains ou Rails, ça envoie du lourd…
Gitignore.io est juste trop cool.
…et on cale son .gitignore avec (idéalement dès le 1er commit)
# … git add .gitignore git commit -m 'Initial commit'
git add rules.log The following paths are ignored by one of your .gitignore files: rules.log Use -f if you really want to add them. git add -f rules.log
Rappel critique : le stage / l’index.
On unstage avec un reset :
git reset path…
Le cas hyper fréquent du staging partiel de fichier :
git add -p path…
Mais en vrai, soyons clairs :
Commit Often, Push Sparinglygit config --global alias.ci commit git ci -m "message" git show [--stat|--dirstat|-w] [ref]
L’inattention, tout ça…
git rm --cached path git reset HEAD~1 path git ci --amend [-m|--no-edit] git config --global alias.oops 'commit --amend --no-edit'
LOG_FORMAT='%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%an %ad)%Creset' git config --global alias.lg "log --graph --date=relative --pretty=tformat:'$LOG_FORMAT'" git config --global log.abbrevCommit true git config --global log.follow true
Le log remonte le temps à partir d’un ou plusieurs points.
Le plus récent d’abord (chronologique inverse).
git lg [ref…] [^neg-ref…] git lg ref.. git lg [--branches[=glob]|--tags[=glob]|--all] [--exclude=glob] git lg [--right-only|--left-only] ref1...ref2 git lg -n
git lg --grep="message regex" [--invert-grep] [--all-match] [-i] git lg --author="author regex" [-i] git config --global grep.extendedRegexp true git lg --[no-]merges
git lg … [--] path… git lg -S "texte en diff actif" [--pickaxe-regex] # Mode pickaxe git lg -G "regex en diff actif" git lg -L ':regex:path' git lg -L 'startline,endline:path' git lg --diff-filter=pattern # Combo de A, C, D, M, R, T…
…et toutes ses options habituelles sont dispo.
git lg -p [-w] [--word-diff]… # Diff classique git lg [--stat|--dirstat|--numstat] # etc.
Qu’est-ce qu’un rebase ?
À quoi sert le rebase interactif ?
Aparté : quels autres types de rebase ?
git rebase -i [latest-ok=@{u}] git rebase -i --root
Quelles actions possibles ?
Verbe Intention Verbe Intention pick commit tel quel (cherry pick) reword ajustement message squash fusion code/message avec le commit précédent fixup fusion du code seul avec le commit précédent drop* commit ignoré edit ajustement manuelRéordonner les lignes réordonne les commits. Et comme toujours, script vide (hors commentaires) = abandon de commande.
Git nous donne la main lors d’un conflit :
git st # résolution fichier par fichier, avec à chaque fois : git add path # et au final : git rebase --continue # ou si ça donne un arbitrage vide par rapport à la cible : git rebase --skip
Git nous donne aussi la main sur un edit. On fait ce qu’on veut, et au final, pareil que pour un conflit.
Si on s’empêtre et qu’on veut juste lâcher l’affaire :
git rebase --abort
curl -O http://tdd.github.io/devoxx-git-protips/playground.zip unzip -q playground.zip cd devoxx-git-protips-playground git lg --all
On va se faire la plupart des besoins :
git commit --fixup=ref git commit --squash=ref git rebase -i --autosquash
Quels pièges ?
git config --global push.default upstream git config --global push.followTags true git push -u remote ref… git branch set-upstream-to=remote/ref [branch]
fetch vs. pull
Quels pièges ?
git config --global pull.rebase preserve git config --global fetch.prune false git remote prune remote
Workflows et impact sur la dynamique de synchro.
« Sync » ?
Il suffit de suivre la méthodologie qui va bien.
Elle est dans git help merge, je voudrais pas dire, hein…
En amont :
git config --global core.whitespace '-trailing' git config --global merge.conflictStyle diff3 git config --global merge.log true # ou un nombre de commits, par défaut 25 git config --global merge.ff false
Puis dans les grandes lignes, ça donne :
git st # Quel est l’étendue des dommages ? git mergetool path # Si on a+veut un, pour le fichier concerné git add path # Fichier par fichier, après l’avoir arbitré git commit [--no-edit] # Une fois que tout est staged
Un conflit, ça se traite fichier par fichier.
# Qui est le prochain coupable ? git st # On édite le fichier en direct, ou si on a un bon outil dédié configuré… git mergetool path # Une fois que les conflits sont réglés, on le dit : git add path # Si on a mal arbitré (notammment mergetool) ou que le style de marqueurs nous gêne : git checkout --conflict=(merge|diff3) path # Besoin de voir un des 3 snapshots sans mergetool ? git show (ref|MERGE_HEAD|HEAD|:1):path # Envie de récupérer le fichier tel quel depuis une des branches, ou ailleurs ? git checkout ref [--] path
REuse REcorded REsolution. OoOoOOOoh, ça vend du rêve.
git config --global rerere.enabled true git config --global rerere.autoupdate true # seulement pour les gens attentifs ! git rerere remaining
En cas de besoin…
git rerere forget path git rerere clear
Note de workflow : ça coupe bien l’herbe sous le pied au principal argument du « rebase tout le temps sur le dernier master »… Parlons donc des merges de contrôle.
Christophe Porteneuve
Les articles de fond qui vont bien
Les slides sont sur bit.ly/devoxx-git
#DevoxxFR / #Git