[gnome-builder/wip/chergert/git-oop: 2/2] git: start stubbing out git worker



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]