23

I use Git to track changes on my TEX files. When producing a PDF, it would be nice to include the current branch and commit ID somewhere in the document (e.g. on the title page or in a footer).

I know that the package gitinfo2 exists but I'd rather avoid adding any hooks to Git. Besides, I am aware of this question but I would prefer a solution that does not require enabling \write18.

Given that the name of the current branch can be found in .git/HEAD and the current commit ID is stored in .git/refs/heads/[name of branch], is it possible to embed this information in the final document?

David Carlisle
  • 757,742
CL.
  • 901
  • 2
    I'm writing this self-answered Q&A because it took me a while to figure this out. Hope it will help others in the future. – CL. Oct 16 '18 at 11:10
  • despite the question saying you wanted an alternative to gitinfo, there is a big overlap so I added that tag – David Carlisle Oct 16 '18 at 11:34

4 Answers4

18

As stated in the question, the branch name can be extracted from .git/HEAD and given [branch name], the commit ID can be found in .git/refs/heads/[branch name].

The package catchfile provides the command \CatchFileDef, which allows us to read .git/HEAD into a macro. As HEAD has no file extension, MiKTeX users have to add a trailing dot to the file name:

\CatchFileDef{\headfull}{.git/HEAD.}{}

This assigns something like ref: refs/heads/master to \headfull. As this string has a trailing whitespace character, we use \StrGobbleRight from the xstring package to trim it:1

\StrGobbleRight{\headfull}{1}[\head]

In order to extract only the branch name (master in the example) from this string, we can use \StrBehind:

\StrBehind[2]{\head}{/}[\branch]

This saves the branch name in \branch. Finally, we can use \CatchFileDef again, to save the commit ID in \commit:

\CatchFileDef{\commit}{.git/refs/heads/\branch.}{}

There are some edge cases where .git/refs/heads/\branch. does not exist: After running git pack-refs (which is a side effect of git gc --aggressive, for example), the heads are packed into the file .git/packed-refs instead of individual branchname files. As a workaround, check if the file exists before trying to read it:

\IfFileExists{.git/refs/heads/\branch.}{%
    \CatchFileDef{\commit}{.git/refs/heads/\branch.}{}}{%
    \newcommand{\commit}{\dots~(in \emph{packed-refs})}}

As a fallback, this creates the (not-so-useful) output "... (in packed-refs)" instead of a commit ID – but this only lasts until the next commit, when the file heads/branchname is recreated for the affected branch. (A more ambitious workaround could parse packed-refs, of course.)

Full MWE:

\documentclass{article}

\usepackage{xstring} \usepackage{catchfile}

\CatchFileDef{\headfull}{.git/HEAD.}{} \StrGobbleRight{\headfull}{1}[\head] \StrBehind[2]{\head}{/}[\branch] \IfFileExists{.git/refs/heads/\branch.}{% \CatchFileDef{\commit}{.git/refs/heads/\branch.}{}}{% \newcommand{\commit}{\dots~(in \emph{packed-refs})}}

\begin{document} This revision: \texttt{\commit} on branch \texttt{\branch}. \end{document}

Sample output:

This revision: d92dc1386e48e04ceecb85461ed3b232146e6a32 on branch master.


1 Using the last optional argument of \StrGobbleRight (name) to assign the trimmed string to a macro (\head) is necessary to allow further manipulation of the string using the xstring functions – see here for a discussion.

CL.
  • 901
  • Is it correct that catchfile is currently a no-go with lualatex? – Ivan Kapitonov Dec 11 '18 at 00:47
  • Sorry, I've never been using lualatex yet. – CL. Dec 11 '18 at 06:39
  • I am using catchfile successfully with lualatex right now. The documentation states: [2007/11/11 v1.2] Use of package pdftexcmds for LuaTEX support. – Alex Povel Apr 03 '19 at 15:30
  • I really like this solution. would you mind editing in a way to escape underscores in branch names? ot should I pose a new question for this? – ingli Jul 05 '20 at 12:39
  • 1
    @ingli I didn't test this yet, but I think you should be able to modify the approach here. – CL. Jul 07 '20 at 09:09
  • Is it me, or with this solution the document revision will always be one commit behind the one where the document currently lives? – SeF Jul 28 '21 at 11:53
  • 1
    @SeF It (still) works for me. Would you mind sharing a reproducible example, maybe even a minimal Github repository? – CL. Jul 28 '21 at 14:15
  • @CL. thanks for the reply. It is just about how the pipeline works: (1) compile the latex source code: the pdf is tagged with hash 1234 and my-branch (2) git commit -am "another commit" (now the git hash of the latest commit is 5678) (3) git pull my-branch (Now) If I am not wrong, on the repo the latest hash is 5678, while on the pdf document on the repo the hash is still 1234. – SeF Jul 28 '21 at 14:21
  • 1
    @SeF Yes, I think I see your problem. But I'd say this is "natural": You need to commit etc. before you compile. … … Ok, now I think I understand: You're commiting the PDF as well? Agreed, this won't work. But IMHO, you should "gitignore" the PDF anyway and only commit the source files. And then the problem should become a non-issue automatically. ;-) – CL. Jul 28 '21 at 14:43
6

A no-package approach (tested on mac os, modify it for Windows regarding extra needed dot perhaps) based on @CL.'s answer.

\documentclass{article}

\newcommand\dotGitHEAD{} \newcommand\branch{} \newcommand\commit{}

\makeatletter\let\myfilehandle@inputcheck\makeatother

\openin\myfilehandle=.git/HEAD\relax

\begingroup\endlinechar-1 \global\read\myfilehandle to \dotGitHEAD \endgroup \closein\myfilehandle

\newcommand\GetBranch{} \def\GetBranch ref: refs/heads/#1\relax{\renewcommand{\branch}{#1}}

\expandafter\GetBranch\dotGitHEAD\relax

\openin\myfilehandle=.git/refs/heads/\branch\relax

\begingroup\endlinechar-1 \global\read\myfilehandle to \commit \endgroup \closein\myfilehandle

\begin{document} \branch+++

\commit+++

This revision: \texttt{\commit} on branch \texttt{\branch}. \end{document}

enter image description here



Package getcommit:

\ProvidesPackage{getcommit}[2018/10/16 get current commit and branch (JFB)]

@ifundefined{branch}{} {\PackageWarning{getcommit}{ATTENTION!^^J @spaces@spaces\string\branch\space macro was already defined. Overwritten.}} @ifundefined{commit}{} {\PackageWarning{getcommit}{ATTENTION!^^J @spaces@spaces\string\commit\space macro was already defined. Overwritten.}}

\openin@inputcheck=.git/HEAD\relax

\begingroup\endlinechar-1 \global\read@inputcheck to \getcommit@HEAD \endgroup \closein@inputcheck

\def\getcommit@GetBranch ref: refs/heads/#1\relax{\def\branch{#1}}

\expandafter\getcommit@GetBranch\getcommit@HEAD\relax

\openin@inputcheck=.git/refs/heads/\branch\relax

\begingroup\endlinechar-1 \global\read@inputcheck to \commit \endgroup \closein@inputcheck \endinput

Example of use:

\documentclass{article}

\usepackage{getcommit}

\begin{document} +++\branch+++

+++\commit+++

%\frenchspacing This revision: \texttt{\commit} on branch \texttt{\branch}. \end{document}

  • is there an available scratch file handle for read operations in LaTeX, avoiding a \newread here? –  Oct 16 '18 at 12:16
  • 1
    There is \@inputcheck used by the kernel in \IfFileExists. – Ulrike Fischer Oct 16 '18 at 12:29
  • Quite nice and it works on windows. But imho for a real package it should try to find the info also in parent folders (imho gitinfo2 looks 4 levels up by default). – Ulrike Fischer Oct 16 '18 at 13:44
  • But miktex really needs the period at the end ... – Ulrike Fischer Oct 16 '18 at 13:54
  • @UlrikeFischer is there any test allowing package to know that this is executed under MikTeX? –  Oct 16 '18 at 14:04
  • I think there was somewhere a test but can't remember now. But the syntax with the dot works for me also with texlive. So if it fails on linux one would need only to test for windows (ifplatform). – Ulrike Fischer Oct 16 '18 at 14:10
  • @UlrikeFischer it fails on mac os x and I expect also on linux. quick look at ifplatform seems to indicate it does not need shell escape for \ifwindows so this is good, but add a dependency to my tiny package... –  Oct 16 '18 at 14:13
  • yes this slightly different handling of unusual file names is rather a pain. – Ulrike Fischer Oct 16 '18 at 14:22
  • a package should also have options [commitmacro=\commit, branchmacro=\branch] for example. –  Oct 16 '18 at 15:48
2

We can use \input instead \read primitive:

\def\gitdir{.git/}
\def\readgitcommitA #1 {\def\gitcommit{#1}}
\def\readgitbranchA #1 #2 {\def\gitbranch{#2}}
\def\readgitcommit{\expandafter\readgitbranchA \input \gitdir HEAD
  \expandafter\readgitcommitA \input \gitdir\gitbranch\relax}

\readgitcommit

Now, the commit ID is in "\gitcommit" macro.

wipet
  • 74,238
2

If you are using latexmk, you can use an alternative approach to intialize gitinfo2, that does not require git hooks (https://github.com/rbarazzutti/gitinfo2-latexmk).

Setup:

$ git submodule add https://github.com/rbarazzutti/gitinfo2-latexmk gitinfo2-latexmk
$ echo "do './gitinfo2-latexmk/gitinfo2.pm';" >> latexmkrc

Usage:

\usepackage[mark]{gitinfo2}