]> git.ipfire.org Git - thirdparty/git.git/commitdiff
odb: support setting mtime when writing objects
authorPatrick Steinhardt <ps@pks.im>
Fri, 17 Jul 2026 09:32:13 +0000 (11:32 +0200)
committerJunio C Hamano <gitster@pobox.com>
Sun, 19 Jul 2026 02:03:48 +0000 (19:03 -0700)
The function `force_object_loose()` is used to loosen packed objects
before repacking. It passes the pack's mtime along so that the newly
written loose object inherits the same timestamp. This matters for
object pruning, which uses the mtime to determine whether an object is
old enough to be pruned.

In a subsequent commit, `force_object_loose()` will be converted to use
the generic `odb_source_write_object()` interface instead of calling
`write_loose_object()` directly. But the generic interface doesn't yet
support setting a specific mtime, which makes it impossible to implement
the logic as of now.

Prepare for the change by introducing a new `mtime` parameter to this
function that we plumb through the stack. If set, the backends are
instructed to set the object's mtime accordingly. If unset, the backends
are expected to use the current time instead.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/pack-objects.c
object-file.c
object-file.h
odb.c
odb/source-files.c
odb/source-inmemory.c
odb/source-loose.c
odb/source-packed.c
odb/source.h
read-cache.c
t/unit-tests/u-odb-inmemory.c

index ea5eab4cf841bfc33c7d181d05c7e3a1c268b4ec..e64a96f1a727ec45e97a56d888717050b672cc92 100644 (file)
@@ -4642,7 +4642,7 @@ static void loosen_unused_packed_objects(void)
                            !has_sha1_pack_kept_or_nonlocal(&oid) &&
                            !loosened_object_can_be_discarded(&oid, p->mtime)) {
                                if (force_object_loose(the_repository->objects->sources,
-                                                      &oid, p->mtime))
+                                                      &oid, &p->mtime))
                                        die(_("unable to force loose object"));
                                loosened_objects_nr++;
                        }
index 9ca14f484dbd5dd4430f352ca7ca6cb8907a6c8a..5b075309506334389cf63e4725ed68d8011bfe86 100644 (file)
@@ -67,9 +67,17 @@ const char *odb_loose_path(struct odb_source_loose *loose,
 }
 
 /* Returns 1 if we have successfully freshened the file, 0 otherwise. */
-static int freshen_file(const char *fn)
+static int freshen_file(const char *fn, const time_t *mtime)
 {
-       return !utime(fn, NULL);
+       struct utimbuf times, *timesp = NULL;
+
+       if (mtime) {
+               times.actime = *mtime;
+               times.modtime = *mtime;
+               timesp = &times;
+       }
+
+       return !utime(fn, timesp);
 }
 
 /*
@@ -79,11 +87,12 @@ static int freshen_file(const char *fn)
  * either does not exist on disk, or has a stale mtime and may be subject to
  * pruning).
  */
-int check_and_freshen_file(const char *fn, int freshen)
+int check_and_freshen_file(const char *fn, int freshen,
+                          const time_t *mtime)
 {
        if (access(fn, F_OK))
                return 0;
-       if (freshen && !freshen_file(fn))
+       if (freshen && !freshen_file(fn, mtime))
                return 0;
        return 1;
 }
@@ -706,7 +715,7 @@ static int end_loose_object_common(struct odb_source_loose *loose,
 int write_loose_object(struct odb_source_loose *loose,
                       const struct object_id *oid, char *hdr,
                       int hdrlen, const void *buf, unsigned long len,
-                      time_t mtime, unsigned flags)
+                      const time_t *mtime, unsigned flags)
 {
        int fd, ret;
        unsigned char compressed[4096];
@@ -751,9 +760,11 @@ int write_loose_object(struct odb_source_loose *loose,
        close_loose_object(loose, fd, tmp_file.buf);
 
        if (mtime) {
-               struct utimbuf utb;
-               utb.actime = mtime;
-               utb.modtime = mtime;
+               struct utimbuf utb = {
+                       .actime = *mtime,
+                       .modtime = *mtime,
+               };
+
                if (utime(tmp_file.buf, &utb) < 0 &&
                    !(flags & ODB_WRITE_OBJECT_SILENT))
                        warning_errno(_("failed utime() on %s"), tmp_file.buf);
@@ -883,7 +894,7 @@ cleanup:
 }
 
 int force_object_loose(struct odb_source *source,
-                      const struct object_id *oid, time_t mtime)
+                      const struct object_id *oid, const time_t *mtime)
 {
        struct odb_source_files *files = odb_source_files_downcast(source);
        const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
index 08aafcda0df67bac77485e26be9474235fcc4ed3..9fd540afb6c6e7f4745b0c0aa59b5f707674555b 100644 (file)
@@ -99,7 +99,8 @@ int format_object_header(char *str, size_t size, enum object_type type,
                         size_t objsize);
 
 int force_object_loose(struct odb_source *source,
-                      const struct object_id *oid, time_t mtime);
+                      const struct object_id *oid,
+                      const time_t *mtime);
 
 /**
  * With in-core object data in "buf", rehash it to make sure the
@@ -137,10 +138,11 @@ void hash_object_file(const struct git_hash_algo *algo, const void *buf,
 int write_loose_object(struct odb_source_loose *loose,
                       const struct object_id *oid, char *hdr,
                       int hdrlen, const void *buf, unsigned long len,
-                      time_t mtime, unsigned flags);
+                      const time_t *mtime, unsigned flags);
 
 /* Helper to check and "touch" a file */
-int check_and_freshen_file(const char *fn, int freshen);
+int check_and_freshen_file(const char *fn, int freshen,
+                          const time_t *mtime);
 
 /*
  * Open the loose object at path, check its hash, and return the contents,
diff --git a/odb.c b/odb.c
index bfeca76f4e4af95a819a68debb9286a0ab0f89f7..dabd481f57dbc4a3acf011d00372bda25d7c56fc 100644 (file)
--- a/odb.c
+++ b/odb.c
@@ -738,7 +738,7 @@ int odb_pretend_object(struct object_database *odb,
                return 0;
 
        return odb_source_write_object(odb->inmemory_objects,
-                                      buf, len, type, oid, NULL, 0);
+                                      buf, len, type, oid, NULL, NULL, 0);
 }
 
 void *odb_read_object(struct object_database *odb,
@@ -829,7 +829,7 @@ int odb_freshen_object(struct object_database *odb,
        struct odb_source *source;
        odb_prepare_alternates(odb);
        for (source = odb->sources; source; source = source->next)
-               if (odb_source_freshen_object(source, oid))
+               if (odb_source_freshen_object(source, oid, NULL))
                        return 1;
        return 0;
 }
@@ -1024,7 +1024,7 @@ int odb_write_object_ext(struct object_database *odb,
        }
 
        return odb_source_write_object(odb->sources, buf, len, type,
-                                      oid, compat_oid_p, flags);
+                                      oid, compat_oid_p, NULL, flags);
 }
 
 int odb_write_object_stream(struct object_database *odb,
index 06dfc8dd78aecb3a9583c59a6437f9bd3a731085..4df4e1af6cf76b6d730530979d4ade1e157c9871 100644 (file)
@@ -150,11 +150,12 @@ out:
 }
 
 static int odb_source_files_freshen_object(struct odb_source *source,
-                                          const struct object_id *oid)
+                                          const struct object_id *oid,
+                                          const time_t *mtime)
 {
        struct odb_source_files *files = odb_source_files_downcast(source);
-       if (odb_source_freshen_object(&files->packed->base, oid) ||
-           odb_source_freshen_object(&files->loose->base, oid))
+       if (odb_source_freshen_object(&files->packed->base, oid, mtime) ||
+           odb_source_freshen_object(&files->loose->base, oid, mtime))
                return 1;
        return 0;
 }
@@ -164,11 +165,12 @@ static int odb_source_files_write_object(struct odb_source *source,
                                         enum object_type type,
                                         const struct object_id *oid,
                                         const struct object_id *compat_oid,
+                                        const time_t *mtime,
                                         enum odb_write_object_flags flags)
 {
        struct odb_source_files *files = odb_source_files_downcast(source);
        return odb_source_write_object(&files->loose->base, buf, len, type,
-                                      oid, compat_oid, flags);
+                                      oid, compat_oid, mtime, flags);
 }
 
 static int odb_source_files_write_object_stream(struct odb_source *source,
index 963d5203178d97f8203a4e41ce72869bdf1015f1..3e71611b8e00710ee793e8ffb9e8d31c03c23bed 100644 (file)
@@ -232,6 +232,7 @@ static int odb_source_inmemory_write_object(struct odb_source *source,
                                            enum object_type type,
                                            const struct object_id *oid,
                                            const struct object_id *compat_oid UNUSED,
+                                           const time_t *mtime UNUSED,
                                            enum odb_write_object_flags flags UNUSED)
 {
        struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
@@ -286,7 +287,7 @@ static int odb_source_inmemory_write_object_stream(struct odb_source *source,
        hash_object_file(source->odb->repo->hash_algo, data, total_read, OBJ_BLOB, oid);
 
        ret = odb_source_inmemory_write_object(source, data, len, OBJ_BLOB, oid,
-                                              NULL, 0);
+                                              NULL, NULL, 0);
        if (ret < 0)
                goto out;
 
@@ -296,7 +297,8 @@ out:
 }
 
 static int odb_source_inmemory_freshen_object(struct odb_source *source,
-                                             const struct object_id *oid)
+                                             const struct object_id *oid,
+                                             const time_t *mtime UNUSED)
 {
        struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
        if (find_cached_object(inmemory, oid))
index 04af1a54a3f63120271d3e7adf198b2996ba4627..520a30157cf0d6499ccc46da039e8d8f2561784e 100644 (file)
@@ -574,12 +574,13 @@ out:
 }
 
 static int odb_source_loose_freshen_object(struct odb_source *source,
-                                          const struct object_id *oid)
+                                          const struct object_id *oid,
+                                          const time_t *mtime)
 {
        struct odb_source_loose *loose = odb_source_loose_downcast(source);
        static struct strbuf path = STRBUF_INIT;
        odb_loose_path(loose, &path, oid);
-       return !!check_and_freshen_file(path.buf, 1);
+       return !!check_and_freshen_file(path.buf, 1, mtime);
 }
 
 static int odb_source_loose_write_object(struct odb_source *source,
@@ -587,6 +588,7 @@ static int odb_source_loose_write_object(struct odb_source *source,
                                         enum object_type type,
                                         const struct object_id *oid,
                                         const struct object_id *compat_oid,
+                                        const time_t *mtime,
                                         enum odb_write_object_flags flags)
 {
        struct odb_source_loose *loose = odb_source_loose_downcast(source);
@@ -595,7 +597,7 @@ static int odb_source_loose_write_object(struct odb_source *source,
 
        hdrlen = format_object_header(hdr, sizeof(hdr), type, len);
 
-       if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, 0, flags))
+       if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, mtime, flags))
                return -1;
 
        if (compat_oid)
index f7f17064472f073eee95e6369ae4a418d8250d80..5e5da9bc547f9a2f39f9f88b1ecf525b357ce42e 100644 (file)
@@ -507,18 +507,26 @@ static int odb_source_packed_find_abbrev_len(struct odb_source *source,
 }
 
 static int odb_source_packed_freshen_object(struct odb_source *source,
-                                           const struct object_id *oid)
+                                           const struct object_id *oid,
+                                           const time_t *mtime)
 {
        struct odb_source_packed *packed = odb_source_packed_downcast(source);
+       struct utimbuf times, *timesp = NULL;
        struct pack_entry e;
 
+       if (mtime) {
+               times.actime = *mtime;
+               times.modtime = *mtime;
+               timesp = &times;
+       }
+
        if (!find_pack_entry(packed, oid, &e))
                return 0;
        if (e.p->is_cruft)
                return 0;
        if (e.p->freshened)
                return 1;
-       if (utime(e.p->pack_name, NULL))
+       if (utime(e.p->pack_name, timesp))
                return 0;
        e.p->freshened = 1;
 
@@ -531,6 +539,7 @@ static int odb_source_packed_write_object(struct odb_source *source UNUSED,
                                          enum object_type type UNUSED,
                                          const struct object_id *oid UNUSED,
                                          const struct object_id *compat_oid UNUSED,
+                                         const time_t *mtime UNUSED,
                                          unsigned flags UNUSED)
 {
        return error("packed backend cannot write objects");
index c4e94c9d0d4615d0c8589e53cc19cae1faa86961..fc04dd5cda880046a0ce89fcd015f45235903c32 100644 (file)
@@ -190,7 +190,8 @@ struct odb_source {
         * has been freshened.
         */
        int (*freshen_object)(struct odb_source *source,
-                             const struct object_id *oid);
+                             const struct object_id *oid,
+                             const time_t *mtime);
 
        /*
         * This callback is expected to persist the given object into the
@@ -208,6 +209,7 @@ struct odb_source {
                            enum object_type type,
                            const struct object_id *oid,
                            const struct object_id *compat_oid,
+                           const time_t *mtime,
                            enum odb_write_object_flags flags);
 
        /*
@@ -403,9 +405,10 @@ static inline int odb_source_find_abbrev_len(struct odb_source *source,
  * not exist.
  */
 static inline int odb_source_freshen_object(struct odb_source *source,
-                                           const struct object_id *oid)
+                                           const struct object_id *oid,
+                                           const time_t *mtime)
 {
-       return source->freshen_object(source, oid);
+       return source->freshen_object(source, oid, mtime);
 }
 
 /*
@@ -418,10 +421,11 @@ static inline int odb_source_write_object(struct odb_source *source,
                                          enum object_type type,
                                          const struct object_id *oid,
                                          const struct object_id *compat_oid,
+                                         const time_t *mtime,
                                          enum odb_write_object_flags flags)
 {
        return source->write_object(source, buf, len, type, oid,
-                                   compat_oid, flags);
+                                   compat_oid, mtime, flags);
 }
 
 /*
index 3510b49edf70bea03604e35a4d59630e95734796..c67930177f7639793fa6b02a0d2b67140ea5c8fe 100644 (file)
@@ -2342,7 +2342,7 @@ unmap:
  */
 static void freshen_shared_index(const char *shared_index, int warn)
 {
-       if (!check_and_freshen_file(shared_index, 1) && warn)
+       if (!check_and_freshen_file(shared_index, 1, NULL) && warn)
                warning(_("could not freshen shared index '%s'"), shared_index);
 }
 
index 28a69fc24477c43062841f91cbf760a5c0e14433..ddf2db5c811fb8ad9fb00446cd6602c121384e69 100644 (file)
@@ -45,7 +45,7 @@ static void cl_assert_write_object(struct odb_source_inmemory *source,
        size_t content_len = strlen(content);
        hash_object_file(repo.hash_algo, content, content_len, type, oid);
        cl_must_pass(odb_source_write_object(&source->base, content, content_len,
-                                            type, oid, NULL, 0));
+                                            type, oid, NULL, NULL, 0));
 }
 
 void test_odb_inmemory__initialize(void)
@@ -256,11 +256,11 @@ void test_odb_inmemory__freshen_object(void)
        const char *end;
 
        cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
-       cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid), 0);
+       cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid, NULL), 0);
 
        cl_assert_write_object(source, "foobar", OBJ_BLOB, &written_oid);
        cl_assert_equal_i(odb_source_freshen_object(&source->base,
-                                                   &written_oid), 1);
+                                                   &written_oid, NULL), 1);
 
        odb_source_free(&source->base);
 }