14

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?

  • 1
    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
13

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.

| improve this answer | |
  • Is it correct that catchfile is currently a no-go with lualatex? – Ivan Kapitonov Dec 11 '18 at 0:47
  • Sorry, I've never been using lualatex yet. – CL. Dec 11 '18 at 6: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 3 '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 5 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 7 at 9:09
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}
| improve this answer | |
  • is there an available scratch file handle for read operations in LaTeX, avoiding a \newread here? – user4686 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? – user4686 Oct 16 '18 at 14:04
1

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.
| improve this answer | |

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.