Eine .docx-Datei ist ein ZIP-Archiv voller XML. Legt man Git zwei Entwürfe davon vor, lautet die einzige Auskunft, dass sich die Binärdateien unterscheiden. Ein LaTeX-Manuskript dagegen ist reiner Text, und git diff zeigt genau auf den Satz, den ein Mitautor verschoben hat – diese eine Eigenschaft ist der Grund, weshalb Versionskontrolle und gemeinschaftliches Schreiben so gut zu LaTeX passen. Von allein stellt sich das allerdings nicht ein. Git vergleicht Zeilen, keine Sätze, und ein als eine lange Zeile geschriebener Absatz ist eine unteilbare Zeile. Schlimmer noch: LaTeX übersetzt eine Datei, in der noch <<<<<<< HEAD steht, ohne jede Beschwerde und druckt den Konflikt ins PDF. Diese Seite behandelt, was in die Versionskontrolle gehört, wo Zeilen umbrochen werden, damit ein Diff lesbar bleibt, wie ein Merge-Konflikt zu überstehen ist und wie latexdiff, todonotes und changes aus einem Quellbaum etwas machen, das Mitautorinnen und Mitautoren wirklich prüfen können.
Warum git diff bei .tex funktioniert und bei .docx nicht
Weil git diff nichts weiter ist als ein zeilenweiser Vergleich zweier Dateien. Eine .tex-Datei enthält genau die Zeichen, die jemand getippt hat, in derselben Reihenfolge; der Vergleich liefert deshalb einen lesbaren Bericht: aus dieser Zeile wurde jene. Das Innere einer .docx ist komprimiertes XML, und ein einziges zusätzliches Komma kann die komprimierten Bytes im Ganzen verändern – Git bleibt nichts als „unterschiedlich“. Deshalb wird die Zusammenarbeit an einem Textverarbeitungsdokument meist zu einer Kette von E-Mail-Anhängen, die am Ende jemand von Hand zusammenführt. Mit LaTeX entfällt diese Kette, Branches und Merges übernehmen die Aufgabe. Umgekehrt gilt: Nur Dateien ins Repository legen, die sich als Text lesen lassen, und alles Erzeugte, das einem Diff nichts sagt, von vornherein weglassen. Darauf läuft der Aufbau eines Repositories für gemeinsames Schreiben fast vollständig hinaus.
Git hält noch eine weitere Höflichkeit bereit. Die Kopfzeile jedes Hunks – die Zeile, die mit @@ -3,2 +3,2 @@ beginnt – trägt normalerweise irgendeine zufällig benachbarte Zeile. Steht in .gitattributes die einzelne Zeile *.tex diff=tex, greift Git auf seine eingebaute Regel für TeX zurück und setzt stattdessen den Namen des \section, das den Hunk umschließt, in diese Kopfzeile. Bei einem Manuskript von mehreren hundert Seiten ist es alles andere als nebensächlich, beim Überfliegen eines Diffs sofort zu sehen, zu welchem Abschnitt eine Änderung gehört. Eine Zeile Konfiguration, keine Nebenwirkungen.
# .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.Was gehört ins Repository, was in die .gitignore
Versioniert wird nur, was von Hand geschrieben wurde: die .tex-Dateien, die .bib-Datenbank, die Quellform der Abbildungen, latexmkrc oder das Makefile sowie jede Klassen- oder Stildatei, von der das Dokument abhängt. Damit lässt sich überall dasselbe PDF erzeugen. Alles, was beim Kompilieren neu entsteht, kommt ausnahmslos in die .gitignore. Ein einziger latexmk-Lauf über ein minimales Dokument mit biblatex und biber erzeugt bereits .aux, .bbl, .bcf, .blg, .fdb_latexmk, .fls, .log, .run.xml und .toc. Mit einem Index kommen .idx, .ilg und .ind hinzu, mit hyperref das .out, mit aktiviertem SyncTeX das .synctex.gz. Wer diese Dateien verfolgt, trägt bei jedem Commit Hunderte Zeilen Rauschen mit, auch wenn sich am Text kein Zeichen geändert hat.
# .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*.texDas erzeugte PDF ist der einzige Kandidat für eine Ausnahme. Ein main.pdf zu verfolgen, das sich bei jedem Build ändert, liefert unlesbare Diffs und ein Repository, das nur wächst. Bequemer ist es, das PDF im Alltag zu ignorieren und nur die Fassungen, die später zeichengenau reproduzierbar sein müssen – eine Einreichung, eine Veröffentlichung –, an ein Tag oder Release zu hängen. Für die .bbl gilt dasselbe: Sie ist ein Build-Ergebnis; verlangt ein Verlag ein Quellpaket samt .bbl, wird sie kurz vor der Abgabe erzeugt und separat beigelegt. Ein Grund, sie dauerhaft im Repository zu halten, ist das nicht. Ebenfalls ignoriert gehört die von latexdiff geschriebene *-diff*.tex. Sie ist ein Artefakt und kein Manuskript; gerät sie in den Hauptzweig, bearbeitet man in der nächsten Runde einen Entwurf voller \DIF-Befehle.
Ein Satz pro Zeile: Der Zeilenumbruch ist die Einheit des Diffs
Das Manuskript mit einem Satz pro Zeile schreiben. Ein einzelner Zeilenumbruch ist für LaTeX nur ein Leerzeichen, das Satzergebnis ändert sich also um kein Zeichen; was sich ändert, ist die Lesbarkeit des Diffs. Steht ein Absatz in einer Zeile, meldet Git schon nach einer korrigierten Kommastelle den gesamten Absatz als gelöscht und neu hinzugefügt. Steht nach jedem Satz ein Umbruch, meldet es den einen Satz, der sich bewegt hat. Für Mitautorinnen und Mitautoren, die Änderungen prüfen, macht allein dieser Unterschied alles aus. Da Zeilenumbrüche den Satz nicht beeinflussen, lässt sich ein bestehendes Manuskript jederzeit umstellen, ohne dass sich das PDF ändert – die Umstellung verschiebt allerdings jede Zeile und gehört deshalb in einen eigenen Commit, niemals zusammen mit einer inhaltlichen Änderung.
# 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.Für Japanisch, Chinesisch und Koreanisch wird aus dem Rat eine Notwendigkeit. Im Englischen gibt es einen Ausweg: git diff --word-diff zeigt auch in einer langen Zeile nur die geänderten Wörter, in der Form [-alt-]{+neu+}. Als Wortgrenze gilt für --word-diff allerdings Leerraum. Angewandt auf Japanisch, das ohne Leerzeichen geschrieben wird, führt schon die Änderung eines einzigen Zeichens in 吾輩は猫である。名前はまだ無い。 dazu, dass Git die ganze Zeile löscht und wieder hinzufügt. Auch --word-diff-regex=. für einen zeichenweisen Vergleich hilft nicht: Das Muster greift byteweise, mehrbyte-lange UTF-8-Zeichen werden zerschnitten, und heraus kommt Zeichensalat wie 吾輩は?[-??-]{+??+}である。. Ein CJK-Manuskript hat also keinen Ausweg. Ein Satz pro Zeile ist im Englischen eine gute Gewohnheit; im Japanischen, Chinesischen und Koreanischen ist es praktisch die einzige Möglichkeit.
Einen Merge-Konflikt in einer .tex-Datei auflösen – LaTeX warnt nicht
Läuft pdflatex über eine Datei, deren Konfliktmarker vergessen wurden, passiert schlicht nichts Schlimmes. <<<<<<<, ======= und >>>>>>> sind allesamt zulässige Zeichenfolgen im Textmodus; für LaTeX sind es bloß Satzzeichen. Die Übersetzung endet daher mit Rückgabewert 0 und erzeugt ein PDF, in dem beide Fassungen samt Markern stehen. In der voreingestellten OT1-Kodierung werden < und > auf umgekehrte Satzzeichen abgebildet, sodass auf der Seite zwei ungewohnte Zeilen erscheinen: ¡¡¡¡¡¡¡ HEAD und ¿¿¿¿¿¿¿ feature. Wer sie sieht, sollte zuerst an einen ungelösten Konflikt denken.
% 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"Die Auflösung selbst ist gewöhnliche Git-Arbeit: die von git status gemeldeten Dateien öffnen, entscheiden, welche Seite zwischen <<<<<<< und >>>>>>> bleibt – oder beide zu einer Fassung umschreiben –, die Marker löschen und git add ausführen. Zwei Punkte sind LaTeX-spezifisch. Erstens: Fällt ein Konflikt mitten in ein Paar \begin{itemize} … \end{itemize}, kann das Übernehmen nur einer Seite die Zuordnung zerreißen, und dann scheitert die Übersetzung tatsächlich; beim Entfernen der Marker also prüfen, ob jede Umgebung noch geöffnet und geschlossen wird. Zweitens lassen sich Konflikte vermeiden. Bei einem Satz pro Zeile führt Git satzweise automatisch zusammen, und solange zwei Personen verschiedene Sätze bearbeitet haben, entsteht überhaupt kein Konflikt. Steht bevor, dass zwei denselben Abschnitt gleichzeitig umschreiben, ist es sicherer, die Datei mit \include aufzuteilen und die Arbeit nach Dateien zu trennen.
latexdiff – aus zwei Fassungen ein PDF mit sichtbaren Änderungen machen
latexdiff gibt kein PDF aus, sondern eine neue .tex-Datei mit eingebettetem Änderungs-Markup. Wird diese selbst übersetzt, entsteht ein PDF, das aussieht wie die Änderungsverfolgung einer Textverarbeitung. Im voreingestellten Stil erscheinen hinzugefügte Wörter als blaue Wellenlinie (\uwave aus ulem), gelöschte als roter Durchstrich (\sout); die dafür nötigen \RequirePackage-Zeilen werden der Präambel der erzeugten Datei automatisch hinzugefügt. Jeder eingefügte Befehl beginnt mit \DIF – \DIFadd, \DIFdel, \DIFaddbegin und innerhalb von Gleitobjekten \DIFaddFL –, sie sind also mühelos wiederzufinden. Autor ist F. J. Tilmann; die in TeX Live 2024 enthaltene Version ist 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.Bemerkenswert ist hier, dass latexdiff wortweise vergleicht. Im obigen Beispiel stellt es „brown fox jumps“ gegen „red fox leaps“, statt eine ganze ersetzte Zeile zu melden. Das steht im Gegensatz zur Zeilengranularität von git diff, und beide konkurrieren weniger, als dass sie sich die Arbeit teilen: Git für Historie und automatisches Zusammenführen, latexdiff dafür, Mitautorinnen und Mitautoren zu zeigen, was sich wirklich geändert hat. Drei Fallen in der Praxis. Ein mit \input oder \include aufgeteiltes Dokument wird ohne --flatten nur auf oberster Ebene verglichen. Die Feinheit des Markups in abgesetzten Formeln lässt sich mit --math-markup=level einstellen; wird der Formelsatz unleserlich, hilft ein gröberer Wert. Und die erzeugte diff.tex ist nicht das Manuskript: Sie behält ihren eigenen Namen, und jede Korrektur geht in die ursprüngliche .tex.
Mit latexdiff-vc direkt gegen eine Git-Revision vergleichen
Die alte Fassung muss niemand von Hand exportieren. Übergibt man dem mitgelieferten latexdiff-vc eine Revision – etwa --git -r HEAD~3 –, holt es diese Fassung vorübergehend heraus, vergleicht sie und schreibt die Differenzdatei als main-diffHEAD~3.tex. Ohne Angabe rät es das Versionskontrollsystem; sicherer ist --git, --svn, --hg, --cvs oder --rcs. Mit --pdf läuft pdflatex zweimal über die Differenzdatei und erzeugt auch gleich das PDF. Einer gutachtenden Person zu zeigen, was sich zwischen Einreichung und Überarbeitung geändert hat, schrumpft damit auf einen einzigen Befehl. Die entstandene *-diff*.tex gehört in die .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 – Randnotizen, die in der Endfassung verschwinden
\todo{...} heftet einen farbigen Zettel an den Rand, und \listoftodos sammelt sie alle in einer einzigen Liste. Es ist das kleinstmögliche Werkzeug, um „später korrigieren“ im Manuskript zu hinterlassen, und sein einziger Vorteil gegenüber einem % TODO-Kommentar ist, dass es im Druck sichtbar ist. Genau diese Sichtbarkeit verhindert das Vergessen. \todo[inline]{...} setzt die Notiz mitten in den Textfluss, \missingfigure{...} reserviert den Platz einer noch nicht gezeichneten Abbildung, und \todototoc bringt die TODO-Liste selbst ins Inhaltsverzeichnis. Für die Endfassung genügt der Wechsel zu \usepackage[disable]{todonotes}: Alles verschwindet von der Seite, ohne dass ein einziger \todo-Aufruf gelöscht werden müsste. Mit obeyFinal folgen die Notizen stattdessen automatisch der final-Option der Dokumentklasse.
\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 – Markup je Autor und der Fehler Undefined changes author
Wenn mehrere Personen ein Manuskript kommentieren, ist das Paket changes das richtige Werkzeug. \added{...}, \deleted{...}, \replaced{neu}{alt}, \highlight{...} und \comment{...} machen die Absicht jeder Änderung deutlich, \listofchanges baut ein Verzeichnis aller Eingriffe, die Option draft zeigt das Markup, und der Wechsel zu final entfernt jede Spur. An einer Stelle stolpert allerdings jede und jeder beim ersten Mal. Steht \added[id=AB]{...} im Text, ohne dass der Autor AB definiert wurde, bricht der Lauf ab mit ! Package changes Error: Undefined changes author: AB. Darauf folgt eine Reihe von Undefined color-Fehlern aus xcolor, die aber alle dieselbe Ursache haben. Eine Zeile in der Präambel – \definechangesauthor[name={...}, color=blue]{AB} – behebt das. Jeder Person eine eigene Farbe zuzuweisen ist der eigentliche Zweck des Pakets; entsprechend kommt für jede hinzukommende Mitautorin eine weitere solche Zeile dazu.
\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.}
\listofchangesMitautoren auf Overleaf – und Mitautoren, die nur Word öffnen
Niemand muss alle Beteiligten zu Git drängen. Overleaf erlaubt gemeinsames Bearbeiten allein im Browser und bringt eine eigene Historie samt Änderungsverfolgung mit – für Mitautorinnen und Mitautoren ohne LaTeX-Routine oft der schnellere Weg. Ein Overleaf-Projekt lässt sich zudem als Git-Repository behandeln, es kann also geklont werden. Ist diese Brücke gebaut, wird eine Arbeitsteilung möglich: Die andere Seite schreibt im Browser, lokal folgt ein git pull, und latexdiff-vc erzeugt das Diff-PDF. Zu beachten ist nur, dass alles oben zu .gitignore Gesagte weiter gilt; die Synchronisationseinstellungen sollten früh geprüft werden, damit nicht die auf Overleaf erzeugten PDFs und Logdateien mit den Quellen herüberwandern.
Schwieriger ist der Fall, dass eine mitschreibende Person ausschließlich in Word arbeitet. Klug ist dann, die LaTeX-Seite zur maßgeblichen zu erklären. Zum Weitergeben erzeugt pandoc aus der .tex eine .docx; kommt sie mit Änderungsverfolgung zurück, geht sie erneut durch pandoc. Dessen Option --track-changes kennt accept (Voreinstellung), reject und all und entscheidet über den Umgang mit Words Änderungsverfolgung: accept übernimmt alle Einfügungen und Löschungen, reject ignoriert sie, und all behält Einfügungen, Löschungen und Kommentare samt Urheber und Zeitpunkt – so lassen sich etwa nur die Eingriffe einer bestimmten begutachtenden Person übernehmen. Die Option wirkt allein auf den .docx-Leser. Jede Runde kostet etwas Formatierung, doch solange die maßgebliche Fassung in LaTeX liegt, geht Formatierung verloren und nicht das Manuskript.
| Werkzeug | Was es zeigt | Wann greift man dazu |
|---|---|---|
git diff | zeilenweise Unterschiede in der Quelle | Historie, Branches und automatisches Zusammenführen gebraucht werden |
latexdiff | wortweise Unterschiede, gesetzt als PDF | Mitautoren oder Gutachter die Änderungen auf der Seite sehen sollen |
todonotes | Randnotizen und eine gesammelte TODO-Liste | etwas unfertig ist und sichtbar bleiben muss |
changes | Änderungen und Kommentare je Autor eingefärbt | mehrere Personen ein Manuskript kommentieren |
pandoc | nichts – es wandelt zwischen .tex und .docx | eine mitschreibende Person nur in Word arbeitet |
Prüfungen, bevor das finale PDF das Haus verlässt
todonotesaufdisableundchangesauffinalumstellen, dann das PDF öffnen und prüfen, dass kein TODO und kein Markup überlebt hat.- Mit
git grep -n "^<<<<<<< " -- "*.tex"nach übrig gebliebenen Konfliktmarkern suchen – LaTeX warnt nicht davor. - Die Build-Ergebnisse mit
latexmk -Clöschen und neu übersetzen, um zu belegen, dass das Dokument nicht von einer veralteten Zwischendatei abhängt. - Ein letztes Mal
git statusansehen, ob eine*-diff*.texvonlatexdiffins Manuskriptverzeichnis geraten ist. - Beim Versand an Mitautorinnen und Mitautoren Quellpaket, Diff-PDF und finales PDF trennen und klar sagen, was geprüft werden soll.