]> 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:06:45 +0000 (16:06 +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 236f65db268b8c6cb77a29ddd78d15e98acfbc70..5c96e00248b551aec6f8f5fd2e5ac40b747df981 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -29,7 +29,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 df68aedef83ca915c9488e25d3531a43751aa7b5..4bcf4bc2bb05c6af3d49735393f4091c6744c212 100755 (executable)
@@ -246,6 +246,7 @@ if ! $skip_gnulib; then
     sigpipe
     sigprocmask
     spawn-pipe
+    stat-time
     stdio-h
     stdlib-h
     stpcpy
index ccb1f42e8be117bbe7407f68e4244d101b047eb7..59d593204371f068cc64bf3c12dc1c488208441c 100644 (file)
@@ -710,6 +710,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 0c1471f8decf23b4981d84a92e9f60a21f09b463..81daa18eb5796fefc2c8d48621ec07d33a03b086 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"
@@ -184,6 +185,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;
 
@@ -281,6 +285,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 },
@@ -731,6 +736,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 */
@@ -1291,6 +1300,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"));
@@ -2222,6 +2235,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)
 {
@@ -2230,8 +2296,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;