config: perform unlocking via git_transaction

This makes the API for commiting or discarding changes the same as for
references.
This commit is contained in:
Carlos Martín Nieto 2015-07-12 12:50:23 +02:00
parent 36f784b538
commit 5340d63d38
7 changed files with 92 additions and 26 deletions

View File

@ -9,11 +9,10 @@ v0.23 + 1
### API additions ### API additions
* `git_config_lock()` and `git_config_unlock()` have been added, which * `git_config_lock()` has been added, which allow for
allow for transactional/atomic complex updates to the configuration, transactional/atomic complex updates to the configuration, removing
removing the opportunity for concurrent operations and not the opportunity for concurrent operations and not committing any
committing any changes until the unlock. changes until the unlock.
### API removals ### API removals

View File

@ -696,25 +696,16 @@ GIT_EXTERN(int) git_config_backend_foreach_match(
* updates made after locking will not be visible to a reader until * updates made after locking will not be visible to a reader until
* the file is unlocked. * the file is unlocked.
* *
* You can apply the changes by calling `git_transaction_commit()`
* before freeing the transaction. Either of these actions will unlock
* the config.
*
* @param tx the resulting transaction, use this to commit or undo the
* changes
* @param cfg the configuration in which to lock * @param cfg the configuration in which to lock
* @return 0 or an error code * @return 0 or an error code
*/ */
GIT_EXTERN(int) git_config_lock(git_config *cfg); GIT_EXTERN(int) git_config_lock(git_transaction **tx, 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 GIT_END_DECL

View File

@ -1144,8 +1144,9 @@ int git_config_open_default(git_config **out)
return error; return error;
} }
int git_config_lock(git_config *cfg) int git_config_lock(git_transaction **out, git_config *cfg)
{ {
int error;
git_config_backend *file; git_config_backend *file;
file_internal *internal; file_internal *internal;
@ -1156,7 +1157,10 @@ int git_config_lock(git_config *cfg)
} }
file = internal->file; file = internal->file;
return file->lock(file); if ((error = file->lock(file)) < 0)
return error;
return git_transaction_config_new(out, cfg);
} }
int git_config_unlock(git_config *cfg, int commit) int git_config_unlock(git_config *cfg, int commit)

View File

@ -88,4 +88,19 @@ extern int git_config__cvar(
*/ */
int git_config_lookup_map_enum(git_cvar_t *type_out, const char **str_out, int git_config_lookup_map_enum(git_cvar_t *type_out, const char **str_out,
const git_cvar_map *maps, size_t map_n, int enum_val); const git_cvar_map *maps, size_t map_n, int enum_val);
/**
* 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);
#endif #endif

View File

@ -12,6 +12,7 @@
#include "pool.h" #include "pool.h"
#include "reflog.h" #include "reflog.h"
#include "signature.h" #include "signature.h"
#include "config.h"
#include "git2/transaction.h" #include "git2/transaction.h"
#include "git2/signature.h" #include "git2/signature.h"
@ -20,6 +21,12 @@
GIT__USE_STRMAP GIT__USE_STRMAP
typedef enum {
TRANSACTION_NONE,
TRANSACTION_REFS,
TRANSACTION_CONFIG,
} transaction_t;
typedef struct { typedef struct {
const char *name; const char *name;
void *payload; void *payload;
@ -39,13 +46,29 @@ typedef struct {
} transaction_node; } transaction_node;
struct git_transaction { struct git_transaction {
transaction_t type;
git_repository *repo; git_repository *repo;
git_refdb *db; git_refdb *db;
git_config *cfg;
git_strmap *locks; git_strmap *locks;
git_pool pool; git_pool pool;
}; };
int git_transaction_config_new(git_transaction **out, git_config *cfg)
{
git_transaction *tx;
assert(out && cfg);
tx = git__calloc(1, sizeof(git_transaction));
GITERR_CHECK_ALLOC(tx);
tx->type = TRANSACTION_CONFIG;
tx->cfg = cfg;
*out = tx;
return 0;
}
int git_transaction_new(git_transaction **out, git_repository *repo) int git_transaction_new(git_transaction **out, git_repository *repo)
{ {
int error; int error;
@ -71,6 +94,7 @@ int git_transaction_new(git_transaction **out, git_repository *repo)
if ((error = git_repository_refdb(&tx->db, repo)) < 0) if ((error = git_repository_refdb(&tx->db, repo)) < 0)
goto on_error; goto on_error;
tx->type = TRANSACTION_REFS;
memcpy(&tx->pool, &pool, sizeof(git_pool)); memcpy(&tx->pool, &pool, sizeof(git_pool));
tx->repo = repo; tx->repo = repo;
*out = tx; *out = tx;
@ -305,6 +329,14 @@ int git_transaction_commit(git_transaction *tx)
assert(tx); assert(tx);
if (tx->type == TRANSACTION_CONFIG) {
error = git_config_unlock(tx->cfg, true);
git_config_free(tx->cfg);
tx->cfg = NULL;
return error;
}
for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) { for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) {
if (!git_strmap_has_data(tx->locks, pos)) if (!git_strmap_has_data(tx->locks, pos))
continue; continue;
@ -332,6 +364,16 @@ void git_transaction_free(git_transaction *tx)
assert(tx); assert(tx);
if (tx->type == TRANSACTION_CONFIG) {
if (tx->cfg) {
git_config_unlock(tx->cfg, false);
git_config_free(tx->cfg);
}
git__free(tx);
return;
}
/* start by unlocking the ones we've left hanging, if any */ /* start by unlocking the ones we've left hanging, if any */
for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) { for (pos = kh_begin(tx->locks); pos < kh_end(tx->locks); pos++) {
if (!git_strmap_has_data(tx->locks, pos)) if (!git_strmap_has_data(tx->locks, pos))

14
src/transaction.h Normal file
View File

@ -0,0 +1,14 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_transaction_h__
#define INCLUDE_transaction_h__
#include "common.h"
int git_transaction_config_new(git_transaction **out, git_config *cfg);
#endif

View File

@ -637,6 +637,7 @@ void test_config_write__locking(void)
{ {
git_config *cfg, *cfg2; git_config *cfg, *cfg2;
git_config_entry *entry; git_config_entry *entry;
git_transaction *tx;
const char *filename = "locked-file"; const char *filename = "locked-file";
/* Open the config and lock it */ /* Open the config and lock it */
@ -645,7 +646,7 @@ void test_config_write__locking(void)
cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); cl_git_pass(git_config_get_entry(&entry, cfg, "section.name"));
cl_assert_equal_s("value", entry->value); cl_assert_equal_s("value", entry->value);
git_config_entry_free(entry); git_config_entry_free(entry);
cl_git_pass(git_config_lock(cfg)); cl_git_pass(git_config_lock(&tx, cfg));
/* Change entries in the locked backend */ /* Change entries in the locked backend */
cl_git_pass(git_config_set_string(cfg, "section.name", "other value")); cl_git_pass(git_config_set_string(cfg, "section.name", "other value"));
@ -665,8 +666,8 @@ void test_config_write__locking(void)
git_config_entry_free(entry); git_config_entry_free(entry);
cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg, "section2.name3")); cl_git_fail_with(GIT_ENOTFOUND, git_config_get_entry(&entry, cfg, "section2.name3"));
git_config_unlock(cfg, true); cl_git_pass(git_transaction_commit(tx));
git_config_free(cfg); git_transaction_free(tx);
/* Now that we've unlocked it, we should see both updates */ /* Now that we've unlocked it, we should see both updates */
cl_git_pass(git_config_open_ondisk(&cfg, filename)); cl_git_pass(git_config_open_ondisk(&cfg, filename));