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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 243b696..4442d0a 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 6d3fdb0..2550f8e 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 77cf573..937d00d 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 5446b95..e43c26b 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);
}