summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/config.h14
-rw-r--r--src/attr.c34
-rw-r--r--src/attr_file.h9
-rw-r--r--src/config.c14
-rw-r--r--src/config_file.c49
-rw-r--r--src/filebuf.c23
-rw-r--r--src/filebuf.h1
-rw-r--r--src/fileops.c45
-rw-r--r--src/fileops.h25
-rw-r--r--src/index.c2
-rw-r--r--src/refs.c3
-rw-r--r--tests-clar/config/refresh.c67
12 files changed, 245 insertions, 41 deletions
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 *);
};
@@ -243,6 +244,19 @@ GIT_EXTERN(int) git_config_open_level(
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
*
* @param cfg the configuration to free
diff --git a/src/attr.c b/src/attr.c
index f5e09cc08..50caa1e1b 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -261,32 +261,26 @@ 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;
+ error = git_futils_stat_sig_needs_reload(sig, filename);
+ if (error < 0)
+ return error;
- 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)
+ /* 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_updated(&content, filename, NULL, NULL);
+ error = git_futils_readbuffer(&content, 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;
- }
-
*data = git_buf_detach(&content);
return 0;
@@ -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"
@@ -54,19 +55,13 @@ typedef struct {
} 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 <rule*> or <fnmatch*> */
git_pool *pool;
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);
+}