diff --git a/NEWS b/NEWS index e15cab81e..5784392fd 100644 --- a/NEWS +++ b/NEWS @@ -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 diff --git a/autogen.sh b/autogen.sh index da1221afa..d16a2a8ff 100755 --- a/autogen.sh +++ b/autogen.sh @@ -238,6 +238,7 @@ if ! $skip_gnulib; then sigpipe sigprocmask spawn-pipe + stat-time stdio-h stdlib-h stpcpy diff --git a/gettext-tools/doc/xgettext.texi b/gettext-tools/doc/xgettext.texi index 21306489f..ed4ad0908 100644 --- a/gettext-tools/doc/xgettext.texi +++ b/gettext-tools/doc/xgettext.texi @@ -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 diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index 1274a7d81..e9bc7555a 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -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 , 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;