]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
xgettext: Add an option to opt-out of the use of git for the POT-Creation-Date.
authorBruno Haible <bruno@clisp.org>
Fri, 2 May 2025 14:06:45 +0000 (16:06 +0200)
committerBruno Haible <bruno@clisp.org>
Fri, 2 May 2025 14:24:13 +0000 (16:24 +0200)
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.

NEWS
autogen.sh
gettext-tools/doc/xgettext.texi
gettext-tools/src/xgettext.c

diff --git a/NEWS b/NEWS
index e15cab81eab5b07dc6a189abbda6147e26b41171..5784392fd9f24184803cb955905925bc7317500d 100644 (file)
--- 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
 
index da1221afad91a59ffbd6aac5b08c4939d5a9e32f..d16a2a8fff778ba46f3a4d866de098f82c67dd93 100755 (executable)
@@ -238,6 +238,7 @@ if ! $skip_gnulib; then
     sigpipe
     sigprocmask
     spawn-pipe
+    stat-time
     stdio-h
     stdlib-h
     stpcpy
index 21306489f9c51da0e4be0b05cf383485fa8190eb..ed4ad0908bd3c9279dbee9ad8509de5e0fbc3c23 100644 (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
index 1274a7d814509afeac39b5561ebb19016152ebd3..e9bc7555af566ed59f1c79971d78a7f144e6b218 100644 (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;