A .docx file is a zip archive of XML. Show Git two drafts of one and all it can tell you is that the binary files differ. A LaTeX manuscript is plain text, so git diff can point straight at the sentence a co-author moved — and that single property is why version control and collaborative writing sit so comfortably under LaTeX. The fit is not automatic, though. Git compares lines, not sentences, and a paragraph written as one long line is one indivisible line. Worse, LaTeX will cheerfully compile a file still carrying <<<<<<< HEAD and print the conflict into the PDF without a word of complaint. This page covers what to commit, where to break lines so a diff stays readable, how to survive a merge conflict, and how latexdiff, todonotes and changes turn a source tree into something a co-author can actually review.
Why git diff works on a .tex file and not on a .docx
Because git diff is nothing more than a line-by-line comparison of two files. A .tex file holds the characters a human typed, in order, so the comparison comes back as a readable report: this line became that line. The inside of a .docx is compressed XML, and adding one comma can change the compressed bytes wholesale, so Git has nothing to say beyond “different”. That is why collaborating on a word-processor document tends to become a relay of email attachments that somebody merges by hand at the end. With LaTeX the relay disappears and branches and merges take over the job. Put the other way round: keep in the repository only the files that are worth reading as text, and leave out the generated ones that mean nothing to a diff. Almost all repository design for co-writing comes down to that.
Git has one further courtesy waiting. The header of each hunk — the line starting @@ -3,2 +3,2 @@ — normally carries whatever nearby line Git happened to grab. Add the single line *.tex diff=tex to .gitattributes and Git switches to its built-in rule for TeX, putting the name of the \section that encloses the hunk on that header instead. In a manuscript of several hundred pages, being able to skim a diff and see which section each change belongs to is not a small thing. One line of configuration, no side effects.
# .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.What to commit, and what belongs in .gitignore
Commit only what a human wrote: the .tex files, the .bib database, the source form of your figures, latexmkrc or the Makefile, and any class or style file the document depends on. With those, anyone can rebuild the same PDF. Everything that compilation regenerates goes into .gitignore without exception. Running latexmk once over a minimal document that uses biblatex and biber already produces .aux, .bbl, .bcf, .blg, .fdb_latexmk, .fls, .log, .run.xml and .toc. Add an index and you get .idx, .ilg and .ind; add hyperref and you get .out; enable SyncTeX and you get .synctex.gz. Track those and every commit will carry hundreds of lines of noise even when not one character of prose has changed.
# .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*.texThe generated PDF is the one candidate for an exception. Tracking a main.pdf that changes on every build gives you unreadable diffs and a repository that only grows. Ignoring it day to day and attaching just the versions you may need to reproduce byte for byte later — a submission, a release — to a tag or a Release is easier to live with. The .bbl follows the same logic: it is a build product, but if a publisher wants a source bundle with the .bbl included, generate it at submission time and ship it separately. That is not a reason to keep it in the repository permanently. One more thing worth ignoring is the *-diff*.tex that latexdiff writes. It is an artefact, not a manuscript, and letting it drift into the main line means editing a draft full of \DIF commands next time round.
One sentence per line: the line break is the unit of a diff
Write the manuscript one sentence to a line. A single newline is just a space to LaTeX, so the typeset result does not change by one character; what changes is how the diff reads. With a paragraph on one line, correcting a single comma makes Git report the whole paragraph as deleted and re-added. With a line break after each sentence, it reports the one sentence that moved. For a co-author reviewing your changes, that difference alone is transformative. Because line breaks do not affect typesetting, you can convert an existing manuscript to one-sentence-per-line at any point and the PDF stays identical — but the conversion moves every line, so it must be a commit of its own, never mixed with a change of content.
# 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.For Japanese, Chinese and Korean the advice becomes urgent rather than merely helpful. In English there is an escape hatch: git diff --word-diff shows only the words that changed, as [-old-]{+new+}, even on a long line. But what --word-diff treats as a word boundary is whitespace. Run it on Japanese, which is written without spaces, and changing one character in 吾輩は猫である。名前はまだ無い。 still makes Git delete the whole line and add the whole line back. Reaching for --word-diff-regex=. to compare character by character does not help either: the pattern is applied byte by byte, so multi-byte UTF-8 characters are sliced apart and the output comes back as mojibake like 吾輩は?[-??-]{+??+}である。. In other words, a CJK manuscript has no escape hatch. One sentence per line is a good habit in English; in Japanese, Chinese and Korean it is effectively the only option.
Resolving a merge conflict in a .tex file — LaTeX will not warn you
Run pdflatex on a file whose conflict markers you forgot to remove and nothing goes wrong. <<<<<<<, ======= and >>>>>>> are all legal sequences of text-mode characters; to LaTeX they are just punctuation. The compilation therefore succeeds with exit code 0 and produces a PDF containing both versions and the markers between them. In the default OT1 encoding, < and > map to inverted punctuation, so what appears on the page are two unfamiliar lines reading ¡¡¡¡¡¡¡ HEAD and ¿¿¿¿¿¿¿ feature. If you ever see those, suspect an unresolved conflict first.
% 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"The resolution itself is ordinary Git work: open the files git status lists, decide which side of <<<<<<< … >>>>>>> survives — or rewrite both into one — delete the markers, and git add. Two things are specific to LaTeX. First, if a conflict lands in the middle of a \begin{itemize} … \end{itemize} pair, keeping only one side can break the pairing, and then the document really does fail to compile; when removing markers, check with your eyes that every environment still opens and closes. Second, conflicts can be prevented. With one sentence per line, Git can merge sentence by sentence automatically, and as long as two people edited different sentences no conflict arises at all. If two people are going to rewrite the same section at the same time, splitting the file with \include and dividing the work by file is the surer route.
latexdiff — turning two versions into a marked-up PDF
What latexdiff writes out is not a PDF but a new .tex file with change markup embedded in it. Compile that yourself and you get a PDF that looks like a word processor's track-changes view. In the default style, added words appear as a blue wavy underline (\uwave from ulem) and deleted words as a red strikeout (\sout); the \RequirePackage lines needed for that are added to the generated file's preamble automatically. Every command it inserts begins with \DIF — \DIFadd, \DIFdel, \DIFaddbegin, and \DIFaddFL inside floats — so they are trivial to spot afterwards. The author is F. J. Tilmann; the version shipped with TeX Live 2024 is 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.What deserves attention here is that latexdiff compares word by word. In the example above it sets “brown fox jumps” against “red fox leaps” rather than reporting a whole replaced line. That contrasts with the line granularity of git diff, and the two do not compete so much as divide the labour: Git for history and automatic merging, latexdiff for showing a co-author what actually changed. Three practical traps. A document split with \input or \include is compared only at the top level unless you add --flatten. The granularity of markup inside displayed mathematics is tunable with --math-markup=level, and turning it down is the cure when equations come out mangled. And the generated diff.tex is not the manuscript: keep it under its own name and make every correction in the original .tex.
Diffing against a Git revision with latexdiff-vc
There is no need to export the old version by hand. Hand a revision to the bundled latexdiff-vc — --git -r HEAD~3, say — and it checks that version out temporarily, compares it, and writes the difference file as main-diffHEAD~3.tex. It will guess your version control system if you do not name one, but --git, --svn, --hg, --cvs or --rcs is safer. Adding --pdf runs pdflatex twice over the difference file and produces the PDF as well. Showing a referee what changed between submission and revision thus collapses into a single command. Remember to keep the resulting *-diff*.tex in .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 — margin notes that vanish from the final build
\todo{...} pins a coloured sticky note in the margin and \listoftodos gathers every one of them into a single list. It is the smallest possible tool for leaving “fix this later” inside a manuscript, and its one advantage over a % TODO comment is that it is visible in print. Being visible is what stops it from being forgotten. Use \todo[inline]{...} when the note should interrupt the flow of the text, \missingfigure{...} to reserve the place of a figure you have not drawn yet, and \todototoc if you want the TODO list itself to appear in the table of contents. For the final build, switch to \usepackage[disable]{todonotes} and everything disappears from the page without deleting a single \todo call. Passing obeyFinal instead makes the notes follow the document class's own final option automatically.
\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 — per-author markup, and the Undefined changes author error
When several people mark up one manuscript, the changes package is the right tool. \added{...}, \deleted{...}, \replaced{new}{old}, \highlight{...} and \comment{...} state the intent of each edit, \listofchanges builds an index of them all, the draft option shows the markup, and switching to final removes every trace. There is one place where everybody trips the first time, though. Write \added[id=AB]{...} without having defined the author AB and the run stops with ! Package changes Error: Undefined changes author: AB. A cascade of Undefined color errors from xcolor follows, but they all have the same cause. One line in the preamble — \definechangesauthor[name={...}, color=blue]{AB} — fixes it. Assigning a distinct colour per author is the whole point of the package, so the working pattern is to add one such line each time a co-author joins.
\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.}
\listofchangesCo-authors on Overleaf, and co-authors who only open Word
Not everyone has to be pushed onto Git. Overleaf allows collaborative editing in the browser and has its own history and change-tracking view, which is often the faster route for a co-author who is not fluent in LaTeX. An Overleaf project can also be treated as a Git repository — you can git clone it. With that bridge in place a division of labour becomes possible: the co-author writes in the browser, you git pull locally and produce a diff PDF with latexdiff-vc. The one thing to watch is that everything said above about .gitignore still applies; check the sync settings early so that PDFs and logs generated on the Overleaf side do not come across with the sources.
The harder case is a co-author who works only in Word. The wise move here is to declare the LaTeX side authoritative. Send them a .docx produced from your .tex with pandoc, and feed the marked-up .docx back through pandoc when it returns. Its --track-changes option takes accept (the default), reject or all, deciding what happens to Word's tracked changes: accept applies every insertion and deletion, reject ignores them, and all keeps insertions, deletions and comments together with the author and the time of each change, which makes it possible to take in only one reviewer's edits. The option affects the .docx reader only. Each round trip costs some formatting, but as long as the authoritative copy lives in LaTeX, what you lose is formatting and not the manuscript.
| Tool | What it shows | Reach for it when |
|---|---|---|
git diff | line-by-line differences in the source | you need history, branches, and automatic merging |
latexdiff | word-by-word differences typeset into a PDF | a co-author or referee must see the changes on the page |
todonotes | margin notes and a collected TODO list | something is unfinished and must stay visible to you |
changes | edits and comments coloured per author | several people are marking up one manuscript |
pandoc | nothing — it converts between .tex and .docx | a co-author works only in Word |
Checks before the final PDF leaves your machine
- Switch
todonotestodisableandchangestofinal, then open the PDF and confirm that no TODO and no markup survived. - Hunt for leftover conflict markers with
git grep -n "^<<<<<<< " -- "*.tex"— LaTeX will not warn you about them. - Delete the build products with
latexmk -Cand rebuild from clean, to prove the document does not depend on a stale intermediate file. - Check
git statusone last time for a*-diff*.texfromlatexdiffthat has wandered into the manuscript directory. - When sending to co-authors, separate the source bundle, the diff PDF and the final PDF, and say plainly which one you want reviewed.