Commit 967e073dca8742d20bc14fd8c62b64b54f9a5326

Edward Thomson 2016-02-27T16:42:02

merge driver: correct global initialization

diff --git a/src/global.c b/src/global.c
index c725b51..cbd12dd 100644
--- a/src/global.c
+++ b/src/global.c
@@ -9,6 +9,7 @@
 #include "hash.h"
 #include "sysdir.h"
 #include "filter.h"
+#include "merge_driver.h"
 #include "openssl_stream.h"
 #include "thread-utils.h"
 #include "git2/global.h"
@@ -59,6 +60,7 @@ static int init_common(void)
 	if ((ret = git_hash_global_init()) == 0 &&
 		(ret = git_sysdir_global_init()) == 0 &&
 		(ret = git_filter_global_init()) == 0 &&
+		(ret = git_merge_driver_global_init()) == 0 &&
 		(ret = git_transport_ssh_global_init()) == 0)
 		ret = git_openssl_stream_global_init();
 
diff --git a/src/merge.c b/src/merge.c
index bf75cf2..7423305 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -29,6 +29,7 @@
 #include "annotated_commit.h"
 #include "commit.h"
 #include "oidarray.h"
+#include "merge_driver.h"
 
 #include "git2/types.h"
 #include "git2/repository.h"
diff --git a/src/merge.h b/src/merge.h
index 8eaa1ad..68290e7 100644
--- a/src/merge.h
+++ b/src/merge.h
@@ -36,37 +36,6 @@ enum {
 };
 
 
-/* Merge drivers */
-
-struct git_merge_driver_source {
-	git_repository *repo;
-	const char *default_driver;
-	const git_merge_file_options *file_opts;
-
-	const git_index_entry *ancestor;
-	const git_index_entry *ours;
-	const git_index_entry *theirs;
-};
-
-extern int git_merge_driver_for_path(
-	char **name_out,
-	git_merge_driver **driver_out,
-	git_repository *repo,
-	const char *path);
-
-/* Basic (normal) merge driver, takes favor type as the payload argument */
-extern git_merge_driver git_merge_driver__normal;
-
-/* Merge driver for text files, performs a standard three-way merge */
-extern git_merge_driver git_merge_driver__text;
-
-/* Merge driver for union-style merging */
-extern git_merge_driver git_merge_driver__union;
-
-/* Merge driver for unmergeable (binary) files: always produces conflicts */
-extern git_merge_driver git_merge_driver__binary;
-
-
 /** Types of changes when files are merged from branch to branch. */
 typedef enum {
 	/* No conflict - a change only occurs in one branch. */
diff --git a/src/merge_driver.c b/src/merge_driver.c
index 59b5461..e5bb169 100644
--- a/src/merge_driver.c
+++ b/src/merge_driver.c
@@ -9,6 +9,7 @@
 #include "vector.h"
 #include "global.h"
 #include "merge.h"
+#include "merge_driver.h"
 #include "git2/merge.h"
 #include "git2/sys/merge.h"
 
@@ -17,6 +18,7 @@ static const char *merge_driver_name__union = "union";
 static const char *merge_driver_name__binary = "binary";
 
 struct merge_driver_registry {
+	git_rwlock lock;
 	git_vector drivers;
 };
 
@@ -26,11 +28,14 @@ typedef struct {
 	char name[GIT_FLEX_ARRAY];
 } git_merge_driver_entry;
 
-static struct merge_driver_registry *merge_driver_registry = NULL;
+static struct merge_driver_registry merge_driver_registry;
 
 static git_merge_file_favor_t merge_favor_normal = GIT_MERGE_FILE_FAVOR_NORMAL;
 static git_merge_file_favor_t merge_favor_union = GIT_MERGE_FILE_FAVOR_UNION;
 
+static void git_merge_driver_global_shutdown(void);
+
+
 static int merge_driver_apply(
 	git_merge_driver *self,
 	void **payload,
@@ -144,26 +149,6 @@ static int merge_driver_entry_search(const void *a, const void *b)
 	return strcmp(name_a, entry_b->name);
 }
 
-static void merge_driver_registry_shutdown(void)
-{
-	struct merge_driver_registry *reg;
-	git_merge_driver_entry *entry;
-	size_t i;
-
-	if ((reg = git__swap(merge_driver_registry, NULL)) == NULL)
-		return;
-
-	git_vector_foreach(&reg->drivers, i, entry) {
-		if (entry && entry->driver->shutdown)
-			entry->driver->shutdown(entry->driver);
-
-		git__free(entry);
-	}
-
-	git_vector_free(&reg->drivers);
-	git__free(reg);
-}
-
 git_merge_driver git_merge_driver__normal = {
 	GIT_MERGE_DRIVER_VERSION,
 	NULL,
@@ -196,73 +181,133 @@ git_merge_driver git_merge_driver__binary = {
 	merge_driver_binary_apply
 };
 
-static int merge_driver_registry_initialize(void)
+/* Note: callers must lock the registry before calling this function */
+static int merge_driver_registry_insert(
+	const char *name, git_merge_driver *driver)
 {
-	struct merge_driver_registry *reg;
-	int error = 0;
+	git_merge_driver_entry *entry;
+
+	entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1);
+	GITERR_CHECK_ALLOC(entry);
+
+	strcpy(entry->name, name);
+	entry->driver = driver;
 
-	if (merge_driver_registry)
-		return 0;
+	return git_vector_insert_sorted(
+		&merge_driver_registry.drivers, entry, NULL);
+}
 
-	reg = git__calloc(1, sizeof(struct merge_driver_registry));
-	GITERR_CHECK_ALLOC(reg);
+int git_merge_driver_global_init(void)
+{
+	int error;
 
-	if ((error = git_vector_init(&reg->drivers, 3, merge_driver_entry_cmp)) < 0)
-		goto done;
-	
-	reg = git__compare_and_swap(&merge_driver_registry, NULL, reg);
+	if (git_rwlock_init(&merge_driver_registry.lock) < 0)
+		return -1;
 
-	if (reg != NULL)
+	if ((error = git_vector_init(&merge_driver_registry.drivers, 3,
+		merge_driver_entry_cmp)) < 0)
 		goto done;
 
-	git__on_shutdown(merge_driver_registry_shutdown);
-
-	if ((error = git_merge_driver_register(
+	if ((error = merge_driver_registry_insert(
 			merge_driver_name__text, &git_merge_driver__text)) < 0 ||
-		(error = git_merge_driver_register(
+		(error = merge_driver_registry_insert(
 			merge_driver_name__union, &git_merge_driver__union)) < 0 ||
-		(error = git_merge_driver_register(
+		(error = merge_driver_registry_insert(
 			merge_driver_name__binary, &git_merge_driver__binary)) < 0)
-		goto done;
+
+	git__on_shutdown(git_merge_driver_global_shutdown);
 
 done:
 	if (error < 0)
-		merge_driver_registry_shutdown();
+		git_vector_free_deep(&merge_driver_registry.drivers);
 
 	return error;
 }
 
-int git_merge_driver_register(const char *name, git_merge_driver *driver)
+static void git_merge_driver_global_shutdown(void)
 {
 	git_merge_driver_entry *entry;
+	size_t i;
+
+	if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0)
+		return;
+
+	git_vector_foreach(&merge_driver_registry.drivers, i, entry) {
+		if (entry->driver->shutdown)
+			entry->driver->shutdown(entry->driver);
+
+		git__free(entry);
+	}
+
+	git_vector_free(&merge_driver_registry.drivers);
+
+	git_rwlock_wrunlock(&merge_driver_registry.lock);
+	git_rwlock_free(&merge_driver_registry.lock);
+}
+
+/* Note: callers must lock the registry before calling this function */
+static int merge_driver_registry_find(size_t *pos, const char *name)
+{
+	return git_vector_search2(pos, &merge_driver_registry.drivers,
+		merge_driver_entry_search, name);
+}
+
+/* Note: callers must lock the registry before calling this function */
+static git_merge_driver_entry *merge_driver_registry_lookup(
+	size_t *pos, const char *name)
+{
+	git_merge_driver_entry *entry = NULL;
+
+	if (!merge_driver_registry_find(pos, name))
+		entry = git_vector_get(&merge_driver_registry.drivers, *pos);
+
+	return entry;
+}
+
+int git_merge_driver_register(const char *name, git_merge_driver *driver)
+{
+	int error;
 
 	assert(name && driver);
 
-	if (merge_driver_registry_initialize() < 0)
+	if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) {
+		giterr_set(GITERR_OS, "failed to lock merge driver registry");
 		return -1;
+	}
 
-	entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1);
-	GITERR_CHECK_ALLOC(entry);
+	if (!merge_driver_registry_find(NULL, name)) {
+		giterr_set(GITERR_MERGE, "attempt to reregister existing driver '%s'",
+			name);
+		error = GIT_EEXISTS;
+		goto done;
+	}
 
-	strcpy(entry->name, name);
-	entry->driver = driver;
+	error = merge_driver_registry_insert(name, driver);
 
-	return git_vector_insert_sorted(
-		&merge_driver_registry->drivers, entry, NULL);
+done:
+	git_rwlock_wrunlock(&merge_driver_registry.lock);
+	return error;
 }
 
 int git_merge_driver_unregister(const char *name)
 {
 	git_merge_driver_entry *entry;
 	size_t pos;
-	int error;
+	int error = 0;
 
-	if ((error = git_vector_search2(&pos, &merge_driver_registry->drivers,
-		merge_driver_entry_search, name)) < 0)
-		return error;
+	if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) {
+		giterr_set(GITERR_OS, "failed to lock merge driver registry");
+		return -1;
+	}
+
+	if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) {
+		giterr_set(GITERR_MERGE, "cannot find merge driver '%s' to unregister",
+			name);
+		error = GIT_ENOTFOUND;
+		goto done;
+	}
 
-	entry = git_vector_get(&merge_driver_registry->drivers, pos);
-	git_vector_remove(&merge_driver_registry->drivers, pos);
+	git_vector_remove(&merge_driver_registry.drivers, pos);
 
 	if (entry->initialized && entry->driver->shutdown) {
 		entry->driver->shutdown(entry->driver);
@@ -271,7 +316,9 @@ int git_merge_driver_unregister(const char *name)
 
 	git__free(entry);
 
-	return 0;
+done:
+	git_rwlock_wrunlock(&merge_driver_registry.lock);
+	return error;
 }
 
 git_merge_driver *git_merge_driver_lookup(const char *name)
@@ -282,24 +329,27 @@ git_merge_driver *git_merge_driver_lookup(const char *name)
 
 	/* If we've decided the merge driver to use internally - and not
 	 * based on user configuration (in merge_driver_name_for_path)
-	 * then we can use a hardcoded name instead of looking it up in
-	 * the vector.
+	 * then we can use a hardcoded name to compare instead of bothering
+	 * to take a lock and look it up in the vector.
 	 */
 	if (name == merge_driver_name__text)
 		return &git_merge_driver__text;
 	else if (name == merge_driver_name__binary)
 		return &git_merge_driver__binary;
 
-	if (merge_driver_registry_initialize() < 0)
+	if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) {
+		giterr_set(GITERR_OS, "failed to lock merge driver registry");
 		return NULL;
+	}
 
-	error = git_vector_search2(&pos, &merge_driver_registry->drivers,
-		merge_driver_entry_search, name);
+	entry = merge_driver_registry_lookup(&pos, name);
 
-	if (error == GIT_ENOTFOUND)
-		return NULL;
+	git_rwlock_rdunlock(&merge_driver_registry.lock);
 
-	entry = git_vector_get(&merge_driver_registry->drivers, pos);
+	if (entry == NULL) {
+		giterr_set(GITERR_MERGE, "cannot use an unregistered filter");
+		return NULL;
+	}
 
 	if (!entry->initialized) {
 		if (entry->driver->initialize &&
diff --git a/src/merge_driver.h b/src/merge_driver.h
new file mode 100644
index 0000000..c0f75fa
--- /dev/null
+++ b/src/merge_driver.h
@@ -0,0 +1,44 @@
+/*
+ * 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_merge_driver_h__
+#define INCLUDE_merge_driver_h__
+
+#include "git2/merge.h"
+#include "git2/index.h"
+#include "git2/sys/merge.h"
+
+struct git_merge_driver_source {
+	git_repository *repo;
+	const char *default_driver;
+	const git_merge_file_options *file_opts;
+
+	const git_index_entry *ancestor;
+	const git_index_entry *ours;
+	const git_index_entry *theirs;
+};
+
+extern int git_merge_driver_global_init(void);
+
+extern int git_merge_driver_for_path(
+	char **name_out,
+	git_merge_driver **driver_out,
+	git_repository *repo,
+	const char *path);
+
+/* Basic (normal) merge driver, takes favor type as the payload argument */
+extern git_merge_driver git_merge_driver__normal;
+
+/* Merge driver for text files, performs a standard three-way merge */
+extern git_merge_driver git_merge_driver__text;
+
+/* Merge driver for union-style merging */
+extern git_merge_driver git_merge_driver__union;
+
+/* Merge driver for unmergeable (binary) files: always produces conflicts */
+extern git_merge_driver git_merge_driver__binary;
+
+#endif