Skip to content

Commit

Permalink
vc-mtime: New module.
Browse files Browse the repository at this point in the history
* lib/vc-mtime.h: New file.
* lib/vc-mtime.c: New file.
* modules/vc-mtime: New file.
  • Loading branch information
bhaible committed Feb 24, 2025
1 parent 931d2b1 commit 701d20a
Show file tree
Hide file tree
Showing 4 changed files with 346 additions and 0 deletions.
7 changes: 7 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
2025-02-24 Bruno Haible <[email protected]>

vc-mtime: New module.
* lib/vc-mtime.h: New file.
* lib/vc-mtime.c: New file.
* modules/vc-mtime: New file.

2025-02-24 Bruno Haible <[email protected]>

nl_langinfo: Support abbreviated alternative month names on FreeBSD.
Expand Down
208 changes: 208 additions & 0 deletions lib/vc-mtime.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/* Return the version-control based modification time of a file.
Copyright (C) 2025 Free Software Foundation, Inc.
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 <https://www.gnu.org/licenses/>. */

/* Written by Bruno Haible <[email protected]>, 2025. */

#include <config.h>

/* Specification. */
#include "vc-mtime.h"

#include <stdlib.h>
#include <unistd.h>

#include <error.h>
#include "spawn-pipe.h"
#include "wait-process.h"
#include "execute.h"
#include "safe-read.h"
#include "xstrtol.h"
#include "stat-time.h"
#include "gettext.h"

#define _(msgid) dgettext ("gnulib", msgid)


/* Determines whether the specified file is under version control. */
static bool
git_vc_controlled (const char *filename)
{
/* Run "git ls-files FILENAME" and return true if the exit code is 0
and the output is non-empty. */
const char *argv[4];
pid_t child;
int fd[1];

argv[0] = "git";
argv[1] = "ls-files";
argv[2] = filename;
argv[3] = NULL;
child = create_pipe_in ("git", "git", argv, NULL, NULL,
DEV_NULL, true, true, false, fd);
if (child == -1)
return false;

/* Read the subprocess output, and test whether it is non-empty. */
size_t count = 0;
char c;

while (safe_read (fd[0], &c, 1) > 0)
count++;

close (fd[0]);

/* Remove zombie process from process list, and retrieve exit status. */
int exitstatus =
wait_subprocess (child, "git", false, true, true, false, NULL);
return (exitstatus == 0 && count > 0);
}

/* Determines whether the specified file is unmodified, compared to the
last version in version control. */
static bool
git_unmodified (const char *filename)
{
/* Run "git diff --quiet -- HEAD FILENAME"
(or "git diff --quiet HEAD FILENAME")
and return true if the exit code is 0.
The '--' option is for the case that the specified file was removed. */
const char *argv[7];
int exitstatus;

argv[0] = "git";
argv[1] = "diff";
argv[2] = "--quiet";
argv[3] = "--";
argv[4] = "HEAD";
argv[5] = filename;
argv[6] = NULL;
exitstatus = execute ("git", "git", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
return (exitstatus == 0);
}

/* Stores in *MTIME the time of last modification in version control of the
specified file, and returns 0.
Upon failure, it returns -1. */
static int
git_mtime (struct timespec *mtime, const char *filename)
{
/* Run "git log -1 --format=%ct -- FILENAME". It prints the time of last
modification, as the number of seconds since the Epoch.
The '--' option is for the case that the specified file was removed. */
const char *argv[7];
pid_t child;
int fd[1];

argv[0] = "git";
argv[1] = "log";
argv[2] = "-1";
argv[3] = "--format=%ct";
argv[4] = "--";
argv[5] = filename;
argv[6] = NULL;
child = create_pipe_in ("git", "git", argv, NULL, NULL,
DEV_NULL, true, true, false, fd);
if (child == -1)
return -1;

/* Retrieve its result. */
FILE *fp;
char *line;
size_t linesize;
size_t linelen;

fp = fdopen (fd[0], "r");
if (fp == NULL)
error (EXIT_FAILURE, errno, _("fdopen() failed"));

line = NULL; linesize = 0;
linelen = getline (&line, &linesize, fp);
if (linelen == (size_t)(-1))
{
error (0, 0, _("%s subprocess I/O error"), "git");
fclose (fp);
wait_subprocess (child, "git", true, false, true, false, NULL);
}
else
{
int exitstatus;

if (linelen > 0 && line[linelen - 1] == '\n')
line[linelen - 1] = '\0';

fclose (fp);

/* Remove zombie process from process list, and retrieve exit status. */
exitstatus =
wait_subprocess (child, "git", true, false, true, false, NULL);
if (exitstatus == 0)
{
char *endptr;
unsigned long git_log_time;
if (xstrtoul (line, &endptr, 10, &git_log_time, NULL) == LONGINT_OK
&& endptr == line + strlen (line))
{
mtime->tv_sec = git_log_time;
mtime->tv_nsec = 0;
free (line);
return 0;
}
}
}
free (line);
return -1;
}

int
vc_mtime (struct timespec *mtime, const char *filename)
{
static bool git_tested;
static bool git_present;

if (!git_tested)
{
/* Test for presence of git:
"git --version >/dev/null 2>/dev/null" */
const char *argv[3];
int exitstatus;

argv[0] = "git";
argv[1] = "--version";
argv[2] = NULL;
exitstatus = execute ("git", "git", argv, NULL, NULL,
false, false, true, true,
true, false, NULL);
git_present = (exitstatus == 0);
git_tested = true;
}

if (git_present
&& git_vc_controlled (filename)
&& git_unmodified (filename))
{
if (git_mtime (mtime, filename) == 0)
return 0;
}
struct stat statbuf;
if (stat (filename, &statbuf) == 0)
{
*mtime = get_stat_mtime (&statbuf);
return 0;
}
return -1;
}
97 changes: 97 additions & 0 deletions lib/vc-mtime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* Return the version-control based modification time of a file.
Copyright (C) 2025 Free Software Foundation, Inc.
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 <https://www.gnu.org/licenses/>. */

/* Written by Bruno Haible <[email protected]>, 2025. */

#ifndef _VC_MTIME_H
#define _VC_MTIME_H

/* Get struct timespec. */
#include <time.h>

/* The "version-controlled modification time" vc_mtime(F) of a file F
is defined as:
- If F is under version control and not modified locally:
the time of the last change of F in the version control system.
- Otherwise: The modification time of F on disk.
For now, the only VCS supported by this module is git. (hg and svn are
hardly in use any more.)
This has the properties that:
- Different users who have checked out the same git repo on different
machines, at different times, and not done local modifications,
get the same vc_mtime(F).
- If a user has modified F locally, the modification time of that file
counts.
- If that user then reverts the modification, they then again get the
same vc_mtime(F) as everyone else.
- Different users who have unpacked the same tarball (without .git
directory) on different machines, at different times, also get the same
vc_mtime(F) [but possibly a different one than when the .git directory
was present]. (Assuming a POSIX compliant file system.)
- When a user commits local modifications into git, this only increases
(not decreases) the vc_mtime(F).
The purpose of the version-controlled modification time is to produce a
reproducible timestamp(Z) of a file Z that depends on files X1, ..., Xn,
in such a way that
- timestamp(Z) is reproducible, that is, different users on different
machines get the same value.
- timestamp(Z) is related to reality. It's not just a dummy, like what
is suggested in <https://reproducible-builds.org/docs/timestamps/>.
- One can arrange for timestamp(Z) to respect the modification time
relations of a build system.
There are two uses of such a timestamp:
- It can be set as the modification time of file Z in a file system, or
- It can be embedded in Z, with the purpose of telling a user how old
the file Z is. For example, in PDF files or in generated documentation,
such a time is embedded in a special place.
The simplest example is a file Z that depends on files X1, ..., Xn.
Generally one will define
timestamp(Z) = max (vc_mtime(X1), ..., vc_mtime(Xn))
for an embedded timestamp, or
timestamp(Z) = max (vc_mtime(X1), ..., vc_mtime(Xn)) + 1 second
for a time stamp in a file system. The added second
1. accounts for fractional seconds in mtime(X1), ..., mtime(Xn),
2. allows for 'make' implementation that attempt to rebuild Z
if mtime(Z) == mtime(Xi).
A more complicated example is when there are intermediate built files, not
under version control. For example, if the build process produces
X1, X2 -> Y1
X3, X4 -> Y2
Y1, Y2, X5 -> Z
where Y1 and Y2 are intermediate built files, you should ignore the
mtime(Y1), mtime(Y2), and consider only the vc_mtime(X1), ..., vc_mtime(X5).
*/

#ifdef __cplusplus
extern "C" {
#endif

/* Determines the version-controlled modification time of FILENAME, stores it
in *MTIME, and returns 0.
Upon failure, it returns -1. */
extern int vc_mtime (struct timespec *mtime, const char *filename);

#ifdef __cplusplus
}
#endif

#endif /* _VC_MTIME_H */
34 changes: 34 additions & 0 deletions modules/vc-mtime
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Description:
Returns the version-control based modification time of a file.

Files:
lib/vc-mtime.h
lib/vc-mtime.c

Depends-on:
time-h
bool
spawn-pipe
wait-process
execute
safe-read
error
getline
xstrtol
stat-time
gettext-h
gnulib-i18n

configure.ac:

Makefile.am:
lib_SOURCES += vc-mtime.c

Include:
"vm-mtime.h"

License:
GPL

Maintainer:
Bruno Haible

0 comments on commit 701d20a

Please sign in to comment.