20

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?

2
  • 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, 2018 at 11:10
  • despite the question saying you wanted an alternative to gitinfo, there is a big overlap so I added that tag Oct 16, 2018 at 11:34

4 Answers 4

16

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.

9
  • Is it correct that catchfile is currently a no-go with lualatex? Dec 11, 2018 at 0:47
  • Sorry, I've never been using lualatex yet.
    – CL.
    Dec 11, 2018 at 6:39
  • 1
    @ingli I didn't test this yet, but I think you should be able to modify the approach here.
    – CL.
    Jul 7, 2020 at 9:09
  • 1
    @SeF It (still) works for me. Would you mind sharing a reproducible example, maybe even a minimal Github repository?
    – CL.
    Jul 28, 2021 at 14:15
  • 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, 2021 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}
9
  • is there an available scratch file handle for read operations in LaTeX, avoiding a \newread here?
    – user4686
    Oct 16, 2018 at 12:16
  • 1
    There is \@inputcheck used by the kernel in \IfFileExists. Oct 16, 2018 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). Oct 16, 2018 at 13:44
  • But miktex really needs the period at the end ... Oct 16, 2018 at 13:54
  • @UlrikeFischer is there any test allowing package to know that this is executed under MikTeX?
    – user4686
    Oct 16, 2018 at 14:04
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.
0

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}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.