[geany/geany-plugins] b5703b: git-ui: Read Git repository in a separate thread
Colomban Wendling
git-noreply at xxxxx
Wed Feb 18 08:38:04 UTC 2015
Branch: refs/heads/master
Author: Colomban Wendling <ban at herbesfolles.org>
Committer: Colomban Wendling <ban at herbesfolles.org>
Date: Tue, 07 Oct 2014 13:33:05 UTC
Commit: b5703b007d3b1ae793767f24bf9f8867f2bc3332
https://github.com/geany/geany-plugins/commit/b5703b007d3b1ae793767f24bf9f8867f2bc3332
Log Message:
-----------
git-ui: Read Git repository in a separate thread
Modified Paths:
--------------
git-ui/src/ggu-plugin.c
Modified: git-ui/src/ggu-plugin.c
178 lines changed, 128 insertions(+), 50 deletions(-)
===================================================================
@@ -49,6 +49,10 @@ PLUGIN_SET_TRANSLATABLE_INFO (
)
+/* g_async_queue_push() doesn't allow for NULL data, so use a non-NULL fake
+ * data that we know cannot ever be a valid job */
+#define QUIT_THREAD_JOB ((AsyncBlobJob *) (&G_queue))
+
#define MARKER_ALLOCATED_QTAG (g_quark_from_static_string ("ggu-git-marker-allocated"))
@@ -58,12 +62,24 @@ enum {
MARKER_COUNT
};
+typedef void (*BlobReadyFunc) (const gchar *path,
+ git_blob *blob,
+ gpointer data);
+
+typedef struct AsyncBlobJob AsyncBlobJob;
+struct AsyncBlobJob {
+ gchar *path;
+ git_blob *blob;
+ BlobReadyFunc callback;
+ gpointer user_data;
+};
+
/* cache */
-static git_repository *G_repo = NULL;
static git_blob *G_file_blob = NULL;
/* global state */
-static GFileMonitor *G_monitor = NULL;
+static GAsyncQueue *G_queue = NULL;
+static GThread *G_thread = NULL;
static gulong G_source_id = 0;
static struct {
gint num;
@@ -82,6 +98,15 @@ static void on_git_dir_changed (GFileMonitor *monitor,
gpointer user_data);
+static void
+clear_cached_blob (void)
+{
+ if (G_file_blob) {
+ git_blob_free (G_file_blob);
+ G_file_blob = NULL;
+ }
+}
+
/* get the file blob for @relpath at HEAD */
static git_blob *
repo_get_file_blob (git_repository *repo,
@@ -113,61 +138,116 @@ repo_get_file_blob (git_repository *repo,
return blob;
}
-/* FIXME: allow to do this in a separate thread because it can be slow */
-static const git_blob *
-get_cached_blob (GeanyDocument *doc)
+static void
+free_job (gpointer data)
{
- g_return_if_fail (DOC_VALID (doc));
+ AsyncBlobJob *job = data;
+
+ g_free (job->path);
+ g_slice_free1 (sizeof *job, job);
+}
+
+static gboolean
+report_work_in_idle (gpointer data)
+{
+ AsyncBlobJob *job = data;
+
+ /* update cached blob */
+ clear_cached_blob ();
+ G_file_blob = job->blob;
+
+ job->callback (job->path, job->blob, job->user_data);
- if (! G_file_blob && doc->real_path) {
- const gchar *path = doc->real_path;
+ return FALSE;
+}
+
+static gpointer
+worker_thread (gpointer data)
+{
+ GAsyncQueue *queue = data;
+ git_repository *repo = NULL;
+ GFileMonitor *monitor = NULL;
+ AsyncBlobJob *job;
+
+ while ((job = g_async_queue_pop (queue)) != QUIT_THREAD_JOB) {
+ const gchar *path = job->path;
- if (G_repo && ! g_str_has_prefix (path, git_repository_workdir (G_repo))) {
+ if (repo && ! g_str_has_prefix (path, git_repository_workdir (repo))) {
/* FIXME: this can fail with nested repositories */
- git_repository_free (G_repo);
- G_repo = NULL;
- if (G_monitor) {
- g_object_unref (G_monitor);
- G_monitor = NULL;
+ git_repository_free (repo);
+ repo = NULL;
+ if (monitor) {
+ g_object_unref (monitor);
+ monitor = NULL;
}
}
- if (! G_repo && git_repository_open_ext (&G_repo, path, 0, NULL) == 0) {
- if (git_repository_is_bare (G_repo)) {
- git_repository_free (G_repo);
- G_repo = NULL;
+ if (! repo && git_repository_open_ext (&repo, path, 0, NULL) == 0) {
+ if (git_repository_is_bare (repo)) {
+ git_repository_free (repo);
+ repo = NULL;
} else {
- GFile *file = g_file_new_for_path (git_repository_path (G_repo));
+ GFile *file = g_file_new_for_path (git_repository_path (repo));
GError *err = NULL;
- G_monitor = g_file_monitor_directory (file, 0, NULL, &err);
+ monitor = g_file_monitor_directory (file, 0, NULL, &err);
if (err) {
g_warning ("Failed to monitor Git directory: %s", err->message);
g_error_free (err);
} else {
- g_signal_connect (G_monitor, "changed",
- G_CALLBACK (on_git_dir_changed),
- GUINT_TO_POINTER (doc->id));
+ g_signal_connect (monitor, "changed",
+ G_CALLBACK (on_git_dir_changed), job->user_data);
}
g_object_unref (file);
}
}
- if (G_repo) {
- const gchar *relpath = path + strlen (git_repository_workdir (G_repo));
+ if (repo) {
+ const gchar *relpath = path + strlen (git_repository_workdir (repo));
- G_file_blob = repo_get_file_blob (G_repo, relpath);
+ job->blob = repo_get_file_blob (repo, relpath);
+ } else {
+ job->blob = NULL;
}
+
+ g_idle_add_full (G_PRIORITY_DEFAULT, report_work_in_idle, job, free_job);
}
- return G_file_blob;
+ if (monitor) {
+ g_object_unref (monitor);
+ }
+ if (repo) {
+ git_repository_free (repo);
+ }
+
+ return NULL;
}
+/* @user_data will also be used to the file monitor callback */
static void
-clear_cached_blob (void)
+get_cached_blob_async (const gchar *path,
+ BlobReadyFunc callback,
+ gpointer user_data)
{
- if (G_file_blob) {
- git_blob_free (G_file_blob);
- G_file_blob = NULL;
+ if (G_file_blob || ! path) {
+ callback (path, G_file_blob, user_data);
+ } else {
+ AsyncBlobJob *job = g_slice_alloc (sizeof *job);
+
+ job->path = g_strdup (path);
+ job->blob = NULL;
+ job->callback = callback;
+ job->user_data = user_data;
+
+ if (! G_thread) {
+ G_queue = g_async_queue_new ();
+#if GLIB_CHECK_VERSION (2, 32, 0)
+ G_thread = g_thread_new ("ggu-git/blob-worker", worker_thread, G_queue);
+#else
+ G_thread = g_thread_create (worker_thread, G_queue, NULL, NULL);
+#endif
+ }
+
+ g_async_queue_push (G_queue, job);
}
}
@@ -272,16 +352,15 @@ diff_hunk_cb (const git_diff_delta *delta,
}
static void
-update_diff (GeanyDocument *doc)
+update_diff (const gchar *path,
+ git_blob *blob,
+ gpointer data)
{
- ScintillaObject *sci;
- const git_blob *blob;
-
- g_return_if_fail (DOC_VALID (doc));
+ GeanyDocument *doc = document_get_current ();
- sci = doc->editor->sci;
- blob = get_cached_blob (doc);
- if (blob && allocate_markers (sci)) {
+ if (doc && doc->id == GPOINTER_TO_UINT (data) &&
+ blob && allocate_markers (doc->editor->sci)) {
+ ScintillaObject *sci = doc->editor->sci;
git_diff_options opts;
const gchar *buf;
size_t len;
@@ -314,7 +393,7 @@ update_diff_idle (gpointer id)
G_source_id = 0;
/* make sure the document is still valid and current */
if (doc && doc->id == GPOINTER_TO_UINT (id))
- update_diff (doc);
+ get_cached_blob_async (doc->real_path, update_diff, id);
return FALSE;
}
@@ -386,9 +465,9 @@ plugin_init (GeanyData *data)
GeanyDocument *doc;
G_file_blob = NULL;
- G_repo = NULL;
- G_monitor = NULL;
G_source_id = 0;
+ G_thread = NULL;
+ G_queue = NULL;
if (git_threads_init () != 0) {
const git_error *err = giterr_last ();
@@ -418,22 +497,21 @@ plugin_cleanup (void)
{
guint i;
- if (G_monitor) {
- g_object_unref (G_monitor);
- G_monitor = NULL;
- }
if (G_source_id) {
g_source_remove (G_source_id);
G_source_id = 0;
}
+ if (G_thread) {
+ g_async_queue_push (G_queue, QUIT_THREAD_JOB); /* notify the thread */
+ g_thread_join (G_thread);
+ G_thread = NULL;
+ g_async_queue_unref (G_queue);
+ G_queue = NULL;
+ }
if (G_file_blob) {
git_blob_free (G_file_blob);
G_file_blob = NULL;
}
- if (G_repo) {
- git_repository_free (G_repo);
- G_repo = NULL;
- }
foreach_document (i) {
release_markers (documents[i]->editor->sci);
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
More information about the Plugins-Commits
mailing list