diff --git a/include/git2/config.h b/include/git2/config.h index 67408f90f..e417cb379 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -56,6 +56,7 @@ struct git_config_file { int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_file *, const char *key); int (*foreach)(struct git_config_file *, const char *, int (*fn)(const git_config_entry *, void *), void *data); + int (*refresh)(struct git_config_file *); void (*free)(struct git_config_file *); }; @@ -242,6 +243,19 @@ GIT_EXTERN(int) git_config_open_level( git_config *cfg_parent, unsigned int level); +/** + * Reload changed config files + * + * A config file may be changed on disk out from under the in-memory + * config object. This function causes us to look for files that have + * been modified since we last loaded them and refresh the config with + * the latest information. + * + * @param cfg The configuration to refresh + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_refresh(git_config *cfg); + /** * Free the configuration and its associated memory and files * diff --git a/src/attr.c b/src/attr.c index f5e09cc08..50caa1e1b 100644 --- a/src/attr.c +++ b/src/attr.c @@ -261,31 +261,25 @@ bool git_attr_cache__is_cached( static int load_attr_file( const char **data, - git_attr_file_stat_sig *sig, + git_futils_stat_sig *sig, const char *filename) { int error; git_buf content = GIT_BUF_INIT; - struct stat st; - if (p_stat(filename, &st) < 0) - return GIT_ENOTFOUND; - - if (sig != NULL && - (git_time_t)st.st_mtime == sig->seconds && - (git_off_t)st.st_size == sig->size && - (unsigned int)st.st_ino == sig->ino) - return GIT_ENOTFOUND; - - error = git_futils_readbuffer_updated(&content, filename, NULL, NULL); + error = git_futils_stat_sig_needs_reload(sig, filename); if (error < 0) return error; - if (sig != NULL) { - sig->seconds = (git_time_t)st.st_mtime; - sig->size = (git_off_t)st.st_size; - sig->ino = (unsigned int)st.st_ino; - } + /* if error == 0, then file is up to date. By returning GIT_ENOTFOUND, + * we tell the caller not to reparse this file... + */ + if (!error) + return GIT_ENOTFOUND; + + error = git_futils_readbuffer(&content, filename); + if (error < 0) + return error; *data = git_buf_detach(&content); @@ -386,7 +380,7 @@ int git_attr_cache__push_file( git_attr_cache *cache = git_repository_attr_cache(repo); git_attr_file *file = NULL; git_blob *blob = NULL; - git_attr_file_stat_sig st; + git_futils_stat_sig sig; assert(filename && stack); @@ -409,11 +403,11 @@ int git_attr_cache__push_file( if (source == GIT_ATTR_FILE_FROM_FILE) { if (file) - memcpy(&st, &file->cache_data.st, sizeof(st)); + memcpy(&sig, &file->cache_data.sig, sizeof(sig)); else - memset(&st, 0, sizeof(st)); + memset(&sig, 0, sizeof(sig)); - error = load_attr_file(&content, &st, filename); + error = load_attr_file(&content, &sig, filename); } else { error = load_attr_blob_from_index(&content, &blob, repo, file ? &file->cache_data.oid : NULL, relfile); @@ -448,7 +442,7 @@ int git_attr_cache__push_file( if (blob) git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob)); else - memcpy(&file->cache_data.st, &st, sizeof(st)); + memcpy(&file->cache_data.sig, &sig, sizeof(sig)); finish: /* push file onto vector if we found one*/ diff --git a/src/attr_file.h b/src/attr_file.h index 877daf306..ecd64866f 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -11,6 +11,7 @@ #include "vector.h" #include "pool.h" #include "buffer.h" +#include "fileops.h" #define GIT_ATTR_FILE ".gitattributes" #define GIT_ATTR_FILE_INREPO "info/attributes" @@ -53,12 +54,6 @@ typedef struct { const char *value; } git_attr_assignment; -typedef struct { - git_time_t seconds; - git_off_t size; - unsigned int ino; -} git_attr_file_stat_sig; - typedef struct { char *key; /* cache "source#path" this was loaded from */ git_vector rules; /* vector of or */ @@ -66,7 +61,7 @@ typedef struct { bool pool_is_allocated; union { git_oid oid; - git_attr_file_stat_sig st; + git_futils_stat_sig sig; } cache_data; } git_attr_file; diff --git a/src/config.c b/src/config.c index 937510d7e..033bde425 100644 --- a/src/config.c +++ b/src/config.c @@ -267,6 +267,20 @@ int git_config_add_file( return 0; } +int git_config_refresh(git_config *cfg) +{ + int error = 0; + unsigned int i; + + for (i = 0; i < cfg->files.length && !error; ++i) { + file_internal *internal = git_vector_get(&cfg->files, i); + git_config_file *file = internal->file; + error = file->refresh(file); + } + + return error; +} + /* * Loop over all the variables */ diff --git a/src/config_file.c b/src/config_file.c index 92fe13296..1eae8b9ac 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -75,7 +75,11 @@ typedef struct { int eof; } reader; - char *file_path; + char *file_path; + time_t file_mtime; + size_t file_size; + + unsigned int level; } diskfile_backend; static int config_parse(diskfile_backend *cfg_file, unsigned int level); @@ -150,25 +154,53 @@ static int config_open(git_config_file *cfg, unsigned int level) int res; diskfile_backend *b = (diskfile_backend *)cfg; + b->level = level; + b->values = git_strmap_alloc(); GITERR_CHECK_ALLOC(b->values); git_buf_init(&b->reader.buffer, 0); - res = git_futils_readbuffer(&b->reader.buffer, b->file_path); + res = git_futils_readbuffer_updated( + &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL); /* It's fine if the file doesn't exist */ if (res == GIT_ENOTFOUND) return 0; - if (res < 0 || config_parse(b, level) < 0) { + if (res < 0 || (res = config_parse(b, level)) < 0) { free_vars(b->values); b->values = NULL; - git_buf_free(&b->reader.buffer); - return -1; } git_buf_free(&b->reader.buffer); - return 0; + return res; +} + +static int config_refresh(git_config_file *cfg) +{ + int res, updated = 0; + diskfile_backend *b = (diskfile_backend *)cfg; + git_strmap *old_values; + + res = git_futils_readbuffer_updated( + &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated); + if (res < 0 || !updated) + return (res == GIT_ENOTFOUND) ? 0 : res; + + /* need to reload - store old values and prep for reload */ + old_values = b->values; + b->values = git_strmap_alloc(); + GITERR_CHECK_ALLOC(b->values); + + if ((res = config_parse(b, b->level)) < 0) { + free_vars(b->values); + b->values = old_values; + } else { + free_vars(old_values); + } + + git_buf_free(&b->reader.buffer); + return res; } static void backend_free(git_config_file *_backend) @@ -527,6 +559,7 @@ int git_config_file__ondisk(git_config_file **out, const char *path) backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; backend->parent.foreach = file_foreach; + backend->parent.refresh = config_refresh; backend->parent.free = backend_free; *out = (git_config_file *)backend; @@ -1197,8 +1230,12 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p git__free(section); git__free(current_section); + /* refresh stats - if this errors, then commit will error too */ + (void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file); + result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); git_buf_free(&cfg->reader.buffer); + return result; rewrite_fail: diff --git a/src/filebuf.c b/src/filebuf.c index b9b908c8d..5e5db0db6 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -466,3 +466,26 @@ int git_filebuf_printf(git_filebuf *file, const char *format, ...) return res; } +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file) +{ + int res; + struct stat st; + + if (file->fd_is_open) + res = p_fstat(file->fd, &st); + else + res = p_stat(file->path_original, &st); + + if (res < 0) { + giterr_set(GITERR_OS, "Could not get stat info for '%s'", + file->path_original); + return res; + } + + if (mtime) + *mtime = st.st_mtime; + if (size) + *size = (size_t)st.st_size; + + return 0; +} diff --git a/src/filebuf.h b/src/filebuf.h index 377883147..a74bb0ce1 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -82,5 +82,6 @@ int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode); void git_filebuf_cleanup(git_filebuf *lock); int git_filebuf_hash(git_oid *oid, git_filebuf *file); int git_filebuf_flush(git_filebuf *file); +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); #endif diff --git a/src/fileops.c b/src/fileops.c index 6342b1679..c22622c72 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -142,10 +142,11 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) } int git_futils_readbuffer_updated( - git_buf *buf, const char *path, time_t *mtime, int *updated) + git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated) { git_file fd; struct stat st; + bool changed = false; assert(buf && path && *path); @@ -162,16 +163,25 @@ int git_futils_readbuffer_updated( } /* - * If we were given a time, we only want to read the file if it - * has been modified. + * If we were given a time and/or a size, we only want to read the file + * if it has been modified. */ - if (mtime != NULL && *mtime >= st.st_mtime) { + if (size && *size != (size_t)st.st_size) + changed = true; + if (mtime && *mtime != st.st_mtime) + changed = true; + if (!size && !mtime) + changed = true; + + if (!changed) { p_close(fd); return 0; } if (mtime != NULL) *mtime = st.st_mtime; + if (size != NULL) + *size = (size_t)st.st_size; if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { p_close(fd); @@ -188,7 +198,7 @@ int git_futils_readbuffer_updated( int git_futils_readbuffer(git_buf *buf, const char *path) { - return git_futils_readbuffer_updated(buf, path, NULL, NULL); + return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL); } int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) @@ -660,3 +670,28 @@ int git_futils_cp_r( return error; } + +int git_futils_stat_sig_needs_reload( + git_futils_stat_sig *sig, const char *path) +{ + struct stat st; + + /* if the sig is NULL, then alway reload */ + if (sig == NULL) + return 1; + + if (p_stat(path, &st) < 0) + return GIT_ENOTFOUND; + + if ((git_time_t)st.st_mtime == sig->seconds && + (git_off_t)st.st_size == sig->size && + (unsigned int)st.st_ino == sig->ino) + return 0; + + sig->seconds = (git_time_t)st.st_mtime; + sig->size = (git_off_t)st.st_size; + sig->ino = (unsigned int)st.st_ino; + + return 1; +} + diff --git a/src/fileops.h b/src/fileops.h index 19f7ffd54..6437ccc45 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -18,7 +18,8 @@ * Read whole files into an in-memory buffer for processing */ extern int git_futils_readbuffer(git_buf *obj, const char *path); -extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated); +extern int git_futils_readbuffer_updated( + git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated); extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len); /** @@ -266,4 +267,26 @@ extern int git_futils_find_system_file(git_buf *path, const char *filename); */ extern int git_futils_fake_symlink(const char *new, const char *old); + +typedef struct { + git_time_t seconds; + git_off_t size; + unsigned int ino; +} git_futils_stat_sig; + +/** + * Compare stat information for file with reference info. + * + * Use this as a way to track if a file has changed on disk. This will + * return GIT_ENOTFOUND if the file doesn't exist, 0 if the file is up-to-date + * with regards to the signature, and 1 if the file needs to reloaded. When + * a 1 is returned, the signature will also be updated with the latest data. + * + * @param sig stat signature structure + * @param path path to be statted + * @return 0 if up-to-date, 1 if out-of-date, <0 on error + */ +extern int git_futils_stat_sig_needs_reload( + git_futils_stat_sig *sig, const char *path); + #endif /* INCLUDE_fileops_h__ */ diff --git a/src/index.c b/src/index.c index 44dd93417..35cf5dd8f 100644 --- a/src/index.c +++ b/src/index.c @@ -405,7 +405,7 @@ int git_index_read(git_index *index) /* We don't want to update the mtime if we fail to parse the index */ mtime = index->last_modified; error = git_futils_readbuffer_updated( - &buffer, index->index_file_path, &mtime, &updated); + &buffer, index->index_file_path, &mtime, NULL, &updated); if (error < 0) return error; diff --git a/src/refs.c b/src/refs.c index 833f2fcc8..779d6080f 100644 --- a/src/refs.c +++ b/src/refs.c @@ -123,7 +123,8 @@ static int reference_read( if (git_buf_joinpath(&path, repo_path, ref_name) < 0) return -1; - result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); + result = git_futils_readbuffer_updated( + file_content, path.ptr, mtime, NULL, updated); git_buf_free(&path); return result; diff --git a/tests-clar/config/refresh.c b/tests-clar/config/refresh.c new file mode 100644 index 000000000..99d677f0e --- /dev/null +++ b/tests-clar/config/refresh.c @@ -0,0 +1,67 @@ +#include "clar_libgit2.h" + +#define TEST_FILE "config.refresh" + +void test_config_refresh__initialize(void) +{ +} + +void test_config_refresh__cleanup(void) +{ + cl_fixture_cleanup(TEST_FILE); +} + +void test_config_refresh__update_value(void) +{ + git_config *cfg; + int32_t v; + + cl_git_mkfile(TEST_FILE, "[section]\n\tvalue = 1\n\n"); + + /* By freeing the config, we make sure we flush the values */ + cl_git_pass(git_config_open_ondisk(&cfg, TEST_FILE)); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(1, v); + + cl_git_rewritefile(TEST_FILE, "[section]\n\tvalue = 10\n\n"); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(1, v); + + cl_git_pass(git_config_refresh(cfg)); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(10, v); + + git_config_free(cfg); +} + +void test_config_refresh__delete_value(void) +{ + git_config *cfg; + int32_t v; + + cl_git_mkfile(TEST_FILE, "[section]\n\tvalue = 1\n\n"); + + /* By freeing the config, we make sure we flush the values */ + cl_git_pass(git_config_open_ondisk(&cfg, TEST_FILE)); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(1, v); + cl_git_fail(git_config_get_int32(&v, cfg, "section.newval")); + + cl_git_rewritefile(TEST_FILE, "[section]\n\tnewval = 10\n\n"); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(1, v); + cl_git_fail(git_config_get_int32(&v, cfg, "section.newval")); + + cl_git_pass(git_config_refresh(cfg)); + + cl_git_fail(git_config_get_int32(&v, cfg, "section.value")); + cl_git_pass(git_config_get_int32(&v, cfg, "section.newval")); + cl_assert_equal_i(10, v); + + git_config_free(cfg); +}