mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-29 22:25:04 +00:00
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.
This commit is contained in:
parent
b166703964
commit
36f784b538
10
CHANGELOG.md
10
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
|
||||
------
|
||||
|
||||
|
@ -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
|
||||
|
31
src/config.c
31
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
|
||||
***********/
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user