Skip to content

Commit 83bcd3a

Browse files
committed
config_file: refresh all files if includes were modified
Currently, we only re-parse the top-level configuration file when it has changed itself. This can cause problems when an include is changed, as we were not updating all values correctly. Instead of conditionally reparsing only refreshed files, the logic becomes much clearer and easier to follow if we always re-parse the top-level configuration file when either the file itself or one of its included configuration files has changed on disk. This commit implements this logic. Note that this might impact performance in some cases, as we need to re-read all configuration files whenever any of the included files changed. It could increase performance to just re-parse include files which have actually changed, but this would compromise maintainability of the code without much gain. The only case where we will gain anything is when we actually use includes and when only these includes are updated, which will probably be quite an unusual scenario to actually be worthwhile to optimize.
1 parent 56a7a26 commit 83bcd3a

File tree

2 files changed

+56
-36
lines changed

2 files changed

+56
-36
lines changed

src/config_file.c

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -317,22 +317,35 @@ static int config_open(git_config_backend *cfg, git_config_level_t level)
317317
return res;
318318
}
319319

320-
/* The meat of the refresh, as we want to use it in different places */
321-
static int config__refresh(diskfile_backend *b, struct reader *reader, refcounted_strmap *values)
320+
static int config_is_modified(int *modified, struct config_file *file)
322321
{
323-
int error = 0;
322+
struct config_file *include;
323+
git_buf buf = GIT_BUF_INIT;
324+
git_oid hash;
325+
uint32_t i;
326+
int error;
324327

325-
if ((error = git_mutex_lock(&b->header.values_mutex)) < 0) {
326-
giterr_set(GITERR_OS, "failed to lock config backend");
328+
*modified = 0;
329+
330+
if ((error = git_futils_readbuffer(&buf, file->path)) < 0)
327331
goto out;
328-
}
329332

330-
if ((error = config_read(values->values, reader, b->level, 0)) < 0)
333+
if ((error = git_hash_buf(&hash, buf.ptr, buf.size)) < 0)
331334
goto out;
332335

333-
git_mutex_unlock(&b->header.values_mutex);
336+
if (!git_oid_equal(&hash, &file->checksum)) {
337+
*modified = 1;
338+
goto out;
339+
}
340+
341+
git_array_foreach(file->includes, i, include) {
342+
if ((error = config_is_modified(modified, include)) < 0 || *modified)
343+
goto out;
344+
}
334345

335346
out:
347+
git_buf_free(&buf);
348+
336349
return error;
337350
}
338351

@@ -342,46 +355,43 @@ static int config_refresh(git_config_backend *cfg)
342355
refcounted_strmap *values = NULL, *tmp;
343356
struct config_file *include;
344357
struct reader reader;
345-
int error, updated;
358+
int error, modified;
346359
uint32_t i;
347360

361+
error = config_is_modified(&modified, &b->file);
362+
if (error < 0 && error != GIT_ENOTFOUND)
363+
goto out;
364+
365+
if (!modified)
366+
return 0;
367+
348368
reader_init(&reader, &b->file);
349369

350370
error = git_futils_readbuffer_updated(
351-
&reader.buffer, b->file.path, &b->file.checksum, &updated);
371+
&reader.buffer, b->file.path, &b->file.checksum, NULL);
352372

353-
if (error < 0 && error != GIT_ENOTFOUND)
373+
if ((error = refcounted_strmap_alloc(&values)) < 0)
354374
goto out;
355375

356-
if (updated) {
357-
if ((error = refcounted_strmap_alloc(&values)) < 0)
358-
goto out;
359-
360-
/* Reparse the current configuration */
361-
git_array_foreach(b->file.includes, i, include) {
362-
config_file_clear(include);
363-
}
376+
/* Reparse the current configuration */
377+
git_array_foreach(b->file.includes, i, include) {
378+
config_file_clear(include);
379+
}
380+
git_array_clear(b->file.includes);
364381

365-
git_array_clear(b->file.includes);
382+
if ((error = config_read(values->values, &reader, b->level, 0)) < 0)
383+
goto out;
366384

367-
if ((error = config__refresh(b, &reader, values)) < 0)
368-
goto out;
385+
if ((error = git_mutex_lock(&b->header.values_mutex)) < 0) {
386+
giterr_set(GITERR_OS, "failed to lock config backend");
387+
goto out;
388+
}
369389

370-
tmp = b->header.values;
371-
b->header.values = values;
372-
values = tmp;
373-
} else {
374-
/* Refresh included configuration files */
375-
git_array_foreach(b->file.includes, i, include) {
376-
git_buf_free(&reader.buffer);
377-
reader_init(&reader, include);
378-
error = git_futils_readbuffer_updated(&reader.buffer, b->file.path,
379-
&b->file.checksum, NULL);
390+
tmp = b->header.values;
391+
b->header.values = values;
392+
values = tmp;
380393

381-
if ((error = config__refresh(b, &reader, b->header.values)) < 0)
382-
goto out;
383-
}
384-
}
394+
git_mutex_unlock(&b->header.values_mutex);
385395

386396
out:
387397
refcounted_strmap_free(values);

tests/config/include.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,13 @@ void test_config_include__depth2(void)
123123
cl_git_pass(git_config_get_string_buf(&buf, cfg, "foo.bar2"));
124124
cl_assert_equal_s("baz2", git_buf_cstr(&buf));
125125
}
126+
127+
void test_config_include__removing_include_removes_values(void)
128+
{
129+
cl_git_mkfile("top-level", "[include]\npath = included");
130+
cl_git_mkfile("included", "[foo]\nbar = value");
131+
132+
cl_git_pass(git_config_open_ondisk(&cfg, "top-level"));
133+
cl_git_mkfile("top-level", "");
134+
cl_git_fail(git_config_get_string_buf(&buf, cfg, "foo.bar"));
135+
}

0 commit comments

Comments
 (0)