Skip to content

Commit 73dab76

Browse files
author
Edward Thomson
authored
Merge pull request libgit2#3861 from libgit2/ethomson/refresh_objects
odb: freshen existing objects when writing
2 parents d2794b0 + 27051d4 commit 73dab76

File tree

9 files changed

+220
-3
lines changed

9 files changed

+220
-3
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ v0.24 + 1
6363
* `git_diff_file` now includes an `id_abbrev` field that reflects the
6464
number of nibbles set in the `id` field.
6565

66+
* `git_odb_backend` now has a `freshen` function pointer. This optional
67+
function pointer is similar to the `exists` function, but it will update
68+
a last-used marker. For filesystem-based object databases, this updates
69+
the timestamp of the file containing the object, to indicate "freshness".
70+
If this is `NULL`, then it will not be called and the `exists` function
71+
will be used instead.
72+
6673
v0.24
6774
-------
6875

include/git2/sys/odb_backend.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ struct git_odb_backend {
8383
git_odb_writepack **, git_odb_backend *, git_odb *odb,
8484
git_transfer_progress_cb progress_cb, void *progress_payload);
8585

86+
/**
87+
* "Freshens" an already existing object, updating its last-used
88+
* time. This occurs when `git_odb_write` was called, but the
89+
* object already existed (and will not be re-written). The
90+
* underlying implementation may want to update last-used timestamps.
91+
*
92+
* If callers implement this, they should return `0` if the object
93+
* exists and was freshened, and non-zero otherwise.
94+
*/
95+
int (* freshen)(git_odb_backend *, const git_oid *);
96+
8697
/**
8798
* Frees any resources held by the odb (including the `git_odb_backend`
8899
* itself). An odb backend implementation must provide this function.

src/fileops.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,19 @@ int git_futils_cp(const char *from, const char *to, mode_t filemode)
837837
return cp_by_fd(ifd, ofd, true);
838838
}
839839

840+
int git_futils_touch(const char *path, time_t *when)
841+
{
842+
struct p_timeval times[2];
843+
int ret;
844+
845+
times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL);
846+
times[0].tv_usec = times[1].tv_usec = 0;
847+
848+
ret = p_utimes(path, times);
849+
850+
return (ret < 0) ? git_path_set_error(errno, path, "touch") : 0;
851+
}
852+
840853
static int cp_link(const char *from, const char *to, size_t link_size)
841854
{
842855
int error = 0;

src/fileops.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,12 @@ extern int git_futils_cp(
184184
const char *to,
185185
mode_t filemode);
186186

187+
/**
188+
* Set the files atime and mtime to the given time, or the current time
189+
* if `ts` is NULL.
190+
*/
191+
extern int git_futils_touch(const char *path, time_t *when);
192+
187193
/**
188194
* Flags that can be passed to `git_futils_cp_r`.
189195
*

src/odb.c

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,10 @@ void git_odb_free(git_odb *db)
654654
GIT_REFCOUNT_DEC(db, odb_free);
655655
}
656656

657-
static int odb_exists_1(git_odb *db, const git_oid *id, bool only_refreshed)
657+
static int odb_exists_1(
658+
git_odb *db,
659+
const git_oid *id,
660+
bool only_refreshed)
658661
{
659662
size_t i;
660663
bool found = false;
@@ -673,6 +676,44 @@ static int odb_exists_1(git_odb *db, const git_oid *id, bool only_refreshed)
673676
return (int)found;
674677
}
675678

679+
static int odb_freshen_1(
680+
git_odb *db,
681+
const git_oid *id,
682+
bool only_refreshed)
683+
{
684+
size_t i;
685+
bool found = false;
686+
687+
for (i = 0; i < db->backends.length && !found; ++i) {
688+
backend_internal *internal = git_vector_get(&db->backends, i);
689+
git_odb_backend *b = internal->backend;
690+
691+
if (only_refreshed && !b->refresh)
692+
continue;
693+
694+
if (b->freshen != NULL)
695+
found = !b->freshen(b, id);
696+
else if (b->exists != NULL)
697+
found = b->exists(b, id);
698+
}
699+
700+
return (int)found;
701+
}
702+
703+
static int odb_freshen(git_odb *db, const git_oid *id)
704+
{
705+
assert(db && id);
706+
707+
if (odb_freshen_1(db, id, false))
708+
return 1;
709+
710+
if (!git_odb_refresh(db))
711+
return odb_freshen_1(db, id, true);
712+
713+
/* Failed to refresh, hence not found */
714+
return 0;
715+
}
716+
676717
int git_odb_exists(git_odb *db, const git_oid *id)
677718
{
678719
git_odb_object *object;
@@ -1131,7 +1172,7 @@ int git_odb_write(
11311172
assert(oid && db);
11321173

11331174
git_odb_hash(oid, data, len, type);
1134-
if (git_odb_exists(db, oid))
1175+
if (odb_freshen(db, oid))
11351176
return 0;
11361177

11371178
for (i = 0; i < db->backends.length && error < 0; ++i) {
@@ -1257,7 +1298,7 @@ int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream)
12571298

12581299
git_hash_final(out, stream->hash_ctx);
12591300

1260-
if (git_odb_exists(stream->backend->odb, out))
1301+
if (odb_freshen(stream->backend->odb, out))
12611302
return 0;
12621303

12631304
return stream->finalize_write(stream, out);

src/odb_loose.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,23 @@ static int loose_backend__write(git_odb_backend *_backend, const git_oid *oid, c
918918
return error;
919919
}
920920

921+
static int loose_backend__freshen(
922+
git_odb_backend *_backend,
923+
const git_oid *oid)
924+
{
925+
loose_backend *backend = (loose_backend *)_backend;
926+
git_buf path = GIT_BUF_INIT;
927+
int error;
928+
929+
if (object_file_name(&path, backend, oid) < 0)
930+
return -1;
931+
932+
error = git_futils_touch(path.ptr, NULL);
933+
git_buf_free(&path);
934+
935+
return error;
936+
}
937+
921938
static void loose_backend__free(git_odb_backend *_backend)
922939
{
923940
loose_backend *backend;
@@ -975,6 +992,7 @@ int git_odb_backend_loose(
975992
backend->parent.exists = &loose_backend__exists;
976993
backend->parent.exists_prefix = &loose_backend__exists_prefix;
977994
backend->parent.foreach = &loose_backend__foreach;
995+
backend->parent.freshen = &loose_backend__freshen;
978996
backend->parent.free = &loose_backend__free;
979997

980998
*backend_out = (git_odb_backend *)backend;

src/odb_pack.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020

2121
#include "git2/odb_backend.h"
2222

23+
/* re-freshen pack files no more than every 2 seconds */
24+
#define FRESHEN_FREQUENCY 2
25+
2326
struct pack_backend {
2427
git_odb_backend parent;
2528
git_vector packs;
@@ -363,6 +366,28 @@ static int pack_backend__read_header(
363366
return git_packfile_resolve_header(len_p, type_p, e.p, e.offset);
364367
}
365368

369+
static int pack_backend__freshen(
370+
git_odb_backend *backend, const git_oid *oid)
371+
{
372+
struct git_pack_entry e;
373+
time_t now;
374+
int error;
375+
376+
if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0)
377+
return error;
378+
379+
now = time(NULL);
380+
381+
if (e.p->last_freshen > now - FRESHEN_FREQUENCY)
382+
return 0;
383+
384+
if ((error = git_futils_touch(e.p->pack_name, &now)) < 0)
385+
return error;
386+
387+
e.p->last_freshen = now;
388+
return 0;
389+
}
390+
366391
static int pack_backend__read(
367392
void **buffer_p, size_t *len_p, git_otype *type_p,
368393
git_odb_backend *backend, const git_oid *oid)
@@ -560,6 +585,7 @@ static int pack_backend__alloc(struct pack_backend **out, size_t initial_size)
560585
backend->parent.refresh = &pack_backend__refresh;
561586
backend->parent.foreach = &pack_backend__foreach;
562587
backend->parent.writepack = &pack_backend__writepack;
588+
backend->parent.freshen = &pack_backend__freshen;
563589
backend->parent.free = &pack_backend__free;
564590

565591
*out = backend;

src/pack.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ struct git_pack_file {
102102

103103
git_pack_cache bases; /* delta base cache */
104104

105+
time_t last_freshen; /* last time the packfile was freshened */
106+
105107
/* something like ".git/objects/pack/xxxxx.pack" */
106108
char pack_name[GIT_FLEX_ARRAY]; /* more */
107109
};

tests/odb/freshen.c

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#include "clar_libgit2.h"
2+
#include "odb.h"
3+
#include "posix.h"
4+
5+
static git_repository *repo;
6+
static git_odb *odb;
7+
8+
void test_odb_freshen__initialize(void)
9+
{
10+
repo = cl_git_sandbox_init("testrepo.git");
11+
cl_git_pass(git_repository_odb(&odb, repo));
12+
}
13+
14+
void test_odb_freshen__cleanup(void)
15+
{
16+
git_odb_free(odb);
17+
cl_git_sandbox_cleanup();
18+
}
19+
20+
#define LOOSE_STR "hey\n"
21+
#define LOOSE_ID "1385f264afb75a56a5bec74243be9b367ba4ca08"
22+
#define LOOSE_FN "13/85f264afb75a56a5bec74243be9b367ba4ca08"
23+
24+
void test_odb_freshen__loose_object(void)
25+
{
26+
git_oid expected_id, id;
27+
struct stat before, after;
28+
struct p_timeval old_times[2];
29+
30+
cl_git_pass(git_oid_fromstr(&expected_id, LOOSE_ID));
31+
32+
old_times[0].tv_sec = 1234567890;
33+
old_times[0].tv_usec = 0;
34+
old_times[1].tv_sec = 1234567890;
35+
old_times[1].tv_usec = 0;
36+
37+
/* set time to way back */
38+
cl_must_pass(p_utimes("testrepo.git/objects/" LOOSE_FN, old_times));
39+
cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_FN, &before));
40+
41+
cl_git_pass(git_odb_write(&id, odb, LOOSE_STR, CONST_STRLEN(LOOSE_STR),
42+
GIT_OBJ_BLOB));
43+
cl_assert_equal_oid(&expected_id, &id);
44+
cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_FN, &after));
45+
46+
cl_assert(before.st_atime < after.st_atime);
47+
cl_assert(before.st_mtime < after.st_mtime);
48+
}
49+
50+
#define PACKED_STR "Testing a readme.txt\n"
51+
#define PACKED_ID "6336846bd5c88d32f93ae57d846683e61ab5c530"
52+
#define PACKED_FN "pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack"
53+
54+
void test_odb_freshen__packed_object(void)
55+
{
56+
git_oid expected_id, id;
57+
struct stat before, after;
58+
struct p_timeval old_times[2];
59+
60+
cl_git_pass(git_oid_fromstr(&expected_id, PACKED_ID));
61+
62+
old_times[0].tv_sec = 1234567890;
63+
old_times[0].tv_usec = 0;
64+
old_times[1].tv_sec = 1234567890;
65+
old_times[1].tv_usec = 0;
66+
67+
/* set time to way back */
68+
cl_must_pass(p_utimes("testrepo.git/objects/pack/" PACKED_FN, old_times));
69+
cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &before));
70+
71+
/* ensure that packfile is freshened */
72+
cl_git_pass(git_odb_write(&id, odb, PACKED_STR,
73+
CONST_STRLEN(PACKED_STR), GIT_OBJ_BLOB));
74+
cl_assert_equal_oid(&expected_id, &id);
75+
cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after));
76+
77+
cl_assert(before.st_atime < after.st_atime);
78+
cl_assert(before.st_mtime < after.st_mtime);
79+
80+
memcpy(&before, &after, sizeof(struct stat));
81+
82+
/* ensure that the pack file is not freshened again immediately */
83+
cl_git_pass(git_odb_write(&id, odb, PACKED_STR,
84+
CONST_STRLEN(PACKED_STR), GIT_OBJ_BLOB));
85+
cl_assert_equal_oid(&expected_id, &id);
86+
cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after));
87+
88+
cl_assert(before.st_atime == after.st_atime);
89+
cl_assert(before.st_atime_nsec == after.st_atime_nsec);
90+
cl_assert(before.st_mtime == after.st_mtime);
91+
cl_assert(before.st_mtime_nsec == after.st_mtime_nsec);
92+
}
93+

0 commit comments

Comments
 (0)