Overleaf Git Integration
If your experiment code lives in a GitHub repository and your paper lives on Overleaf, you have probably done the upload dance: rerun an experiment, hunt down the regenerated figure on disk, open Overleaf, delete the old file, upload the new one, and repeat for every figure and table that changed. It is slow, it is easy to leave a stale figure behind, and it leaves no record of which version of your code produced which figure in the paper.
Overleaf Premium's GitHub Synchronization feature removes most of this busywork. This article describes one particular way to use it: cloning the paper's repository inside your experiment repository, in a directory that the experiment repository ignores. Your analysis scripts then write figures and tables straight into the paper, and a single push sends them to Overleaf.
How It Works
There are several ways to connect a code repository to an Overleaf project. In this one, the two repositories stay completely independent. The paper repository is cloned into a subdirectory (here called paper/), and that subdirectory is listed in the experiment repository's .gitignore. The experiment repository never records anything about the paper, and the paper repository never sees your experimental code. Your scripts simply write files into a folder that happens to be a different Git repository.
The final layout looks like this:
my-experiments/ ← experiment repo (github.com/you/my-experiments)
├── .gitignore ← contains "paper/"
├── src/
├── scripts/
│ └── make_figures.py ← writes into paper/figures and paper/tables
├── Makefile
└── paper/ ← SEPARATE git repo, ignored by the outer repo
├── .git/ ← points to github.com/you/my-paper
├── main.tex
├── refs.bib
├── figures/ ← written by code
│ └── coverage.pdf
└── tables/ ← written by code
└── results.tex
Generated figures and tables flow from your code to Overleaf, and prose written on Overleaf flows back to your local copy:
scripts/make_figures.py ──writes──▶ paper/figures, paper/tables paper/ ──git push──▶ GitHub (my-paper) ──"Pull GitHub changes"──▶ Overleaf Overleaf ──"Push Overleaf changes"──▶ GitHub (my-paper) ──git pull──▶ paper/
| Strengths | Weaknesses |
|---|---|
|
|
| 🔗 Overleaf Documentation: GitHub Synchronization | |
Setting It Up
Step 1: Link Overleaf to GitHub
In Overleaf, open Account Settings and find the Git entry under the project synchronization section. Click Link and set up access in the dialogue that pops up.
Step 2: Create a GitHub repository from your Overleaf project
Open your paper in Overleaf, click Menu in the top left, and choose GitHub under the Sync section. Select Create a GitHub repository, give it a name (e.g. my-paper), and make it private. Overleaf then pushes the current contents of your project to the new repository.
Step 3: Clone the paper inside your experiment repository
From the root of your experiment repository, tell Git to ignore the paper/ directory before you clone into it:
cd my-experiments
echo "paper/" >> .gitignore
git add .gitignore
git commit -m "Ignore nested paper clone"
git clone git@github.com:you/my-paper.git paperThen check that the two repositories don't see each other:
git status # should NOT list paper/
git -C paper status # shows the paper repo, on its own branchpaper/; the inner repository is independent.Step 4: Point your code at the paper directory
Rather than hard-coding paths in every script, define the paper location once. Using an environment variable with a sensible default means the code still runs for people who haven't cloned the paper (their figures just land in an ignored folder).
# src/paths.py
import os
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
PAPER_DIR = Path(os.environ.get("PAPER_DIR", REPO_ROOT / "paper"))
FIG_DIR = PAPER_DIR / "figures"
TAB_DIR = PAPER_DIR / "tables"
FIG_DIR.mkdir(parents=True, exist_ok=True)
TAB_DIR.mkdir(parents=True, exist_ok=True)For figures, save vector PDFs directly into the paper. Removing the creation timestamp means a figure that didn't actually change produces an identical file, so Git won't report a pointless change:
import matplotlib.pyplot as plt
from src.paths import FIG_DIR
fig, ax = plt.subplots(figsize=(3.25, 2.4)) # match your template's column width
...
fig.savefig(FIG_DIR / "coverage.pdf", bbox_inches="tight",
metadata={"CreationDate": None})For tables, write a LaTeX fragment rather than a full document:
from src.paths import TAB_DIR
tex = df.to_latex(index=False, escape=True, float_format="%.3f")
(TAB_DIR / "results.tex").write_text(tex)and pull both into the paper as usual:
\includegraphics[width=\linewidth]{figures/coverage}
...
\input{tables/results}For numbers quoted in the text, the same idea works. Have your code write LaTeX macros so the prose never goes stale:
(TAB_DIR / "numbers.tex").write_text(
f"\\newcommand{{\\coverageNinetyFive}}{{{cov95:.1f}\\%}}\n"
)% preamble
\input{tables/numbers}
% body
Our intervals achieve \coverageNinetyFive{} empirical coverage.Day-to-Day Workflow
Sending new results to the paper
After rerunning your experiments, commit and push from inside paper/:
python scripts/make_figures.py
cd paper
git pull # pick up any text edits pushed from Overleaf first
git add figures tables
git commit -m "Regenerate figures from my-experiments@$(git -C .. rev-parse --short HEAD)"
git pushThen, in Overleaf, open Menu → GitHub and click Pull GitHub changes into Overleaf.
The commit message above includes the short hash of your experiment repository. Since the two repositories aren't linked, this message is your record of which code produced which figure. Running git log -- figures/coverage.pdf inside paper/ will answer "where did this figure come from?" months later.
Automating it with a Makefile
Once the steps above feel familiar, wrap them in a single command:
PAPER := paper
SHA := $(shell git rev-parse --short HEAD)$(shell git diff --quiet || echo -dirty)
.PHONY: figures paper-sync
figures:
python scripts/make_figures.py
paper-sync: figures
git -C $(PAPER) pull --rebase
git -C $(PAPER) add figures tables
git -C $(PAPER) diff --cached --quiet || \
git -C $(PAPER) commit -m "Regenerate figures from my-experiments@$(SHA)"
git -C $(PAPER) push
@echo "Now click 'Pull GitHub changes into Overleaf' in the Overleaf GitHub menu."Now make paper-sync regenerates everything, skips the commit if nothing actually changed, marks the commit -dirty if your experiment code had uncommitted changes, and reminds you to pull in Overleaf.
When co-authors edit on Overleaf
Before regenerating figures locally, push any Overleaf edits out to GitHub (Menu → GitHub → Push Overleaf changes to GitHub) so that the git pull in paper/ picks them up.
Avoiding conflicts
Conflicts are rare if every file has exactly one "owner":
figures/ and tables/ belong to your code. Never edit them by hand on Overleaf; the next regeneration will silently overwrite your changes. Change the formatting in the code that generates them instead.
.tex and .bib files belong to people. Write them on Overleaf (or locally, then push), and always pull before you push figures.
Why Not a Git Submodule?
Git submodules are the "official" way to put one repository inside another, so it is natural to ask why this workflow avoids them.
| Gitignored Clone | Git Submodule | Git Subtree | |
|---|---|---|---|
| Experiment repo records the paper version | No | Yes (a pointer) | Yes (full history) |
| Extra commit in experiment repo per paper change | No | Yes | Yes |
| Paper history mixed into code history | No | No | Yes |
| Special clone instructions for collaborators | No | --recurse-submodules |
No |
| Link between figure and code version | Commit message | Automatic | Automatic |
A submodule gives you exact, automatic provenance, but at a price: every change to the paper, including every typo fix a co-author pushes from Overleaf, needs a matching "bump the submodule" commit in your experiment repository. For a paper that is edited daily, that noise usually isn't worth it. The gitignored clone trades automatic provenance for a scripted commit message, which is plenty in practice.
Variations
Skip GitHub with Overleaf's Git integration: Overleaf Premium also lets you clone a project directly (git clone https://git.overleaf.com/<project-id>, using a Git token generated in Account Settings). Clone that into the gitignored directory instead, and a git push lands in Overleaf immediately with no pull button. The catch is that you no longer get a GitHub copy of the paper for issues, reviews, or backups.
Generating figures on a cluster: Clone the paper repository on the cluster as well and run make paper-sync there. Alternatively, point PAPER_DIR at a scratch directory on the cluster, copy the outputs back to your laptop, and push from there.
Common Pitfalls
| Things to Watch Out For |
|---|
|
Note: This is just one of several ways to connect your code and your paper. If the manual pull button bothers you, try the Git integration variation above; if you want airtight provenance, a submodule may be worth the extra commits.