From 36f784b538c4b27f7b52427d2cfce06c535abba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 1 Jun 2015 20:02:23 +0200 Subject: config: expose locking via the main API This lock/unlock pair allows for the cller to lock a configuration file to avoid concurrent operations. It also allows for a transactional approach to updating a configuration file. If multiple updates must be made atomically, they can be done while the config is locked. --- CHANGELOG.md | 10 ++++++++++ include/git2/config.h | 27 +++++++++++++++++++++++++++ src/config.c | 31 +++++++++++++++++++++++++++++++ tests/config/write.c | 41 ++++++++++++++++++++++------------------- 4 files changed, 90 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 243b696d7..4442d0a3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ v0.23 + 1 ### API additions +* `git_config_lock()` and `git_config_unlock()` have been added, which + allow for transactional/atomic complex updates to the configuration, + removing the opportunity for concurrent operations and not + committing any changes until the unlock. + + ### API removals ### Breaking API changes @@ -19,6 +25,10 @@ v0.23 + 1 with the reflog on ref deletion. The file-based backend must delete it, a database-backed one may wish to archive it. +* `git_config_backend` has gained two entries. `lock` and `unlock` + with which to implement the transactional/atomic semantics for the + configuration backend. + v0.23 ------ diff --git a/include/git2/config.h b/include/git2/config.h index 6d3fdb0c2..2550f8edb 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -689,6 +689,33 @@ GIT_EXTERN(int) git_config_backend_foreach_match( void *payload); +/** + * Lock the backend with the highest priority + * + * Locking disallows anybody else from writing to that backend. Any + * updates made after locking will not be visible to a reader until + * the file is unlocked. + * + * @param cfg the configuration in which to lock + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_lock(git_config *cfg); + +/** + * Unlock the backend with the highest priority + * + * Unlocking will allow other writers to updat the configuration + * file. Optionally, any changes performed since the lock will be + * applied to the configuration. + * + * @param cfg the configuration + * @param commit boolean which indicates whether to commit any changes + * done since locking + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit); + + /** @} */ GIT_END_DECL #endif diff --git a/src/config.c b/src/config.c index 77cf573e6..937d00dcb 100644 --- a/src/config.c +++ b/src/config.c @@ -1144,6 +1144,37 @@ int git_config_open_default(git_config **out) return error; } +int git_config_lock(git_config *cfg) +{ + git_config_backend *file; + file_internal *internal; + + internal = git_vector_get(&cfg->files, 0); + if (!internal || !internal->file) { + giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends/files"); + return -1; + } + file = internal->file; + + return file->lock(file); +} + +int git_config_unlock(git_config *cfg, int commit) +{ + git_config_backend *file; + file_internal *internal; + + internal = git_vector_get(&cfg->files, 0); + if (!internal || !internal->file) { + giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends/files"); + return -1; + } + + file = internal->file; + + return file->unlock(file, commit); +} + /*********** * Parsers ***********/ diff --git a/tests/config/write.c b/tests/config/write.c index 5446b95c3..e43c26bd9 100644 --- a/tests/config/write.c +++ b/tests/config/write.c @@ -635,44 +635,47 @@ void test_config_write__to_file_with_only_comment(void) void test_config_write__locking(void) { - git_config_backend *cfg, *cfg2; + git_config *cfg, *cfg2; git_config_entry *entry; const char *filename = "locked-file"; /* Open the config and lock it */ cl_git_mkfile(filename, "[section]\n\tname = value\n"); - cl_git_pass(git_config_file__ondisk(&cfg, filename)); - cl_git_pass(git_config_file_open(cfg, GIT_CONFIG_LEVEL_APP)); - cl_git_pass(git_config_file_get_string(&entry, cfg, "section.name")); + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); cl_assert_equal_s("value", entry->value); git_config_entry_free(entry); - cl_git_pass(git_config_file_lock(cfg)); + cl_git_pass(git_config_lock(cfg)); /* Change entries in the locked backend */ - cl_git_pass(git_config_file_set_string(cfg, "section.name", "other value")); - cl_git_pass(git_config_file_set_string(cfg, "section2.name3", "more value")); + cl_git_pass(git_config_set_string(cfg, "section.name", "other value")); + cl_git_pass(git_config_set_string(cfg, "section2.name3", "more value")); /* We can see that the file we read from hasn't changed */ - cl_git_pass(git_config_file__ondisk(&cfg2, filename)); - cl_git_pass(git_config_file_open(cfg2, GIT_CONFIG_LEVEL_APP)); - cl_git_pass(git_config_file_get_string(&entry, cfg2, "section.name")); + cl_git_pass(git_config_open_ondisk(&cfg2, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg2, "section.name")); cl_assert_equal_s("value", entry->value); git_config_entry_free(entry); - cl_git_fail_with(GIT_ENOTFOUND, git_config_file_get_string(&entry, cfg2, "section2.name3")); - git_config_file_free(cfg2); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg2, "section2.name3")); + git_config_free(cfg2); - git_config_file_unlock(cfg, true); - git_config_file_free(cfg); + /* And we also get the old view when we read from the locked config */ + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("value", entry->value); + git_config_entry_free(entry); + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg, "section2.name3")); + + git_config_unlock(cfg, true); + git_config_free(cfg); /* Now that we've unlocked it, we should see both updates */ - cl_git_pass(git_config_file__ondisk(&cfg, filename)); - cl_git_pass(git_config_file_open(cfg, GIT_CONFIG_LEVEL_APP)); - cl_git_pass(git_config_file_get_string(&entry, cfg, "section.name")); + cl_git_pass(git_config_open_ondisk(&cfg, filename)); + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); cl_assert_equal_s("other value", entry->value); git_config_entry_free(entry); - cl_git_pass(git_config_file_get_string(&entry, cfg, "section2.name3")); + cl_git_pass(git_config_get_entry(&entry, cfg, "section2.name3")); cl_assert_equal_s("more value", entry->value); git_config_entry_free(entry); - git_config_file_free(cfg); + git_config_free(cfg); } -- cgit v1.2.3