xgettext: Add an option to opt-out of the use of git for the POT-Creation-Date.

Reported by Serhii Tereshchenko at <https://savannah.gnu.org/bugs/?66865>.

* autogen.sh (GNULIB_MODULES_TOOLS_FOR_SRC): Add stat-time.
* gettext-tools/src/xgettext.c: Include stat-time.h.
(xgettext_no_git): New variable.
(long_options): Add option '--no-git'.
(main): Handle the option --no-git.
(usage): Document the option --no-git.
(struct accumulator): New type, copied from gnulib/lib/vc-mtime.c.
(accumulate): New function, copied from gnulib/lib/vc-mtime.c.
(max_mtime_without_git): New function, based on gnulib/lib/vc-mtime.c.
(finalize_header): Conditionally invoke max_mtime_without_git instead of
max_vc_mtime.
* gettext-tools/doc/xgettext.texi: Document the option --no-git.
* NEWS: Mention the change.
This commit is contained in:
Bruno Haible 2025-05-02 16:06:45 +02:00
parent 8ce6f7d45e
commit 59782d1f8c
4 changed files with 90 additions and 3 deletions

3
NEWS
View File

@ -3,7 +3,8 @@ Version 0.24.1 - May 2025
* Bug fixes:
- Fix bad interactions between autoreconf and autopoint.
- xgettext: Creating the POT file of a package under Git version control
is now faster.
is now faster. Also, the use of Git can be turned off by specifying
the option '--no-git'.
Version 0.24 - February 2025

View File

@ -238,6 +238,7 @@ if ! $skip_gnulib; then
sigpipe
sigprocmask
spawn-pipe
stat-time
stdio-h
stdlib-h
stpcpy

View File

@ -690,6 +690,25 @@ options at different times are guaranteed to produce the same results.
Note that using this option will lead to an error if the resulting file
would not entirely be in ASCII.
@item --no-git
@opindex --no-git@r{, @code{xgettext} option}
Don't use the @code{git} program
to produce a reproducible @samp{POT-Creation-Date} field in the output.
Use this option, for speed, if
your project has a very long @code{Git} history
(hundreds of thousands of commits)
or you are specifying thousands of input files.
By default, @code{xgettext} determines the @samp{POT-Creation-Date} as
the maximum version-controlled modification time
among all the given input files.
With this option, you can specify that it should instead use
the maximum modification time (time stamp on disk)
among all the given input files.
By ``version control'', here we mean the @code{Git} version control system.
@item --copyright-holder=@var{string}
@opindex --copyright-holder@r{, @code{xgettext} option}
Set the copyright holder in the output. @var{string} should be the

View File

@ -80,6 +80,7 @@
#include "msgl-ascii.h"
#include "msgl-ofn.h"
#include "xg-check.h"
#include "stat-time.h"
#include "vc-mtime.h"
#include "po-time.h"
#include "msgl-header.h"
@ -179,6 +180,9 @@ static catalog_output_format_ty output_syntax = &output_format_po;
/* If nonzero omit header with information about this run. */
int xgettext_omit_header;
/* If nonzero, don't use 'git' to compute a reproducible POT-Creation-Date. */
static int xgettext_no_git;
/* Be more verbose. */
int verbose = 0;
@ -271,6 +275,7 @@ static const struct option long_options[] =
{ "msgstr-prefix", optional_argument, NULL, 'm' },
{ "msgstr-suffix", optional_argument, NULL, 'M' },
{ "no-escape", no_argument, NULL, 'e' },
{ "no-git", no_argument, NULL, CHAR_MAX + 23 },
{ "no-location", no_argument, NULL, CHAR_MAX + 16 },
{ "no-wrap", no_argument, NULL, CHAR_MAX + 4 },
{ "omit-header", no_argument, &xgettext_omit_header, 1 },
@ -706,6 +711,10 @@ main (int argc, char *argv[])
string_list_append (&files_for_vc_mtime, optarg);
break;
case CHAR_MAX + 23: /* --no-git */
xgettext_no_git = true;
break;
default:
usage (EXIT_FAILURE);
/* NOTREACHED */
@ -1263,6 +1272,10 @@ Output details:\n"));
printf (_("\
--omit-header don't write header with 'msgid \"\"' entry\n"));
printf (_("\
--no-git don't use the git program to produce a\n\
reproducible 'POT-Creation-Date' field in the\n\
output.\n"));
printf (_("\
--copyright-holder=STRING set copyright holder in output\n"));
printf (_("\
--foreign-user omit FSF copyright in output for foreign user\n"));
@ -2167,6 +2180,59 @@ FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n");
return mp;
}
/* Accumulating mtimes. */
struct accumulator
{
bool has_some_mtimes;
struct timespec max_of_mtimes;
};
static void
accumulate (struct accumulator *accu, struct timespec mtime)
{
if (accu->has_some_mtimes)
{
/* Compute the maximum of accu->max_of_mtimes and mtime. */
if (accu->max_of_mtimes.tv_sec < mtime.tv_sec
|| (accu->max_of_mtimes.tv_sec == mtime.tv_sec
&& accu->max_of_mtimes.tv_nsec < mtime.tv_nsec))
accu->max_of_mtimes = mtime;
}
else
{
accu->max_of_mtimes = mtime;
accu->has_some_mtimes = true;
}
}
static int
max_mtime_without_git (struct timespec *max_of_mtimes,
size_t nfiles, const char * const *filenames)
{
if (nfiles == 0)
/* Invalid argument. */
abort ();
struct accumulator accu = { false };
/* Always use the file's time stamp. */
for (size_t n = 0; n < nfiles; n++)
{
struct stat statbuf;
if (stat (filenames[n], &statbuf) < 0)
return -1;
struct timespec mtime = get_stat_mtime (&statbuf);
accumulate (&accu, mtime);
}
/* Since nfiles > 0, we must have accumulated at least one mtime. */
if (!accu.has_some_mtimes)
abort ();
*max_of_mtimes = accu.max_of_mtimes;
return 0;
}
static void
finalize_header (msgdomain_list_ty *mdlp)
{
@ -2175,8 +2241,8 @@ finalize_header (msgdomain_list_ty *mdlp)
time_t stamp;
struct timespec max_of_mtimes;
if (files_for_vc_mtime.nitems > 0
&& max_vc_mtime (&max_of_mtimes,
files_for_vc_mtime.nitems, files_for_vc_mtime.item)
&& (xgettext_no_git ? max_mtime_without_git : max_vc_mtime)
(&max_of_mtimes, files_for_vc_mtime.nitems, files_for_vc_mtime.item)
== 0)
/* Use the maximum of the encountered mtimes. */
stamp = max_of_mtimes.tv_sec;