.docx は ZIP に固めた XML の束です。二つの草稿を Git に見せても、返ってくるのは「バイナリファイルが異なります」の一行だけ。ところが LaTeX の原稿はプレーンテキストなので、git diff は共著者が動かした一文そのものを指し示せます。共同執筆とバージョン管理が LaTeX に馴染むのは、突きつめればこの一点のためです。ただし、放っておいて馴染むわけではありません。Git が比べるのは行であって文ではなく、段落まるごとを一行で書けばそれは分割不能な一行です。さらに悪いことに、LaTeX は <<<<<<< HEAD を抱えたままのファイルを何の文句も言わずコンパイルし、コンフリクトの跡をそのまま PDF に刷り込みます。このページでは、何をコミットするか、差分が読めるようにどこで改行するか、マージ衝突をどう生き延びるか、そして latexdiff・todonotes・changes がソースツリーを「共著者がレビューできるもの」に変えるまでを扱います。
なぜ git diff は .tex には効いて .docx には効かないのか
git diff は行を単位に、二つのファイルを文字どおり照合するだけの道具だからです。.tex は人間が打った文字がそのまま並んだファイルなので、照合結果は「この行がこの行に変わった」という読める報告になります。.docx の中身は圧縮された XML で、句読点を一つ足しただけでも圧縮結果は全体が別物になり、Git には「違う」以上のことが言えません。だからワープロ文書の共同執筆は、ファイルを添付して往復させ、最後に誰かが手で統合する作業になりがちです。LaTeX ではその往復が不要で、代わりに ブランチとマージ がその役目を引き受けます。逆にいえば、Git に読ませる価値のあるファイルだけを置き、読ませても意味のない生成物は最初から外しておく——共同執筆のリポジトリ設計は、ほぼこの一点に尽きます。
Git はもう一歩踏み込んだ配慮も用意しています。差分の各かたまりの見出し行(@@ -3,2 +3,2 @@ で始まる行)には、通常は「その付近にあった適当な行」が添えられますが、.gitattributes に *.tex diff=tex と一行書いておくと、Git は組み込みの tex 用ルールを使い、そのかたまりを含んでいる \section の名前 を見出しに載せてくれます。原稿が数百ページある文書では、差分をざっと流し読みするだけでどの節の話かが分かるので、効果は小さくありません。設定は一行、副作用はありません。
# .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.何をコミットし、何を .gitignore に入れるか
コミットするのは 手で書いたものだけ です。.tex、.bib、図の元データ、latexmkrc や Makefile、クラスファイルやスタイルファイル——これらが揃っていれば、誰の手元でも同じ PDF が組み上がります。逆に、コンパイルすれば必ず再生成されるファイルは一つ残らず .gitignore に入れます。biblatex と biber を使った最小の文書を一度 latexmk に通すだけで、.aux・.bbl・.bcf・.blg・.fdb_latexmk・.fls・.log・.run.xml・.toc が生まれます。索引を作れば .idx・.ilg・.ind が、hyperref を使えば .out が、SyncTeX を有効にすれば .synctex.gz が加わります。これらを追跡してしまうと、本文を一文字も触っていないコミットが毎回何百行もの雑音を運んでくることになります。
# .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*.tex生成 PDF をコミットするかどうかは、この方針の唯一の例外候補です。毎回のビルドで書き換わる main.pdf を追跡すると、差分は読めないうえリポジトリだけが太っていきます。ふだんは無視し、提出版やリリース版のように 後から一字一句そのまま取り出したい版 だけをタグや Release に添付するのが扱いやすいでしょう。.bbl も同じ考え方で、日常は生成物ですが、投稿先が .bbl 込みのソース一式を求める場合には提出の直前に作って別途同梱します——リポジトリに常時置いておく理由にはなりません。もう一つ、latexdiff が吐く *-diff*.tex も無視対象です。これは原稿ではなく成果物であり、うっかり本流に混ぜると次の版で \DIF だらけの原稿を編集する羽目になります。
1 文 1 行——差分の単位は改行である
原稿を 1 文につき 1 行 で書いてください。LaTeX にとって単独の改行はただの空白一つなので、組版結果は一文字も変わりません。変わるのは差分の読みやすさだけです。段落を一行で書くと、句読点を一つ直しただけでも Git はその段落全体を「消して足した」と報告します。文ごとに改行してあれば、報告されるのは変わった一文だけ。共著者のレビューは、その差だけで劇的に楽になります。改行位置は組版に影響しないので、既存の原稿を途中から 1 文 1 行に切り替えても PDF は変わりません——ただし切り替えたコミットは全行が動くので、内容の変更とは必ず別のコミットに分けます。
# 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.日本語・中国語・韓国語では、この助言はいっそう切実になります。英語の原稿なら git diff --word-diff という逃げ道があり、行が長くても変わった単語だけを [-古い-]{+新しい+} の形で見せてくれます。ところが --word-diff が単語の切れ目とみなすのは 空白 です。分かち書きをしない日本語で試すと、「吾輩は猫である。名前はまだ無い。」の「猫」を「犬」に変えただけでも、Git は行まるごとを削除して行まるごとを追加したと表示します。ならばと --word-diff-regex=. で一文字ずつ比べさせると、今度は UTF-8 のマルチバイト文字がバイト単位で切り刻まれ、吾輩は?[-??-]{+??+}である。 のような文字化けが返ってきます。つまり CJK の原稿には差分を細かくする逃げ道がありません。1 文 1 行は、英語圏では便利な作法ですが、日本語・中国語・韓国語では 事実上ただ一つの手段 です。
.tex のマージ衝突を解消する——LaTeX は警告してくれない
衝突マーカーを消し忘れたまま pdflatex を走らせても、エラーは一つも出ません。<<<<<<<・=======・>>>>>>> はどれも本文モードで合法な文字の並びであり、LaTeX にとってはただの記号です。結果、コンパイルは終了コード 0 で成功し、両方の版と衝突マーカーが並んだ PDF ができあがります。標準の OT1 エンコーディングでは < と > が逆さの約物に写像されるので、紙面には ¡¡¡¡¡¡¡ HEAD と ¿¿¿¿¿¿¿ feature という見慣れない行が現れます——これを見たときは、まず衝突の解消し忘れを疑ってください。
% 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"解消の作業自体は普通の Git と同じで、git status が挙げたファイルを開き、<<<<<<< から >>>>>>> までのどちらを残すか(あるいは両方を書き直すか)を決め、マーカーを削って git add します。LaTeX 特有の注意は二つあります。第一に、衝突が \begin{itemize} と \end{itemize} の途中に落ちると、片方の版だけを採用したときに開始と終了の対応が崩れ、こんどは本当にコンパイルが通らなくなります——マーカーを消すときは、環境の対が揃っているかを必ず目で確かめてください。第二に、衝突は 予防できます。1 文 1 行にしておけば Git は文単位で自動マージでき、二人が別々の文を直したかぎり衝突そのものが起きません。同じ節を二人で同時に書き直す予定があるなら、\include でファイルを分けて担当を切り分けるほうが確実です。
latexdiff — 二つの版を「変更が見える PDF」に変える
latexdiff が出力するのは PDF ではなく、変更を強調するマークアップを埋め込んだ新しい .tex です。それを自分でコンパイルすると、ワープロの変更履歴表示のような PDF が得られます。既定のスタイルでは、追加された語は青の波下線(ulem の \uwave)、削除された語は赤の取り消し線(\sout)になり、必要な \RequirePackage は生成ファイルのプリアンブルに自動で足されます。挿入される命令はすべて \DIF で始まる——\DIFadd、\DIFdel、\DIFaddbegin、図表の中では \DIFaddFL——ので、あとから機械的に見分けられます。作者は F. J. Tilmann、TeX Live 2024 に同梱されているのはバージョン 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.ここで注目に値するのは、latexdiff が 語単位 で比較していることです。上の例では「brown fox jumps」と「red fox leaps」を並べており、行が丸ごと置き換わったとは報告していません。git diff の行単位とは対照的で、両者は競合せず補い合います——履歴と自動マージは Git、共著者に見せる「何がどう変わったか」は latexdiff、という分担です。実務での落とし穴は三つ。\input や \include で分割した文書は --flatten を付けないと本体しか比較されません。数式の中の差分の細かさは --math-markup=level で調整でき、荒れるときは粗く落とします。そして生成された diff.tex は原稿ではないので、必ず別名のまま扱い、修正はもとの .tex に入れます。
latexdiff-vc で Git のリビジョンと直接くらべる
古い版をわざわざ書き出す必要はありません。同梱の latexdiff-vc に --git -r HEAD~3 のようにリビジョンを渡せば、その版を一時的に取り出して比較し、main-diffHEAD~3.tex という名前で差分ファイルを書きます。バージョン管理系を指定しなければ推測してくれますが、--git・--svn・--hg・--cvs・--rcs で明示するほうが確実です。--pdf を足すと差分ファイルに対して pdflatex を 2 回走らせ、PDF まで作ってくれます。「投稿から改訂まででどこが変わったか」を査読者に示す作業が、これで一行になります。生成された *-diff*.tex は .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 — 余白のメモを最終版で消す
\todo{...} は余白に色つきの付箋を置き、\listoftodos はそれを一枚の一覧にまとめます。原稿に「あとで直す」を書き残すための最小の道具で、% TODO というコメントより優れている点はただ一つ、印刷物に見える ことです。見えるからこそ消し忘れません。本文の流れに割り込ませたいときは \todo[inline]{...}、まだ描いていない図の場所を確保したいときは \missingfigure{...} を使い、TODO 一覧そのものを目次に載せたければ \todototoc を挟みます。そして最終版では \usepackage[disable]{todonotes} に切り替えれば、\todo の呼び出しを一つも消さないまま、すべてが紙面から消えます。obeyFinal を渡しておけば、文書クラスの final オプションに自動で追従させることもできます。
\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 — 著者ごとの赤入れと Undefined changes author エラー
複数人で赤を入れるなら changes パッケージです。\added{...}・\deleted{...}・\replaced{新}{旧}・\highlight{...}・\comment{...} で編集の意図を明示でき、\listofchanges が全変更の一覧を作り、draft オプションでマークアップを見せ、final に切り替えれば跡形もなく消えます。ただし最初に必ず一度つまずく場所があります。\added[id=AB]{...} と書いて著者 AB を定義していないと、コンパイルはこう止まります——! Package changes Error: Undefined changes author: AB. 続けて Undefined color 'authorcolor' という xcolor のエラーも並びますが、原因はどれも同じです。プリアンブルに \definechangesauthor[name={...}, color=blue]{AB} を一行足せば通ります。著者ごとに違う色を割り当てるのがこのパッケージの要点なので、共著者が増えるたびに一行ずつ足していく運用になります。
\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.}
\listofchangesOverleaf の共著者、Word しか開かない共著者
Git を全員に強いる必要はありません。Overleaf はブラウザだけで共同編集でき、独自の履歴と変更追跡の画面を持っているので、LaTeX に不慣れな共著者にはこちらのほうが速い場合が多いでしょう。しかも Overleaf のプロジェクトは Git リポジトリとしても扱えます(git clone できます)。この橋を渡しておけば、共著者はブラウザで書き、あなたは手元で git pull し、latexdiff-vc で差分 PDF を作る——という分業が成り立ちます。注意点は、.gitignore の考え方がそのまま当てはまることです。Overleaf 側で生成された PDF やログを取り込んでしまわないよう、同期の設定は最初に確認しておきます。
もっと厄介なのは、共著者が Word しか使わない場合です。ここで賢明なのは、LaTeX 側を正本と決めてしまう ことです。共著者に渡すときは pandoc で .tex から .docx を作り、変更履歴つきで返ってきた .docx は再び pandoc に食わせます。pandoc の --track-changes は accept(既定)・reject・all の三択で、Word の変更履歴の扱いを決めます。accept はすべての挿入と削除を反映し、reject は無視し、all は挿入・削除・コメントを著者と日時つきで残すので、特定の査読者の変更だけを取り込むといった選別が可能になります。このオプションが効くのは .docx を読み込むときだけです。往復のたびに書式は多少落ちますが、正本が LaTeX にある以上、失われるのは書式であって原稿ではありません。
| 道具 | 何を見せるか | いつ使うか |
|---|---|---|
git diff | ソースの行単位の差分 | 履歴・ブランチ・分担・自動マージが要るとき |
latexdiff | 語単位の差分を組版した PDF | 共著者や査読者に「紙面上で」変更を見せるとき |
todonotes | 余白の付箋と TODO 一覧 | 未完成の箇所を自分のために目立たせるとき |
changes | 著者ごとに色分けした編集とコメント | 複数人が一つの原稿に赤を入れるとき |
pandoc | .tex と .docx の相互変換 | 共著者が Word でしか作業しないとき |
最終 PDF を送り出す前の確認
todonotesをdisable、changesをfinalに切り替え、TODO も赤入れも PDF に残っていないことを実際に開いて確かめます。git grep -n "^<<<<<<< " -- "*.tex"で衝突マーカーの取り残しを探します——LaTeX は警告してくれません。latexmk -Cで生成物を消してからクリーンビルドし、手元の中間ファイルに依存していないことを確認します。latexdiffが作った*-diff*.texが原稿ディレクトリに紛れ込んでいないか、git statusで最後に一度見ます。- 共著者に送るときは「ソース一式」「差分 PDF」「最終 PDF」を分け、どれをレビューしてほしいかを明記します。