Paper publishing workflow
A homage to mise-en-place
{{}}
Install a new project from paper-publishing-process with
- Automatic compilation of main.pdf, review-.pdf and diff-.pdf
- Git Tag-based versioning and changelog generation
- CI/CD pipeline with compilation and new releases
{{< video src="compile-all.mp4" loop="true" autoplay="true" muted="true" >}}
{{}}
A frustration from when I first started writing papers to be submitted to journals and go through a review cycle was keeping track of which version was submitted at which point in time. Which version should I respond to, which version should the updated version be diffed against? This was a very manual process where the work of tracking what needed to be submitted in the latest revision sometimes felt as large as the writing itself.
Fortunately, there are solutions.
Setting Up Tools and Repo
Everything should be able to live independently of the system you first develop on, so we create a Git repo where all the files can live. In this guide we will make extensive use of GitHub's workflow, so we create the repo there. I called mine paper-publishing-process, so let's clone it down:
Next we need to be able to install all software, and for that we use mise. First we must install mise; visit the website, or run the following command:
Then we create the file mise.toml in the project root:
We need to give mise permission to use it, mise trust, before installing everything with mise install.
In the first commit I added a simple license file (MIT) and a .gitignore suited for TeX development.
→ See commit ce3150c.
While we're at it, let's add some files that help keep the repo up to a good standard in terms of formatting:
- tex-fmt.toml: Formatting of .tex and .bib
- .taplo.toml: Formatting of .toml
- .yamlfmt.yml: Formatting of .yml
- hk.pkl: Formatting and linting
This change also updates mise.toml and creates the file tex/main.tex.
→ See commit b161e41.
We can verify that everything so far is as it should be:
Automatic Compilation of TeX
Local Compilation with mise
We are now ready to work on the TeX files! We want to write in tex/main.tex, and every time we save, the PDF should update automatically. We turn again to mise for this functionality.
→ See commit e7127b4.
{{< details "Motivation for mise tasks" >}}
In the commit above we add a mise task called "localize-bib-paths". The motivation behind it is that the "compile" task (introduced a little later) will run it so that when developing locally all references come from the global master reference. This is useful in cases where you want to add a new reference to the TeX document that already exists in the master reference. If you use a modern editor it will provide completion suggestions, which would not be available in the generated reference files for new references.
{{< /details >}}
We can now run
It fails! This is due to the first, slightly special choice we make in this workflow.
Bibfish
In order to keep the .bib files in the repo as minimal as possible, they are all generated from a local "master reference file". This file is what the LOCAL_BIB_PATH value we added in mise.toml points to. This master reference is intended to be a file that lives only locally, not in the Git repo, and should be able to supply references not just to this project, but to all your TeX projects! Bibfish will then read it and generate a reference file based only on the references found in a given TeX file.
Bibfish supports most common citation formats (e.g. \cite, \citet, etc.), but can easily be extended with custom formats. In mise.toml we have added citeA, and also specify cite, citet and citep (bibfish -c 'cite,citet,citep,citeA' -f main.tex {{ env.LOCAL_BIB_PATH }}.bib main.bib).
Let's fix the error from before by
- Adding mise.local.toml to .gitignore
- Specifying a local bib file in mise.local.toml
{{}}
Note that the local file's location must either be "absolute" (/home/user/the-file), or "relative" from the tex folder, and that it is specified without .bib.
{{< /callout >}}
I also added the local master reference here to illustrate with a minimal example, but normally I would place it outside the repo. Also add
→ See commit a149cdf.
When we now try to compile, no error!
BBL Files and References in Git
Let's add some references.
We also create a general compile task in mise:
When we now run mise watch compile a new file will be generated: tex/main.bbl. This is normally ignored, but we will need it in the future, so we remove it from .gitignore.
It contains a new .bbl file, and the .bib file has been updated with exactly one reference — the one we added in tex/main.tex!
→ See commit 1a523ea.
Managing TeX Packages with tlmgr
We use tinytex as the TeX distribution in this project. As the name suggests it is much smaller than the more common TeX-live/TeX-live-full. This also means we will more often find that packages are unavailable, but in return we get more control. Let's use a handful of packages not found in TinyTeX.
→ See commit fe28c24 for updated tex/main.tex and mise.toml.
If you first update tex/main.tex with the contents from the commit above and have mise watch compile running it should fail, complaining that underscore is not available. Then add the postinstall hook in mise.toml and run mise install.
This will ensure that various packages are installed via tlmgr. The advantage of this setup is that many journals are very particular about which packages they accept. If you have written a long manuscript that uses many packages and compiles fine locally because you used TeX-live-full, it can quickly become difficult to figure out what needs to go if compilation on the journal's server crashes.
Local Development Workflow
We have now completed the workflow for local TeX files! Run mise watch compile, write down all your good ideas, and every time tex/main.tex is saved the compilation will run. Notice how tex/main.bib updates in the terminal window at the top right when I add and remove references.
{{< video src="compile.mp4" loop="true" autoplay="true" muted="true" >}}
Next steps: automatic compilation on GitHub, support for versioning via Git, and routines for responding to feedback from journals.
CI/CD Pipeline on GitHub
Git Commit Hooks
Since we have tools for formatting the code as well as a working hk setup, we can easily set up Git commit hooks:
{{}}
Since mise run|watch compile converts all \bibliography{...} to use the local master reference, while the pre-commit hook via hk converts them to use the generated bib files, a conflict will arise between them.
Each time you create a commit it is therefore wise to stop mise watch compile, so that it does not overwrite before you have finished the commit.
{{< /callout >}}
Automatic Building and Release
To set up CI/CD we create the file mise.ci.toml, as well as three files in .github/workflows and .mise/tasks:
.github/workflows/build.yml
Compiles all PDFs for every push to the main branch on GitHub.
.github/workflows/fix-bib-filepath.yml
An extra check ensuring the correct bib file is used to compile the PDFs.
.mise/tasks/package
Puts all files a journal needs into an archive, for easy uploading when submitting.
→ See commit 93c8487 for the contents of the files.
That commit actually failed in CI on GitHub. I had forgotten that all files in .mise/tasks must be executable (executable bit), meaning I needed to
→ See commit 06262fd.
Below we see that CI/CD ran without errors and gave us a paper-assets artifact.
{{
Versioning and Replying to the Journal
We are now almost ready to submit the manuscript to a journal, but first we will set up versioning in Git that generates new releases with a changelog. Git-Cliff is a very flexible and powerful tool for exactly this, and we configure it with cliff.toml. This file is completely general, with the exception of the last two lines, shown below. They indicate the owner of the repo and the name of the repo.
We can now set a Git Tag on the latest commit to create our first version of the manuscript!
→ See commit bfe67c6.
And there we have it, our first release! It got its name from what we tagged in the Git Tag (v1.0.0), a tag that is completely arbitrary, but in this case follows the SemVer standard. We could just as well have used CalVer (e.g. vYYYY.MM.P) v2026.03.0, or something completely custom like rel1. We can even switch standards; the next version can perfectly follow CalVer even if we used SemVer in the first release. As long as you stick to a consistent standard, use whatever feels most natural.
{{
As an attachment (artifact) to the release we have both paper.zip and main.pdf. The archive paper.zip contains all files specified in .mise/tasks/package, and more specifically in the Bash array files. It is useful to add all the files needed to generate the PDF here, so that when uploading to the journal you can easily pick out all the files from the archive and upload them, instead of hunting through the repo for every file you need.
{{< details "About Git Tag contents" >}}
In the git tag command we specified the -a flag (annotation) and two -m flags. They stand for message, but do not become part of the release text we see in the image above, since that is generated by Git-Cliff. It will however be visible in Git Tags, see Tags.
{{< /details >}}
Review Cycle and Revision
Receiving and Setting Up a Revision
Even if we were perfectly happy with the manuscript we will likely get a review back with requests to improve certain passages. This is where the good work we put into Git and versioning really shines.
We create one final mise task: .mise/tasks/setup-revision. → See commit b490542 (also adds diff.tex files to .github/workflows/build.yml).
Let's test it:
A full six new files were generated! This includes two TeX files to serve as the response to reviewers and the diff between the previous Git Tag and the new manuscript, as well as four mise task files to compile the two TeX files. These depend on some TeX code we also add:
- tex/reviewresponse.cls: the class used by the review documents
- tex/reviewresponse-extra.sty: extra TeX code that sets up formatting in the review documents
{{}}
If at some point you think you are ready to submit the manuscript and create a Git Tag against the latest commit, but then later realize you need to make further changes, you can easily move or delete Git Tags. If you do not move a misplaced Git Tag the setup-revision task will not work as expected, since it would then compare the current main.tex against a Git Tag pointing to files that were never submitted to the journal.
Git Tags can be deleted with git tag --delete , and attached to a specific commit with git tag -a -m "Message".
{{}}
Let's now run mise watch compile!
{{< video src="compile-all.mp4" loop="true" autoplay="true" muted="true" >}}
So what is actually happening in the video? The video starts right after I ran mise run setup-revision which generated the six files for "diff" and "review". First in the video mise watch compile is run, which then runs in the background for the entire video. This single command compiles main.pdf, review-v1.0.0.pdf and diff-v1.0.0.pdf, and also watches for changes. We then open diff-v1.0.0.pdf and review-v1.0.0.pdf, before making a change to main.tex. The change becomes immediately visible in main.pdf and diff-v1.0.0.pdf. Finally we make a change in review-v1.0.0.tex, which is equally immediately visible in review-v1.0.0.pdf.
The diff between the new main.tex/main.pdf and the one we submitted in version v1.0.0 can be generated without the file existing locally. Since we have a Git Tag we simply refer to it, and compare against the file at that point in Git history. This is the step where we depend on the .bbl files, which contain the compiled (exact) bibliography that was used to generate the PDFs. This is done in the generated create tasks with the command
→ See commit 68d913f for the new files.
Submitting the Revised Manuscript
Let's say we are now happy with our latest changes and are ready to submit again to the journal. We again set a Git Tag on the latest commit, push it and wait for the latest version to be prepared for us in a neat format.
Since I personally like CalVer in this context I switch to it now:
{{
If we now get further rounds of changes from the reviewers, we simply repeat the same process:
- mise run setup-revision
- mise watch compile: Make all changes to main.tex and review-.tex as we see fit
- git commit ...
- git tag -a '...' -m '...'
- git push --tags
{{<details "Once more">}}
Here for example is the output from mise run setup-revision now after I have created v2026.03.0:
And then mise run compile. Notice review-v1.0.0: skipping, newer revision exists, and the corresponding for "diff". Nothing is compiled unless it is truly necessary!
And this is what the latest release looks like after the final necessary changes were made, ready to be published!
{{}}
Finalizing and README
The repo now works fully to both easily prepare a manuscript for submission, and to create a response and diff/change document for any reviewers.
But to make the repo itself a little more inviting it is good practice to also create a README file. With all the releases we have created and their artifacts it is easy to link to files at different stages of the process.
→ See commit b3cbc9d for the changes made to README.md.
Further Extensions
Changelog
We have already set up quite a lot of automation, but we can of course continue even further. Git-Cliff can be configured to write to a file, typically called CHANGELOG.md, so that in addition to having changes between versions in each release, the entire changelog lives in that file.
PR-Based Versioning
You can also set up PR-based (pull request) versioning that uses Conventional commits to automatically generate new release proposals. This requires a slightly larger change to the configuration in GitHub CI/CD.
Pinning GitHub Actions Versions
As you may have noticed, all actions used in the GitHub workflow are specified with a long hash, followed by a comment describing which version it points to. This is good practice because, as briefly mentioned earlier, Git Tags are "mutable"; they can be changed and moved. This makes them a potential attack surface, and it is therefore safer to specify the commit hash, since it is "immutable", unchangeable.
To easily stay up to date with the latest versions you can use pinact-action, but this will need a TOKEN with permissions to write to the .github/workflows files.
Even More mise Magic
We have already established that mise-en-place is excellent software, and it can do a little more. Three commands that are useful for having a repo that is both easy to set up and has a well-documented flow:
The first command writes a file with locked versions adapted for installation on all common platforms. The second command generates a shell script that installs the same version of mise-en-place currently in use, while the last command generates documentation for all tasks defined with mise-en-place.
→ See commit 9c1fb30 for files adding functionality for pinning actions versions and mise bootstrapping.
Discussion in the ATmosphere