#!/usr/bin/env perl # SPDX-License-Identifier: GPL-3.0-or-later # Modules and pragmas {{{ use strict; use warnings; use autodie; use feature qw(say); use utf8; use open qw(:std :utf8); binmode STDOUT, ':encoding(UTF-8)'; use IPC::System::Simple qw(capturex); use Getopt::Long; use Pod::Usage; # Get the path where this script is located {{{ my $dirpath; BEGIN { use File::Basename qw(fileparse); use Cwd qw(abs_path); $dirpath = ( fileparse( abs_path($0) ) )[1]; } # }}} use Locale::Maketext::Simple ( Path => $dirpath . 'po/', Decode => 1, ); # }}} # Optional parameters {{{ my $print_author = 0; my $commit_count = 1; my $width = 0; my $lang = undef; my $version = 0; my $help = 0; my $man = 0; my $revision_range = 0; my ( $git_user, $git_repo, $git_commit_address ); GetOptions( 'author' => \$print_author, 'startcommit=s' => \$commit_count, 'width=s' => \$width, 'git-c-add=s' => \$git_commit_address, 'user=s' => \$git_user, 'repo=s' => \$git_repo, 'lang=s' => \$lang, 'version' => \$version, 'help|?' => \$help, 'man' => \$man, 'range=s' => \$revision_range, ); pod2usage( -verbose => 99, -sections => [ qw(Synopsis Options) ] ) if $help; pod2usage( -verbose => 2, -exitstatus => 0, ) if $man; pod2usage( -verbose => 99, -sections => 'Version' ) if $version; my $git_remote = ( split /\n/xms, capturex(qw(git remote -v)) )[0]; loc_lang($lang) if ( defined $lang ); say '%% This file was generated by the script latex-git-log'; my $git_command_commit_msg = '%s'; if ( (defined $git_user and defined $git_repo) or $git_commit_address ) { unless ( defined $git_commit_address ) { $git_commit_address = "https://github.com/$+{user}/$+{repo}/commit"; } $git_command_commit_msg = '%H & %s'; say '%% Base git commit URL: ' . $git_commit_address; } if ($width) { $width = "p{${width}cm}"; } else { $width = 'l'; } # }}} # LaTeX template {{{ say '\begin{tabular}{lp{12cm}} \label{tabular:legend:git-log} \textbf{' . loc('acronym') . '} & \textbf{' . loc('meaning') . '} \\\\ V & \texttt{' . loc('version') . '} \\\\ ' . loc('tag') . ' & ' . loc('\texttt{git tag}') . ' \\\\ ' . loc('MF') . ' & ' . loc('Number of \texttt{modified files}.') . ' \\\\ ' . loc('AL') . ' & ' . loc('Number of \texttt{added lines}.') . ' \\\\ ' . loc('DL') . ' & ' . loc('Number of \texttt{deleted lines}.') . ' \\\\ \end{tabular} \bigskip \iflanguage{ngerman}{\shorthandoff{"}}{}'; if ($print_author) { say "\\begin{longtable}{|rllllrrr|}"; } else { say "\\begin{longtable}{|rll${width}rrr|}"; } say '\hline \multicolumn{1}{|c}{\textbf{' . loc('V') . '}} & \multicolumn{1}{c}{\textbf{' . loc('tag') . '}}'; say '& \multicolumn{1}{c}{\textbf{' . loc('author') . '}}' if $print_author; say '& \multicolumn{1}{c}{\textbf{' . loc('date') . '}} & \multicolumn{1}{c}{\textbf{' . loc('commit message') . '}} & \multicolumn{1}{c}{\textbf{' . loc('MF') . '}} & \multicolumn{1}{c}{\textbf{' . loc('AL') . '}} & \multicolumn{1}{c|}{\textbf{' . loc('DL') . '}} \\\\ \hline \endhead '; if ($print_author) { say '\hline \multicolumn{8}{|r|}{\longtableendfoot} \\\\ \hline'; } else { say '\hline \multicolumn{7}{|r|}{\longtableendfoot} \\\\ \hline'; } say '\endfoot \hline% \hline \endlastfoot '; # }}} # Get version history from git log {{{ # git log --pretty=format:'%ai' # git log --date=short --pretty=format:'%ad' my @lines; my @git_command = qw(git log --date=short --shortstat); if ($print_author) { push( @git_command, qq(--pretty=format:%H & %an NoTinAuthorFiled& %ad & $git_command_commit_msg) ); } else { push( @git_command, qq(--pretty=format:%H %ad & $git_command_commit_msg) ); } if ($revision_range) { push( @git_command, qq($revision_range) ); } @lines = reverse capturex(@git_command); # }}} # Get tags {{{ my @tags_commits = capturex( 'git', 'log', '--tags', '--no-walk', '--pretty=\'%H %d\'' ); my %commit_tags; # The key will be a SHA1 commit hash and the value a comma separated list of all tags. for (@tags_commits) { chomp; m/\A'([^ ]+)\s*\(([^)]+)\)'\z/xms; my $c_hash = $1; my $tag = $2; $tag =~ s/tag: //g; if ( defined $commit_tags{$c_hash} ) { $commit_tags{$c_hash} .= ", $tag"; } else { $commit_tags{$c_hash} = $tag; } } # }}} # Helper function {{{ sub latex_escape { # Source: http://ommammatips.blogspot.de/2011/01/perl-function-for-latex-escape.html my $paragraph = shift; # Replace a \ with $\backslash$ # This is made more complicated because the dollars will be escaped # by the subsequent replacement. Easiest to add \backslash # now and then add the dollars $paragraph =~ s/\\/\\backslash/g; # Must be done after escape of \ since this command adds latex escapes # Replace characters that can be escaped $paragraph =~ s/([\$\#&%_{}])/\\$1/g; # Replace ^ characters with \^{} so that $^F works okay $paragraph =~ s/(\^)/\\$1\{\}/g; # Replace tilde (~) with \texttt{\~{}} # $paragraph =~ s/~/\\texttt\{\\~\{\}\}/g; $paragraph =~ s/~/\\~\{\}/g; # Now add the dollars around each \backslash $paragraph =~ s/(\\backslash)/\$$1\$/g; return $paragraph; } ## end sub latex_escape # }}} # Loop over all commits {{{ my $which_line = 0; my @changes; for (@lines) { next if /\A\Z/xms; chomp; if ( m/^([0-9a-f]{40})\s/g and !$which_line) { $which_line ^= 1; ## additional toggle for merge commits @changes = ( "-", "-", "-" ); } if ($which_line) { s/\A([0-9a-f]{40})\s//xms or die "Did not match the commit hash\n"; my $tags = exists $commit_tags{$1} ? $commit_tags{$1} : q(); my $date_author = ''; my $c_msg = ''; if ($print_author) { /(?:& )(.*?)NoTinAuthorFiled(& .*? &) (.*)/; $date_author = latex_escape($1) . $2; $c_msg = $3; } else { /(.*? &) (.*)/; $date_author = $1; $c_msg = $2; } if ( defined $git_commit_address ) { $c_msg =~ /(.*?) & (.*)/; $c_msg = sprintf '\\href{%s/%s}{%s}', $git_commit_address, $1, latex_escape($2); } else { $c_msg = latex_escape($c_msg); } say "\\hline $commit_count & $tags & $date_author $c_msg & " . join( ' & ', @changes ) . ' \\\\'; $commit_count++; } ## end if ($which_line) else { @changes = ( 0, 0, 0 ); /(\d+) files? changed/ and $changes[0] = $1; /(\d+) insertions?/ and $changes[1] = $1; /(\d+) deletions?/ and $changes[2] = $1; } $which_line ^= 1; ## toggle bit } ## end for (@lines) say '\end{longtable}'; # }}} __END__ # Documentation {{{ =encoding UTF-8 =head1 NAME latex-git-log - Generates the version history of a git project as LaTeX source code. =head1 Synopsis latex-git-log [options] Options: --author set this if you want the author included --startcommit set the start value of count commit --width set the width in cm of the commit message field in the LaTeX table --git-c-add set an base URL to link to a commit --user set a github user to derive the base URL --repo set a github repository to derive the base URL --lang language of the legend and all strings in the output --version, -v print version of this script --help brief help message --man full documentation --range specify a git revision range =head1 Options =over 8 =item B<--width> Set the width in cm of the commit message field in the LaTeX table. If this parameter is not set then the table is not vertically limited. That means that if you have a very long commit message then the table will probably not fit on the page and you will get a "Overfull" error message from TeX. In this case you should specify the width of the column containing the commit messages. I normally use something like --width=14 for DIN A4 in landscape. =item B<--git-c-add> Set an base URL to link to a commit. This script will automatically try to use the base URL for github. =item B<--lang> Set the language of the legend and all strings in the output. By default the language of your system is used but you can overwrite this with this parameter. Currently this script only supports English and German. If you need a translation to another language then you can either create a .po file or I can register this project on one of those websites for online translation. =back =head1 Example You can use it like this: latex-git-log --width=6 --lang=en > example-output.tex =head1 Dependencies =head2 Of this script This module requires these other modules and libraries: IPC::System::Simple Locale::Maketext::Simple Everything else should already be installed. =head2 To compile the output The table is using the B package and the links to a web resource for each commit use the \href macro from B. So these two packages have to be loaded. Furthermore you need to defined the macro B<\longtableendfoot> which will be expanded on the bottom of every page if the table will be continued on the next page. You can defined it to a localized message to inform the reader that this table is not complete and will be continued. =head1 Description B will output the entire version history as table written in LaTeX if it is executed within a git repository. It is intended that you redirect the standard output of this script to a file which can then be included from your main LaTeX document. Because the table can be very large you might want to put the thing on a landscape page. =head1 Version 1.0.0 =head1 Author Robin Schneider =head1 Development CTAN: https://ctan.org/pkg/latex-git-log Source code repository: https://github.com/ypid/typesetting/tree/master/scripts/latex-git-log Please report bugs and feature requests at https://github.com/ypid/typesetting/issues =head1 License and Copyright Copyright (C) 2012-2013,2017,2019 Robin Schneider This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Dieses Programm ist Freie Software: Sie können es unter den Bedingungen der GNU General Public License, wie von der Free Software Foundation, Version 3 der Lizenz oder (nach Ihrer Option) jeder späteren veröffentlichten Version, weiterverbreiten und/oder modifizieren. Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. Siehe die GNU General Public License für weitere Details. Sie sollten eine Kopie der GNU General Public License zusammen mit diesem Programm erhalten haben. Wenn nicht, siehe . =cut # }}}