Un fichier .docx est une archive ZIP remplie de XML. Présentez-en deux versions à Git : tout ce qu'il saura dire, c'est que les fichiers binaires diffèrent. Un manuscrit LaTeX, lui, est du texte brut, et git diff désigne exactement la phrase qu'un coauteur a déplacée — c'est cette seule propriété qui rend le contrôle de version et l'écriture collaborative si naturels avec LaTeX. L'accord ne va pourtant pas de soi. Git compare des lignes et non des phrases, et un paragraphe écrit sur une seule longue ligne est une ligne indivisible. Pire : LaTeX compile sans broncher un fichier qui contient encore <<<<<<< HEAD et imprime le conflit dans le PDF. Cette page traite de ce qu'il faut valider, de l'endroit où couper les lignes pour qu'un diff reste lisible, de la manière de survivre à un conflit de fusion, et de la façon dont latexdiff, todonotes et changes transforment un arbre de sources en quelque chose qu'un coauteur peut réellement relire.
Pourquoi git diff fonctionne sur un .tex et pas sur un .docx
Parce que git diff n'est rien d'autre qu'une comparaison ligne à ligne de deux fichiers. Un fichier .tex contient les caractères tapés par une personne, dans l'ordre ; la comparaison produit donc un rapport lisible : cette ligne est devenue celle-là. L'intérieur d'un .docx est du XML compressé, et une virgule de plus peut modifier les octets compressés dans leur ensemble : Git n'a rien à dire au-delà de « différents ». Voilà pourquoi la collaboration sur un document de traitement de texte tourne souvent à l'échange de pièces jointes que quelqu'un fusionne à la main à la fin. Avec LaTeX, cet échange disparaît et les branches et fusions prennent le relais. À l'inverse : ne conserver dans le dépôt que les fichiers qui se lisent comme du texte et écarter d'emblée les fichiers générés, qui ne signifient rien pour un diff. La conception d'un dépôt d'écriture à plusieurs tient presque tout entière là-dedans.
Git réserve une attention supplémentaire. L'en-tête de chaque bloc de différences — la ligne qui commence par @@ -3,2 +3,2 @@ — reprend d'ordinaire une ligne voisine choisie au hasard. Il suffit d'ajouter la ligne *.tex diff=tex dans .gitattributes pour que Git bascule sur sa règle intégrée pour TeX et place à la place le nom du \section qui contient le bloc. Dans un manuscrit de plusieurs centaines de pages, pouvoir parcourir un diff et voir à quelle section appartient chaque modification n'a rien d'anecdotique. Une ligne de configuration, aucun effet de bord.
# .gitattributes — teach git the structure of a .tex file
*.tex diff=tex
# hunk headers now name the enclosing sectioning command:
# @@ -3,2 +3,2 @@ \section{First}
# without it, git prints an arbitrary nearby line instead.Que valider, et que mettre dans .gitignore
On ne valide que ce qu'une personne a écrit : les fichiers .tex, la base .bib, la forme source des figures, latexmkrc ou le Makefile, ainsi que toute classe ou feuille de style dont le document dépend. Avec cela, chacun reconstruit le même PDF. Tout ce que la compilation régénère va sans exception dans .gitignore. Un seul passage de latexmk sur un document minimal utilisant biblatex et biber produit déjà .aux, .bbl, .bcf, .blg, .fdb_latexmk, .fls, .log, .run.xml et .toc. Un index ajoute .idx, .ilg et .ind ; hyperref ajoute .out ; SyncTeX activé ajoute .synctex.gz. Suivre ces fichiers, c'est faire porter à chaque commit des centaines de lignes de bruit alors même que pas un caractère du texte n'a bougé.
# .gitignore — everything below is regenerated by a build
*.aux
*.log
*.out
*.toc
*.lof
*.lot
*.fls
*.fdb_latexmk
*.synctex.gz
*.bbl
*.blg
*.bcf
*.run.xml
*.idx
*.ilg
*.ind
# generated PDFs: ignore the working build, keep tagged releases by hand
main.pdf
*-diff*.texLe PDF généré est le seul candidat à une exception. Suivre un main.pdf qui change à chaque compilation donne des diffs illisibles et un dépôt qui ne fait qu'enfler. Il est plus commode de l'ignorer au quotidien et de n'attacher à un tag ou à une Release que les versions à reproduire plus tard au caractère près : une soumission, une publication. Le .bbl obéit à la même logique : c'est un produit de compilation, mais si un éditeur réclame un lot de sources incluant le .bbl, on le génère au moment de la soumission et on le joint à part. Cela ne justifie pas de le conserver en permanence dans le dépôt. Autre fichier à ignorer : le *-diff*.tex que produit latexdiff. C'est un artefact, pas un manuscrit, et le laisser dériver vers la branche principale revient à éditer, au tour suivant, un brouillon truffé de commandes \DIF.
Une phrase par ligne : l'unité du diff est le saut de ligne
Écrire le manuscrit à raison d'une phrase par ligne. Un simple saut de ligne n'est qu'une espace pour LaTeX : le résultat composé ne change pas d'un caractère, seule change la lisibilité du diff. Si un paragraphe tient sur une ligne, corriger une virgule suffit pour que Git signale le paragraphe entier comme supprimé puis rajouté. Avec un saut de ligne après chaque phrase, il signale la seule phrase qui a bougé. Pour un coauteur qui relit les modifications, cette seule différence change tout. Comme les sauts de ligne n'influent pas sur la composition, un manuscrit existant peut être converti à tout moment sans que le PDF varie — mais la conversion déplace toutes les lignes et doit donc faire l'objet d'un commit distinct, jamais mêlé à une modification de fond.
# whole paragraph on one line: git rewrites the entire paragraph
-The fox jumps over the dog. The morning was fine. Nobody minded.
+The fox jumps over the dog. The morning was cold. Nobody minded.
# one sentence per line: git points at the sentence that moved
The fox jumps over the dog.
-The morning was fine.
+The morning was cold.
Nobody minded.Pour le japonais, le chinois et le coréen, le conseil cesse d'être un simple confort. En anglais, il existe une échappatoire : git diff --word-diff n'affiche que les mots modifiés, sous la forme [-ancien-]{+nouveau+}, même sur une ligne longue. Mais ce que --word-diff considère comme une frontière de mot, c'est l'espace. Appliqué au japonais, qui s'écrit sans espaces, le changement d'un seul caractère dans 吾輩は猫である。名前はまだ無い。 pousse encore Git à supprimer toute la ligne puis à la rajouter. Recourir à --word-diff-regex=. pour comparer caractère par caractère ne sauve rien : le motif s'applique octet par octet, les caractères UTF-8 multioctets sont découpés et la sortie revient sous forme de charabia, du type 吾輩は?[-??-]{+??+}である。. Autrement dit, un manuscrit CJK n'a aucune échappatoire. Une phrase par ligne est une bonne habitude en anglais ; en japonais, en chinois et en coréen, c'est pratiquement la seule solution.
Résoudre un conflit de fusion dans un .tex — LaTeX ne vous préviendra pas
Lancer pdflatex sur un fichier dont on a oublié d'ôter les marqueurs de conflit ne provoque aucune erreur. <<<<<<<, ======= et >>>>>>> sont des suites de caractères parfaitement licites en mode texte ; pour LaTeX, ce ne sont que des signes de ponctuation. La compilation réussit donc avec le code de retour 0 et produit un PDF contenant les deux versions et les marqueurs entre elles. Dans le codage OT1 par défaut, < et > correspondent à des signes inversés : la page affiche alors deux lignes inhabituelles, ¡¡¡¡¡¡¡ HEAD et ¿¿¿¿¿¿¿ feature. Si elles apparaissent, il faut d'abord soupçonner un conflit non résolu.
% what git leaves behind - and what LaTeX happily typesets
\begin{document}
<<<<<<< HEAD
Main branch sentence.
=======
Feature branch sentence.
>>>>>>> feature
\end{document}
% check before every build:
% git grep -n "^<<<<<<< " -- "*.tex"La résolution proprement dite relève du travail Git ordinaire : ouvrir les fichiers listés par git status, décider quel côté de <<<<<<< … >>>>>>> subsiste — ou réécrire les deux en un seul — supprimer les marqueurs et faire git add. Deux points sont propres à LaTeX. D'abord, si un conflit tombe au milieu d'un couple \begin{itemize} … \end{itemize}, ne conserver qu'un côté peut rompre l'appariement, et la compilation échoue alors pour de bon ; en retirant les marqueurs, vérifier de visu que chaque environnement s'ouvre et se ferme. Ensuite, les conflits se préviennent. Avec une phrase par ligne, Git fusionne automatiquement phrase par phrase, et tant que deux personnes ont modifié des phrases différentes, aucun conflit ne survient. Si deux personnes doivent réécrire la même section en même temps, découper le fichier avec \include et répartir le travail par fichier est plus sûr.
latexdiff — transformer deux versions en un PDF annoté
Ce que latexdiff produit n'est pas un PDF mais un nouveau fichier .tex contenant un balisage des changements. En le compilant soi-même, on obtient un PDF qui ressemble à l'affichage du suivi des modifications d'un traitement de texte. Dans le style par défaut, les mots ajoutés apparaissent en soulignement ondulé bleu (\uwave de ulem) et les mots supprimés en biffure rouge (\sout) ; les lignes \RequirePackage nécessaires sont ajoutées automatiquement au préambule du fichier généré. Toutes les commandes insérées commencent par \DIF — \DIFadd, \DIFdel, \DIFaddbegin, et \DIFaddFL à l'intérieur des flottants — ce qui les rend faciles à repérer ensuite. L'auteur est F. J. Tilmann ; la version livrée avec TeX Live 2024 est la 1.3.3.
latexdiff --flatten old.tex new.tex > diff.tex
pdflatex diff.tex # additions blue and underlined, deletions red and struck out
# what latexdiff actually writes into the body:
# The quick \DIFdelbegin \DIFdel{brown fox jumps }\DIFdelend
# \DIFaddbegin \DIFadd{red fox leaps }\DIFaddend over the lazy dog.Ce qui mérite l'attention ici, c'est que latexdiff compare mot à mot. Dans l'exemple ci-dessus, il oppose « brown fox jumps » à « red fox leaps » au lieu de signaler une ligne entièrement remplacée. Cela tranche avec la granularité de ligne de git diff, et les deux outils se répartissent la tâche plutôt qu'ils ne se concurrencent : Git pour l'historique et la fusion automatique, latexdiff pour montrer à un coauteur ce qui a réellement changé. Trois pièges pratiques. Un document découpé avec \input ou \include n'est comparé qu'au premier niveau sans --flatten. La finesse du balisage à l'intérieur des formules hors texte se règle avec --math-markup=level ; l'abaisser est le remède lorsque les formules ressortent illisibles. Enfin, le diff.tex produit n'est pas le manuscrit : il garde son propre nom, et toute correction retourne dans le .tex d'origine.
Comparer avec une révision Git grâce à latexdiff-vc
Inutile d'exporter l'ancienne version à la main. En passant une révision au latexdiff-vc fourni — --git -r HEAD~3, par exemple —, celui-ci extrait temporairement cette version, la compare et écrit le fichier de différences sous le nom main-diffHEAD~3.tex. Il devine le système de contrôle de version si on ne le précise pas, mais --git, --svn, --hg, --cvs ou --rcs est plus sûr. Ajouter --pdf fait passer deux fois pdflatex sur le fichier de différences et produit également le PDF. Montrer à un relecteur ce qui a changé entre la soumission et la version révisée tient ainsi en une seule commande. Ne pas oublier de placer le *-diff*.tex obtenu dans .gitignore.
latexdiff-vc --git -r HEAD~3 main.tex # writes main-diffHEAD~3.tex
latexdiff-vc --git --pdf -r v1.0 main.tex # ...and builds the PDF as well
# output of the run:
# Running: latexdiff "main-oldtmp-15378.tex" "main.tex" > "main-diffHEAD~3.tex"
# Generated difference file main-diffHEAD~3.textodonotes — des notes en marge qui disparaissent de la version finale
\todo{...} épingle un pense-bête coloré dans la marge et \listoftodos les rassemble tous en une seule liste. C'est le plus petit outil possible pour laisser un « à corriger plus tard » dans un manuscrit, et son unique avantage sur un commentaire % TODO est d'être visible à l'impression. C'est cette visibilité qui empêche l'oubli. \todo[inline]{...} place la note en plein fil du texte, \missingfigure{...} réserve l'emplacement d'une figure non encore dessinée, et \todototoc fait apparaître la liste des TODO dans la table des matières. Pour la version finale, il suffit de passer à \usepackage[disable]{todonotes} : tout disparaît de la page sans qu'un seul appel à \todo soit supprimé. Avec obeyFinal, les notes suivent au contraire automatiquement l'option final de la classe de document.
\usepackage{todonotes} % [disable] hides every note in the final build
...
\todo{Citation needed here}
\todo[inline]{Rewrite this paragraph before submission}
\missingfigure{Circuit diagram goes here}
\listoftodoschanges — un balisage par auteur, et l'erreur Undefined changes author
Lorsque plusieurs personnes annotent un même manuscrit, le package changes est l'outil adapté. \added{...}, \deleted{...}, \replaced{nouveau}{ancien}, \highlight{...} et \comment{...} explicitent l'intention de chaque modification, \listofchanges en dresse l'index complet, l'option draft affiche le balisage et le passage à final en efface toute trace. Il est pourtant un endroit où tout le monde trébuche la première fois. Écrire \added[id=AB]{...} sans avoir défini l'auteur AB arrête la compilation sur ! Package changes Error: Undefined changes author: AB. Suit une cascade d'erreurs Undefined color venues de xcolor, mais toutes ont la même cause. Une ligne dans le préambule — \definechangesauthor[name={...}, color=blue]{AB} — y remédie. Attribuer une couleur distincte à chaque auteur est précisément la raison d'être du package : on ajoute donc une ligne de ce type à chaque nouveau coauteur.
\usepackage[draft]{changes} % swap draft for final to hide all markup
\definechangesauthor[name={Ada Byron}, color=blue]{AB}
\definechangesauthor[name={Bob Lane}, color=orange]{BL}
...
\added[id=AB]{A sentence the reviewer asked for.}
\replaced[id=AB]{new wording}{old wording}
\deleted[id=BL]{This clause has to go.}
\listofchangesCoauteurs sur Overleaf, et coauteurs qui n'ouvrent que Word
Il n'est pas nécessaire d'imposer Git à tout le monde. Overleaf permet l'édition collaborative depuis le seul navigateur et dispose de son propre historique et de son propre suivi des modifications, souvent la voie la plus rapide pour un coauteur peu à l'aise avec LaTeX. Un projet Overleaf peut de surcroît être traité comme un dépôt Git : il se clone. Ce pont établi, une répartition du travail devient possible : l'autre écrit dans le navigateur, on fait un git pull en local et l'on produit le PDF de différences avec latexdiff-vc. Seule précaution : tout ce qui a été dit plus haut sur .gitignore reste valable ; il faut vérifier tôt les réglages de synchronisation pour que les PDF et journaux produits du côté d'Overleaf ne suivent pas les sources.
Le cas plus délicat est celui d'un coauteur qui ne travaille que sous Word. Le geste avisé consiste alors à désigner la version LaTeX comme faisant foi. On lui transmet un .docx produit à partir du .tex par pandoc, et le .docx annoté repasse par pandoc au retour. Son option --track-changes accepte accept (valeur par défaut), reject ou all et décide du sort des modifications suivies par Word : accept applique toutes les insertions et suppressions, reject les ignore, et all conserve insertions, suppressions et commentaires avec l'auteur et la date de chaque changement, ce qui permet de ne retenir que les corrections d'un relecteur donné. Cette option n'agit que sur le lecteur .docx. Chaque aller-retour coûte un peu de mise en forme, mais tant que la version faisant foi réside dans LaTeX, ce qui se perd est la mise en forme, pas le manuscrit.
| Outil | Ce qu'il montre | Quand y recourir |
|---|---|---|
git diff | différences ligne à ligne dans la source | il faut un historique, des branches et une fusion automatique |
latexdiff | différences mot à mot composées en PDF | un coauteur ou un relecteur doit voir les changements sur la page |
todonotes | des notes en marge et une liste de TODO | quelque chose est inachevé et doit rester visible |
changes | modifications et commentaires colorés par auteur | plusieurs personnes annotent un même manuscrit |
pandoc | rien — il convertit entre .tex et .docx | un coauteur ne travaille que sous Word |
Vérifications avant l'envoi du PDF final
- Passer
todonotesàdisableetchangesàfinal, puis ouvrir le PDF et vérifier qu'aucun TODO ni balisage n'a survécu. - Traquer les marqueurs de conflit oubliés avec
git grep -n "^<<<<<<< " -- "*.tex"— LaTeX ne signalera rien. - Effacer les produits de compilation avec
latexmk -Cpuis recompiler à neuf, pour prouver que le document ne dépend d'aucun fichier intermédiaire périmé. - Consulter une dernière fois
git statuspour repérer un*-diff*.texdelatexdiffégaré dans le répertoire du manuscrit. - À l'envoi aux coauteurs, séparer le lot de sources, le PDF de différences et le PDF final, et dire clairement lequel doit être relu.