[gnome-builder/wip/chergert/git-oop: 2/2] git: start stubbing out git worker
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/git-oop: 2/2] git: start stubbing out git worker
- Date: Mon, 25 Feb 2019 23:13:45 +0000 (UTC)
commit 2137ca1e7d07b4c5c1d4c83c80717e157bbe3678
Author: Christian Hergert <chergert redhat com>
Date: Mon Feb 25 14:56:04 2019 -0800
git: start stubbing out git worker
src/plugins/git/gbp-git-client.c | 592 ++++++++++++++++++++++++++++++++++++
src/plugins/git/gbp-git-client.h | 84 +++++
src/plugins/git/gbp-git.c | 260 ++++++++++++++++
src/plugins/git/gbp-git.h | 89 ++++++
src/plugins/git/gnome-builder-git.c | 545 +++++++++++++++++++++++++++++++++
src/plugins/git/meson.build | 22 ++
6 files changed, 1592 insertions(+)
---
diff --git a/src/plugins/git/gbp-git-client.c b/src/plugins/git/gbp-git-client.c
new file mode 100644
index 000000000..9a01eab3d
--- /dev/null
+++ b/src/plugins/git/gbp-git-client.c
@@ -0,0 +1,592 @@
+/* gbp-git-client.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-git-client"
+
+#include "config.h"
+
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <glib-unix.h>
+#include <libide-vcs.h>
+#include <jsonrpc-glib.h>
+
+#include "gbp-git-client.h"
+
+struct _GbpGitClient
+{
+ IdeObject parent;
+ GQueue get_client;
+ IdeSubprocessSupervisor *supervisor;
+ JsonrpcClient *rpc_client;
+ GFile *root_uri;
+ gint state;
+};
+
+enum {
+ STATE_INITIAL,
+ STATE_SPAWNING,
+ STATE_RUNNING,
+ STATE_SHUTDOWN,
+};
+
+typedef struct
+{
+ GbpGitClient *self;
+ GCancellable *cancellable;
+ gchar *method;
+ GVariant *params;
+ GVariant *id;
+ gulong cancel_id;
+} Call;
+
+G_DEFINE_TYPE (GbpGitClient, gbp_git_client, IDE_TYPE_OBJECT)
+
+static void
+call_free (gpointer data)
+{
+ Call *c = data;
+
+ if (c->cancel_id != 0)
+ g_cancellable_disconnect (c->cancellable, c->cancel_id);
+
+ c->cancel_id = 0;
+
+ g_clear_pointer (&c->method, g_free);
+ g_clear_pointer (&c->params, g_variant_unref);
+ g_clear_pointer (&c->id, g_variant_unref);
+ g_clear_object (&c->cancellable);
+ g_clear_object (&c->self);
+ g_slice_free (Call, c);
+}
+
+static void
+gbp_git_client_subprocess_exited (GbpGitClient *self,
+ IdeSubprocess *subprocess,
+ IdeSubprocessSupervisor *supervisor)
+{
+ IDE_ENTRY;
+
+ g_assert (GBP_IS_GIT_CLIENT (self));
+ g_assert (IDE_IS_SUBPROCESS (subprocess));
+ g_assert (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
+
+ if (self->state == STATE_RUNNING)
+ self->state = STATE_SPAWNING;
+
+ g_clear_object (&self->rpc_client);
+
+ IDE_EXIT;
+}
+
+static void
+gbp_git_client_subprocess_spawned (GbpGitClient *self,
+ IdeSubprocess *subprocess,
+ IdeSubprocessSupervisor *supervisor)
+{
+ g_autoptr(GIOStream) stream = NULL;
+ g_autoptr(GVariant) params = NULL;
+ g_autofree gchar *path = NULL;
+ g_autofree gchar *uri = NULL;
+ GOutputStream *output;
+ GInputStream *input;
+ GList *queued;
+ gint fd;
+
+ IDE_ENTRY;
+
+ g_assert (GBP_IS_GIT_CLIENT (self));
+ g_assert (IDE_IS_SUBPROCESS (subprocess));
+ g_assert (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
+ g_assert (self->rpc_client == NULL);
+
+ if (self->state == STATE_SPAWNING)
+ self->state = STATE_RUNNING;
+
+ input = ide_subprocess_get_stdout_pipe (subprocess);
+ output = ide_subprocess_get_stdin_pipe (subprocess);
+ stream = g_simple_io_stream_new (input, output);
+
+ g_assert (G_IS_UNIX_INPUT_STREAM (input));
+ g_assert (G_IS_UNIX_OUTPUT_STREAM (output));
+
+ fd = g_unix_input_stream_get_fd (G_UNIX_INPUT_STREAM (input));
+ g_unix_set_fd_nonblocking (fd, TRUE, NULL);
+
+ fd = g_unix_output_stream_get_fd (G_UNIX_OUTPUT_STREAM (output));
+ g_unix_set_fd_nonblocking (fd, TRUE, NULL);
+
+ self->rpc_client = jsonrpc_client_new (stream);
+ jsonrpc_client_set_use_gvariant (self->rpc_client, TRUE);
+
+ queued = g_steal_pointer (&self->get_client.head);
+
+ self->get_client.head = NULL;
+ self->get_client.tail = NULL;
+ self->get_client.length = 0;
+
+ for (const GList *iter = queued; iter != NULL; iter = iter->next)
+ {
+ IdeTask *task = iter->data;
+
+ ide_task_return_object (task, g_object_ref (self->rpc_client));
+ }
+
+ g_list_free_full (queued, g_object_unref);
+
+ uri = g_file_get_uri (self->root_uri);
+ path = g_file_get_path (self->root_uri);
+ params = JSONRPC_MESSAGE_NEW (
+ "rootUri", JSONRPC_MESSAGE_PUT_STRING (uri),
+ "rootPath", JSONRPC_MESSAGE_PUT_STRING (path),
+ "processId", JSONRPC_MESSAGE_PUT_INT64 (getpid ()),
+ "capabilities", "{", "}"
+ );
+
+ jsonrpc_client_call_async (self->rpc_client,
+ "initialize",
+ params,
+ NULL, NULL, NULL);
+
+ IDE_EXIT;
+}
+
+static void
+gbp_git_client_get_client_async (GbpGitClient *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+
+ g_assert (GBP_IS_GIT_CLIENT (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, gbp_git_client_get_client_async);
+
+ switch (self->state)
+ {
+ case STATE_INITIAL:
+ self->state = STATE_SPAWNING;
+ g_queue_push_tail (&self->get_client, g_steal_pointer (&task));
+ ide_subprocess_supervisor_start (self->supervisor);
+ break;
+
+ case STATE_SPAWNING:
+ g_queue_push_tail (&self->get_client, g_steal_pointer (&task));
+ break;
+
+ case STATE_RUNNING:
+ ide_task_return_object (task, g_object_ref (self->rpc_client));
+ break;
+
+ case STATE_SHUTDOWN:
+ ide_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_CLOSED,
+ "The client has been closed");
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static JsonrpcClient *
+gbp_git_client_get_client_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (GBP_IS_GIT_CLIENT (self));
+ g_assert (IDE_IS_TASK (result));
+
+ return ide_task_propagate_object (IDE_TASK (result), error);
+}
+
+static void
+gbp_git_client_parent_set (IdeObject *object,
+ IdeObject *parent)
+{
+ GbpGitClient *self = (GbpGitClient *)object;
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+ g_autoptr(GFile) workdir = NULL;
+ g_autofree gchar *cwd = NULL;
+ IdeContext *context;
+
+ g_assert (GBP_IS_GIT_CLIENT (self));
+ g_assert (!parent || IDE_IS_OBJECT (parent));
+
+ if (parent == NULL)
+ return;
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ workdir = ide_context_ref_workdir (context);
+
+ self->root_uri = g_object_ref (workdir);
+
+ if (g_file_is_native (workdir))
+ cwd = g_file_get_path (workdir);
+
+ launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDIN_PIPE);
+ if (cwd != NULL)
+ ide_subprocess_launcher_set_cwd (launcher, cwd);
+ ide_subprocess_launcher_set_clear_env (launcher, FALSE);
+ ide_subprocess_launcher_setenv (launcher, "DZL_COUNTER_DISABLE_SHM", "1", TRUE);
+#if 0
+ ide_subprocess_launcher_push_argv (launcher, "gdbserver");
+ ide_subprocess_launcher_push_argv (launcher, "localhost:8889");
+#endif
+ ide_subprocess_launcher_push_argv (launcher, PACKAGE_LIBEXECDIR"/gnome-builder-git");
+
+ self->supervisor = ide_subprocess_supervisor_new ();
+ ide_subprocess_supervisor_set_launcher (self->supervisor, launcher);
+
+ g_signal_connect_object (self->supervisor,
+ "spawned",
+ G_CALLBACK (gbp_git_client_subprocess_spawned),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->supervisor,
+ "exited",
+ G_CALLBACK (gbp_git_client_subprocess_exited),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+gbp_git_client_destroy (IdeObject *object)
+{
+ GbpGitClient *self = (GbpGitClient *)object;
+ GList *queued;
+
+ self->state = STATE_SHUTDOWN;
+
+ if (self->supervisor != NULL)
+ {
+ g_autoptr(IdeSubprocessSupervisor) supervisor = g_steal_pointer (&self->supervisor);
+
+ ide_subprocess_supervisor_stop (supervisor);
+ }
+
+ g_clear_object (&self->rpc_client);
+
+ queued = g_steal_pointer (&self->get_client.head);
+
+ self->get_client.head = NULL;
+ self->get_client.tail = NULL;
+ self->get_client.length = 0;
+
+ for (const GList *iter = queued; iter != NULL; iter = iter->next)
+ {
+ IdeTask *task = iter->data;
+
+ ide_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ "Client is disposing");
+ }
+
+ g_list_free_full (queued, g_object_unref);
+
+ IDE_OBJECT_CLASS (gbp_git_client_parent_class)->destroy (object);
+}
+
+static void
+gbp_git_client_finalize (GObject *object)
+{
+ GbpGitClient *self = (GbpGitClient *)object;
+
+ g_clear_object (&self->rpc_client);
+ g_clear_object (&self->root_uri);
+ g_clear_object (&self->supervisor);
+
+ g_assert (self->get_client.head == NULL);
+ g_assert (self->get_client.tail == NULL);
+ g_assert (self->get_client.length == 0);
+
+ G_OBJECT_CLASS (gbp_git_client_parent_class)->finalize (object);
+}
+
+static void
+gbp_git_client_class_init (GbpGitClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
+
+ object_class->finalize = gbp_git_client_finalize;
+
+ i_object_class->parent_set = gbp_git_client_parent_set;
+ i_object_class->destroy = gbp_git_client_destroy;;
+}
+
+static void
+gbp_git_client_init (GbpGitClient *self)
+{
+}
+
+GbpGitClient *
+gbp_git_client_from_context (IdeContext *context)
+{
+ GbpGitClient *ret;
+
+ g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (!ide_object_in_destruction (IDE_OBJECT (context)), NULL);
+
+ if (!(ret = ide_context_peek_child_typed (context, GBP_TYPE_GIT_CLIENT)))
+ {
+ g_autoptr(GbpGitClient) client = NULL;
+
+ client = ide_object_ensure_child_typed (IDE_OBJECT (context), GBP_TYPE_GIT_CLIENT);
+ ret = ide_context_peek_child_typed (context, GBP_TYPE_GIT_CLIENT);
+ }
+
+ return ret;
+}
+
+static void
+gbp_git_client_call_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ JsonrpcClient *rpc_client = (JsonrpcClient *)object;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (JSONRPC_IS_CLIENT (rpc_client));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ if (!jsonrpc_client_call_finish (rpc_client, result, &reply, &error))
+ ide_task_return_error (task, g_steal_pointer (&error));
+ else
+ ide_task_return_pointer (task, g_steal_pointer (&reply), (GDestroyNotify)g_variant_unref);
+}
+
+static void
+gbp_git_client_call_get_client_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GbpGitClient *self = (GbpGitClient *)object;
+ g_autoptr(JsonrpcClient) client = NULL;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+ Call *call;
+
+ g_assert (GBP_IS_GIT_CLIENT (self));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ if (!(client = gbp_git_client_get_client_finish (self, result, &error)))
+ {
+ ide_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ if (ide_task_return_error_if_cancelled (task))
+ return;
+
+ call = ide_task_get_task_data (task);
+
+ g_assert (call != NULL);
+ g_assert (call->method != NULL);
+
+ jsonrpc_client_call_with_id_async (client,
+ call->method,
+ call->params,
+ &call->id,
+ ide_task_get_cancellable (task),
+ gbp_git_client_call_cb,
+ g_object_ref (task));
+}
+
+static void
+gbp_git_client_call_cancelled (GCancellable *cancellable,
+ Call *call)
+{
+ GVariantDict dict;
+
+ g_assert (G_IS_CANCELLABLE (cancellable));
+ g_assert (call != NULL);
+ g_assert (call->cancellable == cancellable);
+ g_assert (GBP_IS_GIT_CLIENT (call->self));
+
+ /* Will be zero if cancelled immediately */
+ if (call->cancel_id == 0)
+ return;
+
+ if (call->self->rpc_client == NULL)
+ return;
+
+ /* Will be NULL if cancelled between getting build flags
+ * and submitting request. Task will also be cancelled to
+ * handle the cleanup on that side.
+ */
+ if (call->id == NULL)
+ return;
+
+ g_variant_dict_init (&dict, NULL);
+ g_variant_dict_insert_value (&dict, "id", call->id);
+
+ gbp_git_client_call_async (call->self,
+ "$/cancelRequest",
+ g_variant_dict_end (&dict),
+ NULL, NULL, NULL);
+}
+
+void
+gbp_git_client_call_async (GbpGitClient *self,
+ const gchar *method,
+ GVariant *params,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+ Call *call;
+
+ g_return_if_fail (GBP_IS_GIT_CLIENT (self));
+ g_return_if_fail (method != NULL);
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ call = g_slice_new0 (Call);
+ call->self = g_object_ref (self);
+ call->method = g_strdup (method);
+ call->params = params ? g_variant_ref_sink (params) : NULL;
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, gbp_git_client_call_async);
+ ide_task_set_task_data (task, call, call_free);
+
+ if (cancellable != NULL)
+ {
+ call->cancellable = g_object_ref (cancellable);
+ call->cancel_id = g_cancellable_connect (cancellable,
+ G_CALLBACK (gbp_git_client_call_cancelled),
+ call,
+ NULL);
+ if (ide_task_return_error_if_cancelled (task))
+ return;
+ }
+
+ gbp_git_client_get_client_async (self,
+ cancellable,
+ gbp_git_client_call_get_client_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+gbp_git_client_call_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GVariant **reply,
+ GError **error)
+{
+ g_autoptr(GVariant) v = NULL;
+ gboolean ret;
+
+ g_return_val_if_fail (GBP_IS_GIT_CLIENT (self), FALSE);
+ g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+ ret = !!(v = ide_task_propagate_pointer (IDE_TASK (result), error));
+
+ if (reply != NULL)
+ *reply = g_steal_pointer (&v);
+
+ return ret;
+}
+
+static void
+gbp_git_client_is_ignored_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GbpGitClient *self = (GbpGitClient *)object;
+ g_autoptr(IdeTask) task = user_data;
+ g_autoptr(GVariant) reply = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (GBP_IS_GIT_CLIENT (self));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (IDE_IS_TASK (task));
+
+ if (!gbp_git_client_call_finish (self, result, &reply, &error))
+ ide_task_return_error (task, g_steal_pointer (&error));
+ else
+ ide_task_return_pointer (task,
+ g_steal_pointer (&reply),
+ (GDestroyNotify)g_variant_unref);
+}
+
+void
+gbp_git_client_is_ignored_async (GbpGitClient *self,
+ const gchar *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(IdeTask) task = NULL;
+ g_autoptr(GVariant) params = NULL;
+
+ g_return_if_fail (GBP_IS_GIT_CLIENT (self));
+ g_return_if_fail (path != NULL);
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = ide_task_new (self, cancellable, callback, user_data);
+ ide_task_set_source_tag (task, gbp_git_client_is_ignored_async);
+
+ params = JSONRPC_MESSAGE_NEW (
+ "path", JSONRPC_MESSAGE_PUT_STRING (path)
+ );
+
+ gbp_git_client_call_async (self,
+ "git/isIgnored",
+ params,
+ cancellable,
+ gbp_git_client_is_ignored_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+gbp_git_client_is_ignored_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_autoptr(GVariant) ret = NULL;
+
+ g_return_val_if_fail (GBP_IS_GIT_CLIENT (self), FALSE);
+ g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+
+ if (!(ret = ide_task_propagate_pointer (IDE_TASK (result), error)))
+ return FALSE;
+
+ if (g_variant_is_of_type (ret, G_VARIANT_TYPE_BOOLEAN))
+ return g_variant_get_boolean (ret);
+
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "Expected boolean reply");
+
+ return FALSE;
+}
diff --git a/src/plugins/git/gbp-git-client.h b/src/plugins/git/gbp-git-client.h
new file mode 100644
index 000000000..b96f07840
--- /dev/null
+++ b/src/plugins/git/gbp-git-client.h
@@ -0,0 +1,84 @@
+/* gbp-git-client.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GIT_CLIENT (gbp_git_client_get_type())
+
+typedef enum
+{
+ GBP_GIT_REF_BRANCH = 1 << 0,
+ GBP_GIT_REF_TAG = 1 << 1,
+ GBP_GIT_REF_ANY = GBP_GIT_REF_BRANCH | GBP_GIT_REF_TAG,
+} GbpGitRefKind;
+
+G_DECLARE_FINAL_TYPE (GbpGitClient, gbp_git_client, GBP, GIT_CLIENT, IdeObject)
+
+GbpGitClient *gbp_git_client_from_context (IdeContext *context);
+void gbp_git_client_call_async (GbpGitClient *self,
+ const gchar *method,
+ GVariant *params,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_call_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GVariant **reply,
+ GError **error);
+void gbp_git_client_is_ignored_async (GbpGitClient *self,
+ const gchar *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_is_ignored_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_list_status_async (GbpGitClient *self,
+ const gchar *directory_or_file,
+ gboolean include_descendants,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GPtrArray *gbp_git_client_list_status_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_list_refs_by_kind_async (GbpGitClient *self,
+ GbpGitRefKind kind,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GPtrArray *gbp_git_client_list_refs_by_kind_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_client_switch_branch_async (GbpGitClient *self,
+ const gchar *branch_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_client_switch_branch_finish (GbpGitClient *self,
+ GAsyncResult *result,
+ gchar **switch_to_directory,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/plugins/git/gbp-git.c b/src/plugins/git/gbp-git.c
new file mode 100644
index 000000000..1ab230ec2
--- /dev/null
+++ b/src/plugins/git/gbp-git.c
@@ -0,0 +1,260 @@
+/* gbp-git.c
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-git"
+
+#include "config.h"
+
+#include <libgit2-glib/ggit.h>
+
+#include "gbp-git.h"
+
+struct _GbpGit
+{
+ GObject parent_instance;
+ GFile *workdir;
+ GgitRepository *repository;
+};
+
+G_DEFINE_TYPE (GbpGit, gbp_git, G_TYPE_OBJECT)
+
+static void
+gbp_git_finalize (GObject *object)
+{
+ GbpGit *self = (GbpGit *)object;
+
+ g_clear_object (&self->workdir);
+ g_clear_object (&self->repository);
+
+ G_OBJECT_CLASS (gbp_git_parent_class)->finalize (object);
+}
+
+static void
+gbp_git_class_init (GbpGitClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gbp_git_finalize;
+}
+
+static void
+gbp_git_init (GbpGit *self)
+{
+}
+
+GbpGit *
+gbp_git_new (void)
+{
+ return g_object_new (GBP_TYPE_GIT, NULL);
+}
+
+void
+gbp_git_set_workdir (GbpGit *self,
+ GFile *workdir)
+{
+ g_return_if_fail (GBP_IS_GIT (self));
+ g_return_if_fail (G_IS_FILE (workdir));
+
+ if (g_set_object (&self->workdir, workdir))
+ {
+ g_clear_object (&self->repository);
+ }
+}
+
+static void
+gbp_git_is_ignored (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_assert (G_IS_TASK (task));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ g_task_return_boolean (task, FALSE);
+}
+
+void
+gbp_git_is_ignored_async (GbpGit *self,
+ const gchar *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (GBP_IS_GIT (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gbp_git_is_ignored_async);
+ g_task_set_priority (task, G_PRIORITY_HIGH);
+ g_task_run_in_thread (task, gbp_git_is_ignored);
+}
+
+gboolean
+gbp_git_is_ignored_finish (GbpGit *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+gbp_git_switch_branch (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_assert (G_IS_TASK (task));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ g_task_return_boolean (task, FALSE);
+}
+
+void
+gbp_git_switch_branch_async (GbpGit *self,
+ const gchar *branch_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (GBP_IS_GIT (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gbp_git_switch_branch_async);
+ g_task_set_priority (task, G_PRIORITY_LOW + 1000);
+ g_task_run_in_thread (task, gbp_git_switch_branch);
+}
+
+gboolean
+gbp_git_switch_branch_finish (GbpGit *self,
+ GAsyncResult *result,
+ gchar **switch_to_directory,
+ GError **error)
+{
+ g_autoptr(GError) local_error = NULL;
+ g_autofree gchar *other_path = NULL;
+
+ g_assert (GBP_IS_GIT (self));
+ g_assert (G_IS_TASK (result));
+
+ other_path = g_task_propagate_pointer (G_TASK (result), &local_error);
+
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ return FALSE;
+ }
+
+ if (switch_to_directory != NULL)
+ *switch_to_directory = g_steal_pointer (&other_path);
+
+ return TRUE;
+}
+
+static void
+gbp_git_list_refs_by_kind (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_assert (G_IS_TASK (task));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ g_task_return_pointer (task,
+ g_ptr_array_new (),
+ (GDestroyNotify)g_ptr_array_unref);
+}
+
+void
+gbp_git_list_refs_by_kind_async (GbpGit *self,
+ GbpGitRefKind kind,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (GBP_IS_GIT (self));
+ g_assert (kind > 0);
+ g_assert (kind <= 3);
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gbp_git_list_refs_by_kind_async);
+ g_task_run_in_thread (task, gbp_git_list_refs_by_kind);
+}
+
+GPtrArray *
+gbp_git_list_refs_by_kind_finish (GbpGit *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (GBP_IS_GIT (self));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+gbp_git_list_status (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_assert (G_IS_TASK (task));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ g_task_return_pointer (task,
+ g_ptr_array_new (),
+ (GDestroyNotify)g_ptr_array_unref);
+}
+
+void
+gbp_git_list_status_async (GbpGit *self,
+ const gchar *directory_or_file,
+ gboolean include_descendants,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (GBP_IS_GIT (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, gbp_git_list_status_async);
+ g_task_run_in_thread (task, gbp_git_list_status);
+}
+
+GPtrArray *
+gbp_git_list_status_finish (GbpGit *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (GBP_IS_GIT (self));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/src/plugins/git/gbp-git.h b/src/plugins/git/gbp-git.h
new file mode 100644
index 000000000..fb9a59276
--- /dev/null
+++ b/src/plugins/git/gbp-git.h
@@ -0,0 +1,89 @@
+/* gbp-git.h
+ *
+ * Copyright 2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GIT (gbp_git_get_type())
+
+typedef enum
+{
+ GBP_GIT_REF_BRANCH = 1 << 0,
+ GBP_GIT_REF_TAG = 1 << 1,
+ GBP_GIT_REF_ANY = GBP_GIT_REF_BRANCH | GBP_GIT_REF_TAG,
+} GbpGitRefKind;
+
+typedef struct
+{
+ gchar *name;
+ GbpGitRefKind kind : 8;
+ guint is_remote : 1;
+} GbpGitRef;
+
+typedef struct
+{
+ gchar *name;
+ guint is_ignored : 1;
+} GbpGitFile;
+
+G_DECLARE_FINAL_TYPE (GbpGit, gbp_git, GBP, GIT, GObject)
+
+GbpGit *gbp_git_new (void);
+void gbp_git_set_workdir (GbpGit *self,
+ GFile *workdir);
+void gbp_git_is_ignored_async (GbpGit *self,
+ const gchar *path,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_is_ignored_finish (GbpGit *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_list_status_async (GbpGit *self,
+ const gchar *directory_or_file,
+ gboolean include_descendants,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GPtrArray *gbp_git_list_status_finish (GbpGit *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_list_refs_by_kind_async (GbpGit *self,
+ GbpGitRefKind kind,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+GPtrArray *gbp_git_list_refs_by_kind_finish (GbpGit *self,
+ GAsyncResult *result,
+ GError **error);
+void gbp_git_switch_branch_async (GbpGit *self,
+ const gchar *branch_name,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gbp_git_switch_branch_finish (GbpGit *self,
+ GAsyncResult *result,
+ gchar **switch_to_directory,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/plugins/git/gnome-builder-git.c b/src/plugins/git/gnome-builder-git.c
new file mode 100644
index 000000000..c7d024b14
--- /dev/null
+++ b/src/plugins/git/gnome-builder-git.c
@@ -0,0 +1,545 @@
+/* gnome-builder-git.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+/* Prologue {{{1 */
+
+#define G_LOG_DOMAIN "gnome-builder-git"
+
+#include "config.h"
+
+#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <glib-unix.h>
+#include <jsonrpc-glib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "gbp-git.h"
+
+static guint in_flight;
+static gboolean closing;
+static GMainLoop *main_loop;
+static GQueue ops;
+
+/* Client Operations {{{1 */
+
+typedef struct
+{
+ volatile gint ref_count;
+ JsonrpcClient *client;
+ GVariant *id;
+ GCancellable *cancellable;
+ GList link;
+} ClientOp;
+
+static void
+client_op_bad_params (ClientOp *op)
+{
+ g_assert (op != NULL);
+
+ jsonrpc_client_reply_error_async (op->client,
+ op->id,
+ JSONRPC_CLIENT_ERROR_INVALID_PARAMS,
+ "Invalid parameters for method call",
+ NULL, NULL, NULL);
+ jsonrpc_client_close (op->client, NULL, NULL);
+}
+
+static void
+client_op_error (ClientOp *op,
+ const GError *error)
+{
+ g_assert (op != NULL);
+
+ jsonrpc_client_reply_error_async (op->client,
+ op->id,
+ error->code,
+ error->message,
+ NULL, NULL, NULL);
+}
+
+static ClientOp *
+client_op_ref (ClientOp *op)
+{
+ g_return_val_if_fail (op != NULL, NULL);
+ g_return_val_if_fail (op->ref_count > 0, NULL);
+
+ g_atomic_int_inc (&op->ref_count);
+
+ return op;
+}
+
+static void
+client_op_unref (ClientOp *op)
+{
+ g_return_if_fail (op != NULL);
+ g_return_if_fail (op->ref_count > 0);
+
+ if (g_atomic_int_dec_and_test (&op->ref_count))
+ {
+ g_clear_object (&op->cancellable);
+ g_clear_object (&op->client);
+ g_clear_pointer (&op->id, g_variant_unref);
+ g_queue_unlink (&ops, &op->link);
+ g_slice_free (ClientOp, op);
+
+ in_flight--;
+
+ if (closing && in_flight == 0)
+ g_main_loop_quit (main_loop);
+ }
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClientOp, client_op_unref)
+
+static ClientOp *
+client_op_new (JsonrpcClient *client,
+ GVariant *id)
+{
+ ClientOp *op;
+
+ op = g_slice_new0 (ClientOp);
+ op->id = g_variant_ref (id);
+ op->client = g_object_ref (client);
+ op->cancellable = g_cancellable_new ();
+ op->ref_count = 1;
+ op->link.data = op;
+
+ g_queue_push_tail_link (&ops, &op->link);
+
+ ++in_flight;
+
+ return op;
+}
+
+static void
+handle_reply_cb (JsonrpcClient *client,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ g_autoptr(ClientOp) op = user_data;
+
+ g_assert (JSONRPC_IS_CLIENT (client));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (op != NULL);
+ g_assert (op->client == client);
+
+ if (!jsonrpc_client_reply_finish (client, result, &error))
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("Reply failed: %s", error->message);
+ }
+}
+
+static void
+client_op_reply (ClientOp *op,
+ GVariant *reply)
+{
+ g_assert (op != NULL);
+ g_assert (op->client != NULL);
+
+ jsonrpc_client_reply_async (op->client,
+ op->id,
+ reply,
+ op->cancellable,
+ (GAsyncReadyCallback)handle_reply_cb,
+ client_op_ref (op));
+}
+
+/* Initialize {{{1 */
+
+static void
+handle_initialize (JsonrpcServer *server,
+ JsonrpcClient *client,
+ const gchar *method,
+ GVariant *id,
+ GVariant *params,
+ GbpGit *git)
+{
+ g_autoptr(ClientOp) op = NULL;
+ const gchar *uri = NULL;
+
+ g_assert (JSONRPC_IS_SERVER (server));
+ g_assert (JSONRPC_IS_CLIENT (client));
+ g_assert (g_str_equal (method, "initialize"));
+ g_assert (id != NULL);
+ g_assert (GBP_IS_GIT (git));
+
+ op = client_op_new (client, id);
+
+ if (JSONRPC_MESSAGE_PARSE (params, "rootUri", JSONRPC_MESSAGE_GET_STRING (&uri)))
+ {
+ g_autoptr(GFile) file = g_file_new_for_uri (uri);
+
+ gbp_git_set_workdir (git, file);
+ }
+
+ client_op_reply (op, NULL);
+}
+
+/* Cancel Request {{{1 */
+
+static void
+handle_cancel_request (JsonrpcServer *server,
+ JsonrpcClient *client,
+ const gchar *method,
+ GVariant *id,
+ GVariant *params,
+ GbpGit *git)
+{
+ g_autoptr(ClientOp) op = NULL;
+ g_autoptr(GVariant) cid = NULL;
+
+ g_assert (JSONRPC_IS_SERVER (server));
+ g_assert (JSONRPC_IS_CLIENT (client));
+ g_assert (g_str_equal (method, "$/cancelRequest"));
+ g_assert (id != NULL);
+ g_assert (GBP_IS_GIT (git));
+
+ op = client_op_new (client, id);
+
+ if (params == NULL ||
+ !(cid = g_variant_lookup_value (params, "id", NULL)) ||
+ g_variant_equal (id, cid))
+ {
+ client_op_bad_params (op);
+ return;
+ }
+
+ /* Lookup in-flight command to cancel it */
+
+ for (const GList *iter = ops.head; iter != NULL; iter = iter->next)
+ {
+ ClientOp *ele = iter->data;
+
+ if (g_variant_equal (ele->id, cid))
+ {
+ g_cancellable_cancel (ele->cancellable);
+ break;
+ }
+ }
+
+ client_op_reply (op, NULL);
+}
+
+/* Main and Server Setup {{{1 */
+
+static void
+on_client_closed_cb (JsonrpcServer *server,
+ JsonrpcClient *client,
+ gpointer user_data)
+{
+ g_assert (JSONRPC_IS_SERVER (server));
+ g_assert (JSONRPC_IS_CLIENT (client));
+
+ closing = TRUE;
+
+ if (in_flight == 0)
+ g_main_loop_quit (main_loop);
+}
+
+static void
+log_handler_cb (const gchar *log_domain,
+ GLogLevelFlags level,
+ const gchar *message,
+ gpointer user_data)
+{
+ /* Only write to stderr so that we don't interrupt IPC */
+ g_printerr ("%s: %s\n", log_domain, message);
+}
+
+/* Is Ignored Handler {{{1 */
+
+static void
+handle_is_ignored_cb (GbpGit *git,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(ClientOp) op = user_data;
+ g_autoptr(GError) error = NULL;
+ gboolean ret;
+
+ g_assert (GBP_IS_GIT (git));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (op != NULL);
+
+ ret = gbp_git_is_ignored_finish (git, result, &error);
+
+ if (error != NULL)
+ client_op_error (op, error);
+ else
+ client_op_reply (op, g_variant_new_boolean (ret));
+}
+
+static void
+handle_is_ignored (JsonrpcServer *server,
+ JsonrpcClient *client,
+ const gchar *method,
+ GVariant *id,
+ GVariant *params,
+ GbpGit *git)
+{
+ g_autoptr(ClientOp) op = NULL;
+ const gchar *path = NULL;
+
+ g_assert (JSONRPC_IS_SERVER (server));
+ g_assert (JSONRPC_IS_CLIENT (client));
+ g_assert (g_str_equal (method, "git/isIgnored"));
+ g_assert (id != NULL);
+ g_assert (GBP_IS_GIT (git));
+
+ op = client_op_new (client, id);
+
+ if (!JSONRPC_MESSAGE_PARSE (params, "path", JSONRPC_MESSAGE_GET_STRING (&path)))
+ {
+ client_op_bad_params (op);
+ return;
+ }
+
+ gbp_git_is_ignored_async (git,
+ path,
+ op->cancellable,
+ (GAsyncReadyCallback)handle_is_ignored_cb,
+ client_op_ref (op));
+}
+
+/* Switch Branch Handler {{{1 */
+
+static void
+handle_switch_branch_cb (GbpGit *git,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(ClientOp) op = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *switch_to_directory = NULL;
+ GVariantDict reply;
+
+ g_assert (GBP_IS_GIT (git));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (op != NULL);
+
+ if (!gbp_git_switch_branch_finish (git, result, &switch_to_directory, &error))
+ {
+ client_op_error (op, error);
+ return;
+ }
+
+ g_variant_dict_init (&reply, NULL);
+
+ if (switch_to_directory != NULL)
+ g_variant_dict_insert (&reply, "switch-to-directory", "s", switch_to_directory);
+
+ client_op_reply (op, g_variant_dict_end (&reply));
+}
+
+static void
+handle_switch_branch (JsonrpcServer *server,
+ JsonrpcClient *client,
+ const gchar *method,
+ GVariant *id,
+ GVariant *params,
+ GbpGit *git)
+{
+ g_autoptr(ClientOp) op = NULL;
+ const gchar *name = NULL;
+
+ g_assert (JSONRPC_IS_SERVER (server));
+ g_assert (JSONRPC_IS_CLIENT (client));
+ g_assert (g_str_equal (method, "git/switchBranch"));
+ g_assert (id != NULL);
+ g_assert (GBP_IS_GIT (git));
+
+ op = client_op_new (client, id);
+
+ if (!JSONRPC_MESSAGE_PARSE (params, "name", JSONRPC_MESSAGE_GET_STRING (&name)))
+ {
+ client_op_bad_params (op);
+ return;
+ }
+
+ gbp_git_switch_branch_async (git,
+ name,
+ op->cancellable,
+ (GAsyncReadyCallback)handle_switch_branch_cb,
+ client_op_ref (op));
+}
+
+/* List Refs by Kind Handler {{{1 */
+
+static const gchar *
+ref_kind_string (GbpGitRefKind kind)
+{
+ switch (kind)
+ {
+ case GBP_GIT_REF_BRANCH: return "branch";
+ case GBP_GIT_REF_TAG: return "tag";
+ case GBP_GIT_REF_ANY: return "any";
+ default: return "";
+ }
+}
+
+static GbpGitRefKind
+parse_kind_string (const gchar *str)
+{
+ if (str == NULL)
+ return 0;
+
+ if (g_str_equal (str, "branch"))
+ return GBP_GIT_REF_BRANCH;
+
+ if (g_str_equal (str, "tag"))
+ return GBP_GIT_REF_TAG;
+
+ if (g_str_equal (str, "any"))
+ return GBP_GIT_REF_ANY;
+
+ return 0;
+}
+
+static void
+handle_list_refs_by_kind_cb (GbpGit *git,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(ClientOp) op = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GPtrArray) refs = NULL;
+ GVariantBuilder builder;
+
+ g_assert (GBP_IS_GIT (git));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (op != NULL);
+
+ if (!(refs = gbp_git_list_refs_by_kind_finish (git, result, &error)))
+ {
+ client_op_error (op, error);
+ return;
+ }
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+ for (guint i = 0; i < refs->len; i++)
+ {
+ const GbpGitRef *ref = g_ptr_array_index (refs, i);
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add_parsed (&builder, "{%s,<%s>}", "name", ref->name);
+ g_variant_builder_add_parsed (&builder, "{%s,<%s>}", "kind", ref_kind_string (ref->kind));
+ g_variant_builder_add_parsed (&builder, "{%s,<%b>}", "is-remote", ref->is_remote);
+ g_variant_builder_close (&builder);
+ }
+
+ client_op_reply (op, g_variant_builder_end (&builder));
+}
+
+static void
+handle_list_refs_by_kind (JsonrpcServer *server,
+ JsonrpcClient *client,
+ const gchar *method,
+ GVariant *id,
+ GVariant *params,
+ GbpGit *git)
+{
+ g_autoptr(ClientOp) op = NULL;
+ const gchar *kind = NULL;
+ GbpGitRefKind kind_enum;
+
+ g_assert (JSONRPC_IS_SERVER (server));
+ g_assert (JSONRPC_IS_CLIENT (client));
+ g_assert (g_str_equal (method, "git/listRefsByKind"));
+ g_assert (id != NULL);
+ g_assert (GBP_IS_GIT (git));
+
+ op = client_op_new (client, id);
+
+ if (!JSONRPC_MESSAGE_PARSE (params, "kind", JSONRPC_MESSAGE_GET_STRING (&kind)) ||
+ !(kind_enum = parse_kind_string (kind)))
+ {
+ client_op_bad_params (op);
+ return;
+ }
+
+ gbp_git_list_refs_by_kind_async (git,
+ kind_enum,
+ op->cancellable,
+ (GAsyncReadyCallback)handle_list_refs_by_kind_cb,
+ client_op_ref (op));
+}
+
+/* Main Loop and Setup {{{1 */
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autoptr(GInputStream) input = g_unix_input_stream_new (STDIN_FILENO, FALSE);
+ g_autoptr(GOutputStream) output = g_unix_output_stream_new (STDOUT_FILENO, FALSE);
+ g_autoptr(GIOStream) stream = g_simple_io_stream_new (input, output);
+ g_autoptr(JsonrpcServer) server = NULL;
+ g_autoptr(GbpGit) git = NULL;
+ g_autoptr(GError) error = NULL;
+
+ /* Always ignore SIGPIPE */
+ signal (SIGPIPE, SIG_IGN);
+
+ g_set_prgname ("gnome-builder-git");
+
+ /* redirect logging to stderr */
+ g_log_set_handler (NULL, G_LOG_LEVEL_MASK, log_handler_cb, NULL);
+
+ main_loop = g_main_loop_new (NULL, FALSE);
+ git = gbp_git_new ();
+ server = jsonrpc_server_new ();
+
+ if (!g_unix_set_fd_nonblocking (STDIN_FILENO, TRUE, &error) ||
+ !g_unix_set_fd_nonblocking (STDOUT_FILENO, TRUE, &error))
+ {
+ g_printerr ("Failed to set FD non-blocking: %s\n", error->message);
+ return EXIT_FAILURE;
+ }
+
+ g_signal_connect (server,
+ "client-closed",
+ G_CALLBACK (on_client_closed_cb),
+ NULL);
+
+#define ADD_HANDLER(method, func) \
+ jsonrpc_server_add_handler (server, method, (JsonrpcServerHandler)func, g_object_ref (git), g_object_unref)
+
+ ADD_HANDLER ("initialize", handle_initialize);
+ ADD_HANDLER ("git/isIgnored", handle_is_ignored);
+ ADD_HANDLER ("git/listRefsByKind", handle_list_refs_by_kind);
+ ADD_HANDLER ("git/switchBranch", handle_switch_branch);
+ ADD_HANDLER ("$/cancelRequest", handle_cancel_request);
+
+#undef ADD_HANDLER
+
+ jsonrpc_server_accept_io_stream (server, stream);
+
+ g_main_loop_run (main_loop);
+
+ return EXIT_SUCCESS;
+}
+
+/* vim:set foldmethod=marker: */
diff --git a/src/plugins/git/meson.build b/src/plugins/git/meson.build
index 79dc08181..a89cde277 100644
--- a/src/plugins/git/meson.build
+++ b/src/plugins/git/meson.build
@@ -5,6 +5,7 @@ plugins_sources += files([
'gbp-git-branch.c',
'gbp-git-buffer-addin.c',
'gbp-git-buffer-change-monitor.c',
+ 'gbp-git-client.c',
'gbp-git-dependency-updater.c',
'gbp-git-index-monitor.c',
'gbp-git-pipeline-addin.c',
@@ -19,6 +20,11 @@ plugins_sources += files([
'line-cache.c',
])
+gnome_builder_git_sources = [
+ 'gnome-builder-git.c',
+ 'gbp-git.c',
+]
+
plugin_git_resources = gnome.compile_resources(
'git-resources',
'git.gresource.xml',
@@ -27,4 +33,20 @@ plugin_git_resources = gnome.compile_resources(
plugins_sources += plugin_git_resources[0]
+gnome_builder_git_deps = [
+ libgit_dep,
+ libjsonrpc_glib_dep,
+ libgiounix_dep,
+]
+
+executable('gnome-builder-git', gnome_builder_git_sources,
+ dependencies: gnome_builder_git_deps,
+ gui_app: false,
+ install: true,
+ install_dir: get_option('libexecdir'),
+ c_args: exe_c_args,
+ link_args: exe_link_args,
+ install_rpath: pkglibdir_abs,
+)
+
endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]