{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreidcjiin45etgsluoa27xbdtnmuqfoknzu25bw4s6gbqrxvnw4n5xe",
    "uri": "at://did:plc:jhmdbcph6xja3hamtoi4kdy4/app.bsky.feed.post/3moyc7p7ory2o"
  },
  "canonicalUrl": "https://eirik.re/blog/paper-publishing-workflow/index",
  "description": "A homage to mise-en-place",
  "path": "/blog/paper-publishing-workflow/index",
  "publishedAt": "2026-02-21T21:16:35.000Z",
  "site": "at://did:plc:jhmdbcph6xja3hamtoi4kdy4/site.standard.publication/3moyb2g35kh2t",
  "tags": [
    "mise",
    "latex",
    "tinytex",
    "paper"
  ],
  "textContent": "> _A homage to mise-en-place_\n\n{{<callout context=\"tip\" title=\"TL;DR\" icon=\"rocket\">}}\n\nInstall a new project from\npaper-publishing-process with\n\n- Automatic compilation of main.pdf, review-<git-tag>.pdf and diff-<git-tag>.pdf\n- Git Tag-based versioning and changelog generation\n- CI/CD pipeline with compilation and new releases\n\n{{< video src=\"compile-all.mp4\" loop=\"true\" autoplay=\"true\" muted=\"true\" >}}\n\n{{</callout>}}\n\nA frustration from when I first started writing papers to be submitted to journals and\ngo through a review cycle was keeping track of which version was submitted at which\npoint in time. Which version should I respond to, which version should the updated\nversion be diffed against? This was a very manual process where the work of tracking\nwhat needed to be submitted in the latest revision sometimes felt as large as the\nwriting itself.\n\nFortunately, there are solutions.\n\nSetting Up Tools and Repo\n\nEverything should be able to live independently of the system you first develop on, so\nwe create a Git repo where all the files can live. In this guide we will make extensive\nuse of GitHub's workflow, so we create the repo there. I\ncalled mine paper-publishing-process, so let's clone it down:\n\nNext we need to be able to install all software, and for that we use\nmise. First we must install mise;\nvisit the website, or run the following command:\n\nThen we create the file mise.toml in the project root:\n\nWe need to give mise permission to use it, mise trust, before\ninstalling everything with mise install.\n\nIn the first commit I added a simple license file (MIT) and a .gitignore suited for\nTeX development.\n\n_→ See commit\nce3150c._\n\nWhile we're at it, let's add some files that help keep the repo up to a good standard in\nterms of formatting:\n\n- tex-fmt.toml: Formatting of .tex and .bib\n- .taplo.toml: Formatting of .toml\n- .yamlfmt.yml: Formatting of .yml\n- hk.pkl: Formatting and linting\n\nThis change also updates mise.toml and creates the file tex/main.tex.\n\n_→ See commit\nb161e41._\n\nWe can verify that everything so far is as it should be:\n\nAutomatic Compilation of TeX\n\nLocal Compilation with mise\n\nWe are now ready to work on the TeX files! We want to write in tex/main.tex, and every\ntime we save, the PDF should update automatically. We turn again to\nmise for this functionality.\n\n_→ See commit\ne7127b4._\n\n{{< details \"Motivation for mise tasks\" >}}\n\nIn the commit above we add a mise task called\n\"localize-bib-paths\". The motivation behind it is that the \"compile\" task (introduced a\nlittle later) will run it so that when developing locally all references come from the\nglobal master reference. This is useful in cases where you want to add a new reference\nto the TeX document that already exists in the master reference. If you use a modern\neditor it will provide completion suggestions, which would not be available in the\ngenerated reference files for _new_ references.\n\n{{< /details >}}\n\nWe can now run\n\nIt fails! This is due to the first, slightly special choice we make in this workflow.\n\nBibfish\n\nIn order to keep the .bib files in the repo as minimal as possible, they are all\ngenerated from a local \"master reference file\". This file is what the LOCAL_BIB_PATH\nvalue we added in mise.toml points to. This master reference is intended to be a file\nthat lives only locally, not in the Git repo, and should be able to supply references\nnot just to this project, but to all your TeX projects! Bibfish will then read it and\ngenerate a reference file based only on the references found in a given TeX file.\n\nBibfish supports most common citation formats (e.g. \\cite, \\citet, etc.), but can\neasily be extended with custom formats. In mise.toml we have added citeA, and also\nspecify cite, citet and citep\n(bibfish -c 'cite,citet,citep,citeA' -f main.tex {{ env.LOCAL_BIB_PATH }}.bib main.bib).\n\nLet's fix the error from before by\n\n1. Adding mise.local.toml to .gitignore\n2. Specifying a local bib file in mise.local.toml\n\n{{<callout context=\"caution\" title=\"Local bib file\" icon=\"alert-triangle\">}}\n\nNote that the local file's location must either be \"absolute\" (/home/user/the-file),\nor \"relative\" from the tex folder, and that it is specified without .bib.\n\n{{< /callout >}}\n\nI also added the local master reference here to illustrate with a minimal example, but\nnormally I would place it outside the repo. Also add\n\n_→ See commit\na149cdf._\n\nWhen we now try to compile, no error!\n\nBBL Files and References in Git\n\nLet's add some references.\n\nWe also create a general compile task in mise:\n\nWhen we now run mise watch compile a new file will be generated: tex/main.bbl. This\nis normally ignored, but we will need it in the future, so we remove it from\n.gitignore.\n\nIt contains a new .bbl file, and the .bib file has been updated with exactly one\nreference — the one we added in tex/main.tex!\n\n_→ See commit\n1a523ea._\n\nManaging TeX Packages with tlmgr\n\nWe use tinytex as the TeX distribution in this project. As\nthe name suggests it is much smaller than the more common TeX-live/TeX-live-full. This\nalso means we will more often find that packages are unavailable, but in return we get\nmore control. Let's use a handful of packages not found in TinyTeX.\n\n_→ See commit\nfe28c24 for updated\ntex/main.tex and mise.toml._\n\nIf you first update tex/main.tex with the contents from the commit above and have\nmise watch compile running it should fail, complaining that underscore is not\navailable. Then add the postinstall hook in mise.toml and run mise install.\n\nThis will ensure that various packages are installed via tlmgr. The advantage of this\nsetup is that many journals are very particular about which packages they accept. If you\nhave written a long manuscript that uses many packages and compiles fine locally because\nyou used TeX-live-full, it can quickly become difficult to figure out what needs to go\nif compilation on the journal's server crashes.\n\nLocal Development Workflow\n\nWe have now completed the workflow for local TeX files! Run mise watch compile, write\ndown all your good ideas, and every time tex/main.tex is saved the compilation will\nrun. Notice how tex/main.bib updates in the terminal window at the top right when I\nadd and remove references.\n\n{{< video src=\"compile.mp4\" loop=\"true\" autoplay=\"true\" muted=\"true\" >}}\n\nNext steps: automatic compilation on GitHub, support for versioning via Git, and\nroutines for responding to feedback from journals.\n\nCI/CD Pipeline on GitHub\n\nGit Commit Hooks\n\nSince we have tools for formatting the code as well as a working\nhk setup, we can easily set up Git commit hooks:\n\n{{<callout context=\"danger\" title=\"Pre-commit vs. mise run compile\" icon=\"alert-octagon\">}}\n\nSince mise run|watch compile converts all \\bibliography{...} to use the local master\nreference, while the pre-commit hook via hk converts them to use the generated bib\nfiles, a conflict will arise between them.\n\nEach time you create a commit it is therefore wise to stop mise watch compile, so that\nit does not overwrite before you have finished the commit.\n\n{{< /callout >}}\n\nAutomatic Building and Release\n\nTo set up CI/CD we create the file mise.ci.toml, as well as three files in\n.github/workflows and .mise/tasks:\n\n- .github/workflows/build.yml\n\n  Compiles all PDFs for every push to the main branch on GitHub.\n\n- .github/workflows/fix-bib-filepath.yml\n\n  An extra check ensuring the correct bib file is used to compile the PDFs.\n\n- .mise/tasks/package\n\n  Puts all files a journal needs into an archive, for easy uploading when submitting.\n\n_→ See commit\n93c8487 for the\ncontents of the files._\n\nThat commit actually failed in CI on GitHub. I had forgotten that all files in\n.mise/tasks must be executable (executable bit), meaning I needed to\n\n_→ See commit\n06262fd._\n\nBelow we see that CI/CD ran without errors and gave us a paper-assets artifact.\n\n{{<figure src=\"first-ci.png\" alt=\"First CI/CD run\" caption=\"First CI/CD run\" >}}\n\nVersioning and Replying to the Journal\n\nWe are now almost ready to submit the manuscript to a journal, but first we will set up\nversioning in Git that generates new releases with a changelog.\nGit-Cliff is a very flexible and powerful tool for exactly\nthis, and we configure it with cliff.toml. This file is completely general, with the\nexception of the last two lines, shown below. They indicate the owner of the repo and\nthe name of the repo.\n\nWe can now set a Git Tag on the latest commit to create our first version of the\nmanuscript!\n\n_→ See commit\nbfe67c6._\n\nAnd there we have it, our\nfirst release!\nIt got its name from what we tagged in the Git Tag (v1.0.0), a tag that is completely\narbitrary, but in this case follows the SemVer standard. We could just as well have used\nCalVer (e.g. vYYYY.MM.P) v2026.03.0, or something completely custom like rel1. We\ncan even switch standards; the next version can perfectly follow CalVer even if we used\nSemVer in the first release. As long as you stick to a consistent standard, use whatever\nfeels most natural.\n\n{{<figure src=\"first-release.png\" alt=\"First release\" caption=\"First release\" >}}\n\nAs an attachment (artifact) to the release we have both paper.zip and main.pdf. The\narchive paper.zip contains all files specified in .mise/tasks/package, and more\nspecifically in the Bash array files. It is useful to add all the files needed to\n_generate_ the PDF here, so that when uploading to the journal you can easily pick out\nall the files from the archive and upload them, instead of hunting through the repo for\nevery file you need.\n\n{{< details \"About Git Tag contents\" >}}\n\nIn the git tag command we specified the -a flag (annotation) and two -m flags.\nThey stand for message, but do not become part of the release text we see in the image\nabove, since that is generated by Git-Cliff. It will however be visible in Git Tags, see\nTags.\n\n{{< /details >}}\n\nReview Cycle and Revision\n\nReceiving and Setting Up a Revision\n\nEven if we were perfectly happy with the manuscript we will likely get a review back\nwith requests to improve certain passages. This is where the good work we put into Git\nand versioning really shines.\n\nWe create one final mise task: .mise/tasks/setup-revision. _→\nSee commit b490542\n(also adds diff.tex files to .github/workflows/build.yml)._\n\nLet's test it:\n\nA full six new files were generated! This includes two TeX files to serve as the\nresponse to reviewers and the diff between the previous Git Tag and the new manuscript,\nas well as four mise task files to compile the two TeX files. These depend on some TeX\ncode we also add:\n\n- tex/reviewresponse.cls: the class used by the review documents\n- tex/reviewresponse-extra.sty: extra TeX code that sets up formatting in the review\n  documents\n\n{{<callout context=\"note\" title=\"Wrong Git Tag\" icon=\"bulb\">}}\n\nIf at some point you think you are ready to submit the manuscript and create a Git Tag\nagainst the latest commit, but then later realize you need to make further changes, you\ncan easily move or delete Git Tags. If you do not move a misplaced Git Tag the\nsetup-revision task will not work as expected, since it would then compare the current\nmain.tex against a Git Tag pointing to files that were never submitted to the journal.\n\nGit Tags can be deleted with git tag --delete <git-tag>, and attached to a specific\ncommit with git tag -a <git-tag> <commit-hash> -m \"Message\".\n\n{{</callout>}}\n\nLet's now run mise watch compile!\n\n{{< video src=\"compile-all.mp4\" loop=\"true\" autoplay=\"true\" muted=\"true\" >}}\n\nSo what is actually happening in the video? The video starts right after I ran\nmise run setup-revision which generated the six files for \"diff\" and \"review\". First\nin the video mise watch compile is run, which then runs in the background for the\nentire video. This single command compiles main.pdf, review-v1.0.0.pdf and\ndiff-v1.0.0.pdf, and also watches for changes. We then open diff-v1.0.0.pdf and\nreview-v1.0.0.pdf, before making a change to main.tex. The change becomes\nimmediately visible in main.pdf and diff-v1.0.0.pdf. Finally we make a change in\nreview-v1.0.0.tex, which is equally immediately visible in review-v1.0.0.pdf.\n\nThe diff between the new main.tex/main.pdf and the one we submitted in version\nv1.0.0 can be generated without the file existing locally. Since we have a Git Tag we\nsimply refer to it, and compare against the file at that point in Git history. This is\nthe step where we depend on the .bbl files, which contain the compiled (exact)\nbibliography that was used to generate the PDFs. This is done in the generated create\ntasks with the command\n\n_→ See commit\n68d913f for the new\nfiles._\n\nSubmitting the Revised Manuscript\n\nLet's say we are now happy with our latest changes and are ready to submit again to the\njournal. We again set a Git Tag on the latest commit, push it and wait for the latest\nversion to be prepared for us in a neat format.\n\nSince I personally like CalVer in this context I switch to it now:\n\n{{<figure src=\"second-release.png\" alt=\"Second release, with archive (paper.zip) and the three PDFs\" caption=\"Second release, with archive (paper.zip) and the three PDFs\" >}}\n\nIf we now get further rounds of changes from the reviewers, we simply repeat the same\nprocess:\n\n1. mise run setup-revision\n2. mise watch compile: Make all changes to main.tex and review-<git-tag>.tex as we\n   see fit\n3. git commit ...\n4. git tag -a '...' -m '...'\n5. git push --tags\n\n{{<details \"Once more\">}}\n\nHere for example is the output from mise run setup-revision now after I have created\nv2026.03.0:\n\nAnd then mise run compile. Notice review-v1.0.0: skipping, newer revision exists,\nand the corresponding for \"diff\". Nothing is compiled unless it is truly necessary!\n\nAnd this is what the\nlatest release\nlooks like after the final necessary changes were made, ready to be published!\n\n{{</details>}}\n\nFinalizing and README\n\nThe repo now works fully to both easily prepare a manuscript for submission, and to\ncreate a response and diff/change document for any reviewers.\n\nBut to make the repo itself a little more inviting it is good practice to also create a\nREADME file. With all the releases we have created and their artifacts it is easy to\nlink to files at different stages of the process.\n\n_→ See commit\nb3cbc9d for the\nchanges made to README.md._\n\nFurther Extensions\n\nChangelog\n\nWe have already set up quite a lot of automation, but we can of course continue even\nfurther. Git-Cliff can be configured to write to a file, typically called\nCHANGELOG.md, so that in addition to having changes between versions in each release,\nthe entire changelog lives in that file.\n\nPR-Based Versioning\n\nYou can also set up PR-based (pull request) versioning that uses\nConventional commits to automatically\ngenerate new release proposals. This requires a slightly larger change to the\nconfiguration in GitHub CI/CD.\n\nPinning GitHub Actions Versions\n\nAs you may have noticed, all actions used in the GitHub workflow are specified with a\nlong hash, followed by a comment describing which version it points to. This is good\npractice because, as briefly mentioned earlier, Git Tags are \"mutable\"; they can be\nchanged and moved. This makes them a potential attack surface, and it is therefore safer\nto specify the commit hash, since it is \"immutable\", unchangeable.\n\nTo easily stay up to date with the latest versions you can use\npinact-action, but this will need a\nTOKEN with permissions to write to the .github/workflows files.\n\nEven More mise Magic\n\nWe have already established that mise-en-place is excellent software, and it can do a\nlittle more. Three commands that are useful for having a repo that is both easy to set\nup and has a well-documented flow:\n\nThe first command writes a file with locked versions adapted for installation on all\ncommon platforms. The second command generates a shell script that installs the same\nversion of mise-en-place currently in use, while the last command generates\ndocumentation for all tasks defined with mise-en-place.\n\n_→ See commit\n9c1fb30 for files\nadding functionality for pinning actions versions\nand mise bootstrapping._",
  "title": "Paper publishing workflow"
}