mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-05 20:33:41 +00:00
Merge branch 'config-refresh' into development
This commit is contained in:
commit
003808b38c
@ -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
|
||||
*
|
||||
|
40
src/attr.c
40
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_filestamp *stamp,
|
||||
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_filestamp_check(stamp, 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_filestamp stamp;
|
||||
|
||||
assert(filename && stack);
|
||||
|
||||
@ -408,12 +402,10 @@ int git_attr_cache__push_file(
|
||||
/* if not in cache, load data, parse, and cache */
|
||||
|
||||
if (source == GIT_ATTR_FILE_FROM_FILE) {
|
||||
if (file)
|
||||
memcpy(&st, &file->cache_data.st, sizeof(st));
|
||||
else
|
||||
memset(&st, 0, sizeof(st));
|
||||
git_futils_filestamp_set(
|
||||
&stamp, file ? &file->cache_data.stamp : NULL);
|
||||
|
||||
error = load_attr_file(&content, &st, filename);
|
||||
error = load_attr_file(&content, &stamp, filename);
|
||||
} else {
|
||||
error = load_attr_blob_from_index(&content, &blob,
|
||||
repo, file ? &file->cache_data.oid : NULL, relfile);
|
||||
@ -448,7 +440,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));
|
||||
git_futils_filestamp_set(&file->cache_data.stamp, &stamp);
|
||||
|
||||
finish:
|
||||
/* push file onto vector if we found one*/
|
||||
|
@ -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 <rule*> or <fnmatch*> */
|
||||
@ -66,7 +61,7 @@ typedef struct {
|
||||
bool pool_is_allocated;
|
||||
union {
|
||||
git_oid oid;
|
||||
git_attr_file_stat_sig st;
|
||||
git_futils_filestamp stamp;
|
||||
} cache_data;
|
||||
} git_attr_file;
|
||||
|
||||
|
14
src/config.c
14
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
|
||||
*/
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,38 @@ int git_futils_cp_r(
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_futils_filestamp_check(
|
||||
git_futils_filestamp *stamp, const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
/* if the stamp is NULL, then always reload */
|
||||
if (stamp == NULL)
|
||||
return 1;
|
||||
|
||||
if (p_stat(path, &st) < 0)
|
||||
return GIT_ENOTFOUND;
|
||||
|
||||
if (stamp->mtime == (git_time_t)st.st_mtime &&
|
||||
stamp->size == (git_off_t)st.st_size &&
|
||||
stamp->ino == (unsigned int)st.st_ino)
|
||||
return 0;
|
||||
|
||||
stamp->mtime = (git_time_t)st.st_mtime;
|
||||
stamp->size = (git_off_t)st.st_size;
|
||||
stamp->ino = (unsigned int)st.st_ino;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void git_futils_filestamp_set(
|
||||
git_futils_filestamp *target, const git_futils_filestamp *source)
|
||||
{
|
||||
assert(target);
|
||||
|
||||
if (source)
|
||||
memcpy(target, source, sizeof(*target));
|
||||
else
|
||||
memset(target, 0, sizeof(*target));
|
||||
}
|
||||
|
@ -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,44 @@ 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);
|
||||
|
||||
/**
|
||||
* A file stamp represents a snapshot of information about a file that can
|
||||
* be used to test if the file changes. This portable implementation is
|
||||
* based on stat data about that file, but it is possible that OS specific
|
||||
* versions could be implemented in the future.
|
||||
*/
|
||||
typedef struct {
|
||||
git_time_t mtime;
|
||||
git_off_t size;
|
||||
unsigned int ino;
|
||||
} git_futils_filestamp;
|
||||
|
||||
/**
|
||||
* Compare stat information for file with reference info.
|
||||
*
|
||||
* This function updates the file stamp to current data for the given path
|
||||
* and returns 0 if the file is up-to-date relative to the prior setting or
|
||||
* 1 if the file has been changed. (This also may return GIT_ENOTFOUND if
|
||||
* the file doesn't exist.)
|
||||
*
|
||||
* @param stamp File stamp to be checked
|
||||
* @param path Path to stat and check if changed
|
||||
* @return 0 if up-to-date, 1 if out-of-date, <0 on error
|
||||
*/
|
||||
extern int git_futils_filestamp_check(
|
||||
git_futils_filestamp *stamp, const char *path);
|
||||
|
||||
/**
|
||||
* Set or reset file stamp data
|
||||
*
|
||||
* This writes the target file stamp. If the source is NULL, this will set
|
||||
* the target stamp to values that will definitely be out of date. If the
|
||||
* source is not NULL, this copies the source values to the target.
|
||||
*
|
||||
* @param tgt File stamp to write to
|
||||
* @param src File stamp to copy from or NULL to clear the target
|
||||
*/
|
||||
extern void git_futils_filestamp_set(
|
||||
git_futils_filestamp *tgt, const git_futils_filestamp *src);
|
||||
|
||||
#endif /* INCLUDE_fileops_h__ */
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
67
tests-clar/config/refresh.c
Normal file
67
tests-clar/config/refresh.c
Normal file
@ -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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user