[gnome-builder] daemon: add gnome-builder-git daemon



commit 738cf8858ab7f90d7eb6098b9335c4a5443ad25b
Author: Christian Hergert <chergert redhat com>
Date:   Fri Apr 5 17:34:19 2019 -0700

    daemon: add gnome-builder-git daemon

 src/plugins/git/daemon/README.md                   |   10 +
 src/plugins/git/daemon/gnome-builder-git.c         |  106 ++
 .../git/daemon/ipc-git-change-monitor-impl.c       |  345 ++++++
 .../git/daemon/ipc-git-change-monitor-impl.h       |   36 +
 src/plugins/git/daemon/ipc-git-config-impl.c       |  137 +++
 src/plugins/git/daemon/ipc-git-config-impl.h       |   35 +
 src/plugins/git/daemon/ipc-git-index-monitor.c     |  237 ++++
 src/plugins/git/daemon/ipc-git-index-monitor.h     |   33 +
 src/plugins/git/daemon/ipc-git-remote-callbacks.c  |  181 ++++
 src/plugins/git/daemon/ipc-git-remote-callbacks.h  |   36 +
 src/plugins/git/daemon/ipc-git-repository-impl.c   | 1135 ++++++++++++++++++++
 src/plugins/git/daemon/ipc-git-repository-impl.h   |   34 +
 src/plugins/git/daemon/ipc-git-service-impl.c      |  300 ++++++
 src/plugins/git/daemon/ipc-git-service-impl.h      |   33 +
 src/plugins/git/daemon/ipc-git-types.h             |   47 +
 src/plugins/git/daemon/ipc-git-util.h              |   36 +
 src/plugins/git/daemon/line-cache.c                |  227 ++++
 src/plugins/git/daemon/line-cache.h                |   60 ++
 src/plugins/git/daemon/meson.build                 |   71 ++
 .../daemon/org.gnome.Builder.Git.ChangeMonitor.xml |   34 +
 .../git/daemon/org.gnome.Builder.Git.Config.xml    |   35 +
 .../git/daemon/org.gnome.Builder.Git.Progress.xml  |   27 +
 .../daemon/org.gnome.Builder.Git.Repository.xml    |  169 +++
 .../git/daemon/org.gnome.Builder.Git.Service.xml   |   73 ++
 src/plugins/git/daemon/test-git.c                  |  507 +++++++++
 src/plugins/git/meson.build                        |    2 +
 26 files changed, 3946 insertions(+)
---
diff --git a/src/plugins/git/daemon/README.md b/src/plugins/git/daemon/README.md
new file mode 100644
index 000000000..09277834d
--- /dev/null
+++ b/src/plugins/git/daemon/README.md
@@ -0,0 +1,10 @@
+# gnome-builder-git
+
+This is a GPLv2.0+ licensed daemon that wraps libgit2-glib and libgit2.
+It provides some of the services that are necessary for an IDE using Git.
+
+The design is similar to Language Server Protocol, in that RPCs are performed over stdin/stdout to a 
subprocess daemon.
+However, we use the DBus serialization format instead of JSON/JSONRPC for various reasons including 
efficiency and design.
+For example, we can extend our implementation to trivially include FD passing for future extensions.
+Unlike JSONRPC, we have gdbus-codegen to generate the IPC stubs giving us a clean, minimal daemon 
implementation and convenient APIs for the consumer.
+All of the RPCs are defined in the various DBus interface description XML files.
diff --git a/src/plugins/git/daemon/gnome-builder-git.c b/src/plugins/git/daemon/gnome-builder-git.c
new file mode 100644
index 000000000..b70b72b07
--- /dev/null
+++ b/src/plugins/git/daemon/gnome-builder-git.c
@@ -0,0 +1,106 @@
+/* gnome-builder-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
+ */
+
+#include <glib-unix.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <libgit2-glib/ggit.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+
+#include "ipc-git-service.h"
+#include "ipc-git-service-impl.h"
+
+static GDBusConnection *
+create_connection (GIOStream  *stream,
+                   GMainLoop  *main_loop,
+                   GError    **error)
+{
+  GDBusConnection *ret;
+
+  g_assert (G_IS_IO_STREAM (stream));
+  g_assert (main_loop != NULL);
+  g_assert (error != NULL);
+
+  if ((ret = g_dbus_connection_new_sync (stream, NULL,
+                                          G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
+                                          NULL, NULL, error)))
+    {
+      g_dbus_connection_set_exit_on_close (ret, FALSE);
+      g_signal_connect_swapped (ret, "closed", G_CALLBACK (g_main_loop_quit), main_loop);
+    }
+
+  return ret;
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+  g_autoptr(GDBusConnection) connection = NULL;
+  g_autoptr(IpcGitService) service = NULL;
+  g_autoptr(GOutputStream) stdout_stream = NULL;
+  g_autoptr(GInputStream) stdin_stream = NULL;
+  g_autoptr(GIOStream) stream = NULL;
+  g_autoptr(GMainLoop) main_loop = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_set_prgname ("gnome-builder-git");
+  g_set_application_name ("gnome-builder-git");
+
+  prctl (PR_SET_PDEATHSIG, SIGTERM);
+
+  signal (SIGPIPE, SIG_IGN);
+
+  ggit_init ();
+
+  if (!g_unix_set_fd_nonblocking (STDIN_FILENO, TRUE, &error) ||
+      !g_unix_set_fd_nonblocking (STDOUT_FILENO, TRUE, &error))
+    goto error;
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+  stdin_stream = g_unix_input_stream_new (STDIN_FILENO, FALSE);
+  stdout_stream = g_unix_output_stream_new (STDOUT_FILENO, FALSE);
+  stream = g_simple_io_stream_new (stdin_stream, stdout_stream);
+
+  if (!(connection = create_connection (stream, main_loop, &error)))
+    goto error;
+
+  service = ipc_git_service_impl_new ();
+
+  if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (service),
+                                         connection,
+                                         "/org/gnome/Builder/Git",
+                                         &error))
+    goto error;
+
+  g_dbus_connection_start_message_processing (connection);
+  g_main_loop_run (main_loop);
+
+  return EXIT_SUCCESS;
+
+error:
+  if (error != NULL)
+    g_printerr ("%s\n", error->message);
+
+  return EXIT_FAILURE;
+}
diff --git a/src/plugins/git/daemon/ipc-git-change-monitor-impl.c 
b/src/plugins/git/daemon/ipc-git-change-monitor-impl.c
new file mode 100644
index 000000000..656d1888c
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-change-monitor-impl.c
@@ -0,0 +1,345 @@
+/* ipc-git-change-monitor-impl.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 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 "ipc-git-change-monitor-impl"
+
+#include "ipc-git-change-monitor-impl.h"
+#include "line-cache.h"
+
+/* Some code from this file is loosely based around the git-diff
+ * plugin from Atom. Namely, API usage for iterating through hunks
+ * containing changes. It's license is provided below.
+ */
+
+/*
+ * Copyright (c) 2014 GitHub Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+struct _IpcGitChangeMonitorImpl
+{
+  IpcGitChangeMonitorSkeleton  parent;
+  gchar                       *path;
+  GgitRepository              *repository;
+  GBytes                      *contents;
+  GgitObject                  *blob;
+};
+
+typedef struct
+{
+  gint old_start;
+  gint old_lines;
+  gint new_start;
+  gint new_lines;
+} Range;
+
+static gint
+diff_hunk_cb (GgitDiffDelta *delta,
+              GgitDiffHunk  *hunk,
+              gpointer       user_data)
+{
+  GArray *ranges = user_data;
+  Range range;
+
+  g_assert (delta != NULL);
+  g_assert (hunk != NULL);
+  g_assert (ranges != NULL);
+
+  range.old_start = ggit_diff_hunk_get_old_start (hunk);
+  range.old_lines = ggit_diff_hunk_get_old_lines (hunk);
+  range.new_start = ggit_diff_hunk_get_new_start (hunk);
+  range.new_lines = ggit_diff_hunk_get_new_lines (hunk);
+
+  g_array_append_val (ranges, range);
+
+  return 0;
+}
+
+static GgitObject *
+ipc_git_change_monitor_impl_load_blob (IpcGitChangeMonitorImpl  *self,
+                                       GError                  **error)
+{
+  g_autofree gchar *path = NULL;
+  GgitOId *entry_oid = NULL;
+  GgitOId *oid = NULL;
+  GgitObject *blob = NULL;
+  GgitObject *commit = NULL;
+  GgitRef *head = NULL;
+  GgitTree *tree = NULL;
+  GgitTreeEntry *entry = NULL;
+
+  g_assert (IPC_IS_GIT_CHANGE_MONITOR_IMPL (self));
+
+  if (self->blob != NULL)
+    return g_object_ref (self->blob);
+
+  g_object_get (self, "path", &path, NULL);
+
+  if (self->repository == NULL || path == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "No repository to access file contents");
+      return NULL;
+    }
+
+  if (!(head = ggit_repository_get_head (self->repository, error)) ||
+      !(oid = ggit_ref_get_target (head)) ||
+      !(commit = ggit_repository_lookup (self->repository, oid, GGIT_TYPE_COMMIT, error)) ||
+      !(tree = ggit_commit_get_tree (GGIT_COMMIT (commit))) ||
+      !(entry = ggit_tree_get_by_path (tree, path, error)) ||
+      !(entry_oid = ggit_tree_entry_get_id (entry)) ||
+      !(blob = ggit_repository_lookup (self->repository, entry_oid, GGIT_TYPE_BLOB, error)))
+    goto cleanup;
+
+  g_set_object (&self->blob, blob);
+
+cleanup:
+  g_clear_pointer (&entry_oid, ggit_oid_free);
+  g_clear_pointer (&entry, ggit_tree_entry_unref);
+  g_clear_object (&tree);
+  g_clear_object (&commit);
+  g_clear_pointer (&oid, ggit_oid_free);
+  g_clear_object (&head);
+
+  return g_steal_pointer (&blob);
+}
+
+static gboolean
+ipc_git_change_monitor_impl_handle_update_content (IpcGitChangeMonitor   *monitor,
+                                                   GDBusMethodInvocation *invocation,
+                                                   const gchar           *contents)
+{
+  IpcGitChangeMonitorImpl *self = (IpcGitChangeMonitorImpl *)monitor;
+
+  g_assert (IPC_IS_GIT_CHANGE_MONITOR_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (contents != NULL);
+
+  /* Make a copy, but retain the trailing \0 */
+  g_clear_pointer (&self->contents, g_bytes_unref);
+  self->contents = g_bytes_new_take (g_strdup (contents), strlen (contents));
+
+  ipc_git_change_monitor_complete_update_content (monitor, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_change_monitor_impl_handle_list_changes (IpcGitChangeMonitor   *monitor,
+                                                 GDBusMethodInvocation *invocation)
+{
+  IpcGitChangeMonitorImpl *self = (IpcGitChangeMonitorImpl *)monitor;
+  g_autoptr(GgitDiffOptions) options = NULL;
+  g_autoptr(GgitObject) blob = NULL;
+  g_autoptr(GArray) ranges = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(LineCache) cache = NULL;
+  g_autoptr(GVariant) ret = NULL;
+  const guint8 *data;
+  gsize len = 0;
+
+  g_assert (IPC_IS_GIT_CHANGE_MONITOR_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  if (self->contents == NULL)
+    {
+      g_set_error (&error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_INITIALIZED,
+                   "No contents have been set to diff");
+      goto gerror;
+    }
+
+  if (!(blob = ipc_git_change_monitor_impl_load_blob (self, &error)))
+    goto gerror;
+
+  ranges = g_array_new (FALSE, FALSE, sizeof (Range));
+  options = ggit_diff_options_new ();
+  ggit_diff_options_set_n_context_lines (options, 0);
+
+  data = g_bytes_get_data (self->contents, &len);
+
+  ggit_diff_blob_to_buffer (GGIT_BLOB (blob),
+                            self->path,
+                            data,
+                            len,
+                            self->path,
+                            options,
+                            NULL,         /* File Callback */
+                            NULL,         /* Binary Callback */
+                            diff_hunk_cb, /* Hunk Callback */
+                            NULL,
+                            ranges,
+                            &error);
+
+  if (error != NULL)
+    goto gerror;
+
+  cache = line_cache_new ();
+
+  for (guint i = 0; i < ranges->len; i++)
+    {
+      const Range *range = &g_array_index (ranges, Range, i);
+      gint start_line = range->new_start - 1;
+      gint end_line = range->new_start + range->new_lines - 1;
+
+      if (range->old_lines == 0 && range->new_lines > 0)
+        {
+          line_cache_mark_range (cache, start_line, end_line, LINE_MARK_ADDED);
+        }
+      else if (range->new_lines == 0 && range->old_lines > 0)
+        {
+          if (start_line < 0)
+            line_cache_mark_range (cache, 0, 0, LINE_MARK_PREVIOUS_REMOVED);
+          else
+            line_cache_mark_range (cache, start_line + 1, start_line + 1, LINE_MARK_REMOVED);
+        }
+      else
+        {
+          line_cache_mark_range (cache, start_line, end_line, LINE_MARK_CHANGED);
+        }
+    }
+
+  ret = line_cache_to_variant (cache);
+
+  g_assert (ret != NULL);
+  g_assert (g_variant_is_of_type (ret, G_VARIANT_TYPE ("a(uu)")));
+
+gerror:
+  g_assert (ret != NULL || error != NULL);
+
+  if (g_error_matches (error, GGIT_ERROR, GIT_ENOTFOUND))
+    {
+      g_clear_error (&error);
+      ret = g_variant_take_ref (g_variant_new_array (G_VARIANT_TYPE ("(uu)"), NULL, 0));
+    }
+
+  if (error != NULL && error->domain != G_IO_ERROR)
+    {
+      g_autoptr(GError) wrapped = g_steal_pointer (&error);
+
+      g_set_error (&error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_FAILED,
+                   "The operation failed. The original error was \"%s\"",
+                   wrapped->message);
+    }
+
+  if (error != NULL)
+    g_dbus_method_invocation_return_gerror (invocation, error);
+  else
+    ipc_git_change_monitor_complete_list_changes (monitor, invocation, ret);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_change_monitor_impl_handle_close (IpcGitChangeMonitor   *monitor,
+                                          GDBusMethodInvocation *invocation)
+{
+  g_assert (IPC_IS_GIT_CHANGE_MONITOR_IMPL (monitor));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  /* Repository will drop it's reference from the hashtable */
+  ipc_git_change_monitor_emit_closed (monitor);
+
+  g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (monitor));
+  ipc_git_change_monitor_complete_close (monitor, invocation);
+
+  return TRUE;
+}
+
+static void
+git_change_monitor_iface_init (IpcGitChangeMonitorIface *iface)
+{
+  iface->handle_update_content = ipc_git_change_monitor_impl_handle_update_content;
+  iface->handle_list_changes = ipc_git_change_monitor_impl_handle_list_changes;
+  iface->handle_close = ipc_git_change_monitor_impl_handle_close;
+}
+
+G_DEFINE_TYPE_WITH_CODE (IpcGitChangeMonitorImpl, ipc_git_change_monitor_impl, 
IPC_TYPE_GIT_CHANGE_MONITOR_SKELETON,
+                         G_IMPLEMENT_INTERFACE (IPC_TYPE_GIT_CHANGE_MONITOR, git_change_monitor_iface_init))
+
+static void
+ipc_git_change_monitor_impl_finalize (GObject *object)
+{
+  IpcGitChangeMonitorImpl *self = (IpcGitChangeMonitorImpl *)object;
+
+  g_clear_object (&self->blob);
+  g_clear_object (&self->repository);
+  g_clear_pointer (&self->contents, g_bytes_unref);
+  g_clear_pointer (&self->path, g_free);
+
+  G_OBJECT_CLASS (ipc_git_change_monitor_impl_parent_class)->finalize (object);
+}
+
+static void
+ipc_git_change_monitor_impl_class_init (IpcGitChangeMonitorImplClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ipc_git_change_monitor_impl_finalize;
+}
+
+static void
+ipc_git_change_monitor_impl_init (IpcGitChangeMonitorImpl *self)
+{
+}
+
+IpcGitChangeMonitor *
+ipc_git_change_monitor_impl_new (GgitRepository *repository,
+                                 const gchar    *path)
+{
+  IpcGitChangeMonitorImpl *ret;
+
+  ret = g_object_new (IPC_TYPE_GIT_CHANGE_MONITOR_IMPL,
+                      "path", path,
+                      NULL);
+  ret->repository = g_object_ref (repository);
+
+  return IPC_GIT_CHANGE_MONITOR (g_steal_pointer (&ret));
+}
+
+void
+ipc_git_change_monitor_impl_reset (IpcGitChangeMonitorImpl *self)
+{
+  g_return_if_fail (IPC_IS_GIT_CHANGE_MONITOR_IMPL (self));
+
+  g_clear_object (&self->blob);
+}
diff --git a/src/plugins/git/daemon/ipc-git-change-monitor-impl.h 
b/src/plugins/git/daemon/ipc-git-change-monitor-impl.h
new file mode 100644
index 000000000..768a51181
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-change-monitor-impl.h
@@ -0,0 +1,36 @@
+/* ipc-git-change-monitor-impl.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 <libgit2-glib/ggit.h>
+
+#include "ipc-git-change-monitor.h"
+
+G_BEGIN_DECLS
+
+#define IPC_TYPE_GIT_CHANGE_MONITOR_IMPL (ipc_git_change_monitor_impl_get_type())
+
+G_DECLARE_FINAL_TYPE (IpcGitChangeMonitorImpl, ipc_git_change_monitor_impl, IPC, GIT_CHANGE_MONITOR_IMPL, 
IpcGitChangeMonitorSkeleton)
+
+IpcGitChangeMonitor *ipc_git_change_monitor_impl_new (GgitRepository *repository,
+                                                      const gchar    *path);
+
+G_END_DECLS
diff --git a/src/plugins/git/daemon/ipc-git-config-impl.c b/src/plugins/git/daemon/ipc-git-config-impl.c
new file mode 100644
index 000000000..82315319e
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-config-impl.c
@@ -0,0 +1,137 @@
+/* ipc-git-config-impl.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 "ipc-git-config-impl"
+
+#include "ipc-git-config-impl.h"
+#include "ipc-git-util.h"
+
+struct _IpcGitConfigImpl
+{
+  IpcGitConfigSkeleton parent;
+  GgitConfig *config;
+};
+
+static gboolean
+ipc_git_config_impl_handle_read_key (IpcGitConfig          *config,
+                                     GDBusMethodInvocation *invocation,
+                                     const gchar           *key)
+{
+  IpcGitConfigImpl *self = (IpcGitConfigImpl *)config;
+  g_autoptr(GgitConfig) snapshot = NULL;
+  g_autoptr(GError) error = NULL;
+  const gchar *value;
+
+  g_assert (IPC_IS_GIT_CONFIG (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (key != NULL);
+
+  if (!(snapshot = ggit_config_snapshot (self->config, &error)) ||
+      !(value = ggit_config_get_string (snapshot, key, &error)))
+    g_dbus_method_invocation_return_dbus_error (invocation,
+                                                "org.gnome.Builder.Git.Config.Error.NotFound",
+                                                "No such key");
+  else
+    ipc_git_config_complete_read_key (config, invocation, value);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_config_impl_handle_write_key (IpcGitConfig          *config,
+                                      GDBusMethodInvocation *invocation,
+                                      const gchar           *key,
+                                      const gchar           *value)
+{
+  IpcGitConfigImpl *self = (IpcGitConfigImpl *)config;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IPC_IS_GIT_CONFIG (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (key != NULL);
+
+  if (!ggit_config_set_string (self->config, key, value, &error))
+    complete_wrapped_error (invocation, error);
+  else
+    ipc_git_config_complete_write_key (config, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_config_impl_handle_close (IpcGitConfig          *config,
+                                  GDBusMethodInvocation *invocation)
+{
+  g_assert (IPC_IS_GIT_CONFIG (config));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  ipc_git_config_emit_closed (config);
+  g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (config));
+  ipc_git_config_complete_close (config, invocation);
+
+  return TRUE;
+}
+
+static void
+git_config_iface_init (IpcGitConfigIface *iface)
+{
+  iface->handle_read_key = ipc_git_config_impl_handle_read_key;
+  iface->handle_write_key = ipc_git_config_impl_handle_write_key;
+  iface->handle_close = ipc_git_config_impl_handle_close;
+}
+
+G_DEFINE_TYPE_WITH_CODE (IpcGitConfigImpl, ipc_git_config_impl, IPC_TYPE_GIT_CONFIG_SKELETON,
+                         G_IMPLEMENT_INTERFACE (IPC_TYPE_GIT_CONFIG, git_config_iface_init))
+
+static void
+ipc_git_config_impl_finalize (GObject *object)
+{
+  IpcGitConfigImpl *self = (IpcGitConfigImpl *)object;
+
+  g_clear_object (&self->config);
+
+  G_OBJECT_CLASS (ipc_git_config_impl_parent_class)->finalize (object);
+}
+
+static void
+ipc_git_config_impl_class_init (IpcGitConfigImplClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ipc_git_config_impl_finalize;
+}
+
+static void
+ipc_git_config_impl_init (IpcGitConfigImpl *self)
+{
+}
+
+IpcGitConfig *
+ipc_git_config_impl_new (GgitConfig *config)
+{
+  IpcGitConfigImpl *ret;
+
+  g_return_val_if_fail (GGIT_IS_CONFIG (config), NULL);
+
+  ret = g_object_new (IPC_TYPE_GIT_CONFIG_IMPL, NULL);
+  ret->config = g_object_ref (config);
+
+  return IPC_GIT_CONFIG (g_steal_pointer (&ret));
+}
diff --git a/src/plugins/git/daemon/ipc-git-config-impl.h b/src/plugins/git/daemon/ipc-git-config-impl.h
new file mode 100644
index 000000000..2dff33285
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-config-impl.h
@@ -0,0 +1,35 @@
+/* ipc-git-config-impl.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 <libgit2-glib/ggit.h>
+
+#include "ipc-git-config.h"
+
+G_BEGIN_DECLS
+
+#define IPC_TYPE_GIT_CONFIG_IMPL (ipc_git_config_impl_get_type())
+
+G_DECLARE_FINAL_TYPE (IpcGitConfigImpl, ipc_git_config_impl, IPC, GIT_CONFIG_IMPL, IpcGitConfigSkeleton)
+
+IpcGitConfig *ipc_git_config_impl_new (GgitConfig *config);
+
+G_END_DECLS
diff --git a/src/plugins/git/daemon/ipc-git-index-monitor.c b/src/plugins/git/daemon/ipc-git-index-monitor.c
new file mode 100644
index 000000000..d8469d956
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-index-monitor.c
@@ -0,0 +1,237 @@
+/* ipc-git-index-monitor.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
+ */
+
+#define G_LOG_DOMAIN "ipc-git-index-monitor"
+
+#include "ipc-git-index-monitor.h"
+
+#define CHANGED_DELAY_MSEC 500
+#define str_equal0(str1, str2) (g_strcmp0 (str1, str2) == 0)
+
+struct _IpcGitIndexMonitor
+{
+  GObject       parent_instance;
+  GFileMonitor *refs_heads_monitor;
+  GFileMonitor *dot_git_monitor;
+  guint         changed_source;
+};
+
+G_DEFINE_TYPE (IpcGitIndexMonitor, ipc_git_index_monitor, G_TYPE_OBJECT)
+
+enum {
+  CHANGED,
+  N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+ipc_git_index_monitor_dispose (GObject *object)
+{
+  IpcGitIndexMonitor *self = (IpcGitIndexMonitor *)object;
+
+  g_clear_handle_id (&self->changed_source, g_source_remove);
+
+  if (self->refs_heads_monitor != NULL)
+    {
+      g_file_monitor_cancel (self->refs_heads_monitor);
+      g_clear_object (&self->refs_heads_monitor);
+    }
+
+  if (self->dot_git_monitor != NULL)
+    {
+      g_file_monitor_cancel (self->dot_git_monitor);
+      g_clear_object (&self->dot_git_monitor);
+    }
+
+  G_OBJECT_CLASS (ipc_git_index_monitor_parent_class)->dispose (object);
+}
+
+static void
+ipc_git_index_monitor_class_init (IpcGitIndexMonitorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ipc_git_index_monitor_dispose;
+
+  signals [CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+  g_signal_set_va_marshaller (signals [CHANGED],
+                              G_TYPE_FROM_CLASS (klass),
+                              g_cclosure_marshal_VOID__VOIDv);
+}
+
+static void
+ipc_git_index_monitor_init (IpcGitIndexMonitor *self)
+{
+}
+
+static gboolean
+ipc_git_index_monitor_queue_changed_cb (gpointer data)
+{
+  IpcGitIndexMonitor *self = data;
+
+  g_assert (IPC_IS_GIT_INDEX_MONITOR (self));
+
+  self->changed_source = 0;
+
+  g_signal_emit (self, signals [CHANGED], 0);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ipc_git_index_monitor_queue_changed (IpcGitIndexMonitor *self)
+{
+  g_assert (IPC_IS_GIT_INDEX_MONITOR (self));
+
+  g_clear_handle_id (&self->changed_source, g_source_remove);
+  self->changed_source = g_timeout_add_full (G_PRIORITY_LOW,
+                                             CHANGED_DELAY_MSEC,
+                                             ipc_git_index_monitor_queue_changed_cb,
+                                             g_object_ref (self),
+                                             g_object_unref);
+}
+
+static void
+ipc_git_index_monitor_dot_git_changed_cb (IpcGitIndexMonitor *self,
+                                          GFile              *file,
+                                          GFile              *other_file,
+                                          GFileMonitorEvent   event,
+                                          GFileMonitor       *monitor)
+{
+  g_autofree gchar *name = NULL;
+
+  g_assert (IPC_IS_GIT_INDEX_MONITOR (self));
+  g_assert (G_IS_FILE (file));
+  g_assert (!other_file || G_IS_FILE (other_file));
+  g_assert (G_IS_FILE_MONITOR (monitor));
+
+  name = g_file_get_basename (file);
+
+  if (str_equal0 (name, "index") ||
+      str_equal0 (name, "HEAD") ||
+      str_equal0 (name, "HEAD.lock") ||
+      str_equal0 (name, "ORIG_HEAD") ||
+      str_equal0 (name, "FETCH_HEAD") ||
+      str_equal0 (name, "COMMIT_EDITMSG") ||
+      str_equal0 (name, "config"))
+    ipc_git_index_monitor_queue_changed (self);
+}
+
+static void
+ipc_git_index_monitor_refs_heads_changed (IpcGitIndexMonitor *self,
+                                          GFile              *file,
+                                          GFile              *other_file,
+                                          GFileMonitorEvent   event,
+                                          GFileMonitor       *monitor)
+{
+  g_assert (IPC_IS_GIT_INDEX_MONITOR (self));
+  g_assert (G_IS_FILE (file));
+  g_assert (!other_file || G_IS_FILE (other_file));
+  g_assert (G_IS_FILE_MONITOR (monitor));
+
+  ipc_git_index_monitor_queue_changed (self);
+}
+
+IpcGitIndexMonitor *
+ipc_git_index_monitor_new (GFile *location)
+{
+  IpcGitIndexMonitor *self;
+  g_autofree gchar *name = NULL;
+  g_autoptr(GFile) dot_git_dir = NULL;
+  g_autoptr(GFile) refs_heads_dir = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_return_val_if_fail (G_IS_FILE (location), NULL);
+  g_return_val_if_fail (g_file_is_native (location), NULL);
+
+  self = g_object_new (IPC_TYPE_GIT_INDEX_MONITOR, NULL);
+
+  name = g_file_get_basename (location);
+
+  if (str_equal0 (name, ".git"))
+    dot_git_dir = g_object_ref (location);
+  else
+    {
+      const gchar *path = g_file_peek_path (location);
+      g_autofree gchar *new_path = NULL;
+      const gchar *dot_git;
+
+      /* Can't find .git directory, bail */
+      if (NULL == (dot_git = strstr (path, ".git"G_DIR_SEPARATOR_S)))
+        {
+          g_critical ("Failed to locate .git directory, cannot monitor repository");
+          return g_steal_pointer (&self);
+        }
+
+      new_path = g_strndup (path, dot_git - path + 4);
+      dot_git_dir = g_file_new_for_path (new_path);
+    }
+
+  self->dot_git_monitor = g_file_monitor_directory (dot_git_dir,
+                                                    G_FILE_MONITOR_NONE,
+                                                    NULL,
+                                                    &error);
+
+  if (self->dot_git_monitor == NULL)
+    {
+      g_critical ("Failed to monitor git repository, no changes will be detected: %s",
+                  error->message);
+      g_clear_error (&error);
+    }
+  else
+    {
+      g_signal_connect_object (self->dot_git_monitor,
+                               "changed",
+                               G_CALLBACK (ipc_git_index_monitor_dot_git_changed_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+    }
+
+  refs_heads_dir = g_file_get_child (dot_git_dir, "refs/heads");
+  self->refs_heads_monitor = g_file_monitor_directory (refs_heads_dir,
+                                                       G_FILE_MONITOR_NONE,
+                                                       NULL,
+                                                       &error);
+
+  if (self->refs_heads_monitor == NULL)
+    {
+      g_critical ("Failed to monitor git repository, no changes will be detected: %s",
+                  error->message);
+      g_clear_error (&error);
+    }
+  else
+    {
+      g_signal_connect_object (self->refs_heads_monitor,
+                               "changed",
+                               G_CALLBACK (ipc_git_index_monitor_refs_heads_changed),
+                               self,
+                               G_CONNECT_SWAPPED);
+    }
+
+  return g_steal_pointer (&self);
+}
diff --git a/src/plugins/git/daemon/ipc-git-index-monitor.h b/src/plugins/git/daemon/ipc-git-index-monitor.h
new file mode 100644
index 000000000..d2082a4b1
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-index-monitor.h
@@ -0,0 +1,33 @@
+/* ipc-git-index-monitor.h
+ *
+ * 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
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IPC_TYPE_GIT_INDEX_MONITOR (ipc_git_index_monitor_get_type())
+
+G_DECLARE_FINAL_TYPE (IpcGitIndexMonitor, ipc_git_index_monitor, IPC, GIT_INDEX_MONITOR, GObject)
+
+IpcGitIndexMonitor *ipc_git_index_monitor_new (GFile *location);
+
+G_END_DECLS
diff --git a/src/plugins/git/daemon/ipc-git-remote-callbacks.c 
b/src/plugins/git/daemon/ipc-git-remote-callbacks.c
new file mode 100644
index 000000000..036eaba05
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-remote-callbacks.c
@@ -0,0 +1,181 @@
+/* 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
+ */
+
+#define G_LOG_DOMAIN "ipc-git-remote-callbacks"
+
+#include <glib/gi18n.h>
+
+#include "ipc-git-remote-callbacks.h"
+
+struct _IpcGitRemoteCallbacks
+{
+  GgitRemoteCallbacks  parent_instance;
+  IpcGitProgress      *progress;
+  GgitCredtype         tried;
+  guint                cancelled : 1;
+};
+
+G_DEFINE_TYPE (IpcGitRemoteCallbacks, ipc_git_remote_callbacks, GGIT_TYPE_REMOTE_CALLBACKS)
+
+GgitRemoteCallbacks *
+ipc_git_remote_callbacks_new (IpcGitProgress *progress)
+{
+  IpcGitRemoteCallbacks *ret;
+
+  ret = g_object_new (IPC_TYPE_GIT_REMOTE_CALLBACKS, NULL);
+  ret->progress = progress ? g_object_ref (progress) : NULL;
+
+  return GGIT_REMOTE_CALLBACKS (g_steal_pointer (&ret));
+}
+
+static GgitCred *
+ipc_git_remote_callbacks_real_credentials (GgitRemoteCallbacks  *callbacks,
+                                           const gchar          *url,
+                                           const gchar          *username_from_url,
+                                           GgitCredtype          allowed_types,
+                                           GError              **error)
+{
+  IpcGitRemoteCallbacks *self = (IpcGitRemoteCallbacks *)callbacks;
+  GgitCred *ret = NULL;
+
+  g_assert (IPC_IS_GIT_REMOTE_CALLBACKS (self));
+  g_assert (url != NULL);
+
+  g_debug ("username=%s url=%s", username_from_url ?: "", url);
+
+  if (self->cancelled)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_CANCELLED,
+                   "The operation has been canceled");
+      return NULL;
+    }
+
+  allowed_types &= ~self->tried;
+
+  if ((allowed_types & GGIT_CREDTYPE_SSH_KEY) != 0)
+    {
+      GgitCredSshKeyFromAgent *cred;
+
+      cred = ggit_cred_ssh_key_from_agent_new (username_from_url, error);
+      ret = GGIT_CRED (cred);
+      self->tried |= GGIT_CREDTYPE_SSH_KEY;
+
+      if (ret != NULL)
+        return g_steal_pointer (&ret);
+    }
+
+  if ((allowed_types & GGIT_CREDTYPE_SSH_INTERACTIVE) != 0)
+    {
+      GgitCredSshInteractive *cred;
+
+      cred = ggit_cred_ssh_interactive_new (username_from_url, error);
+      ret = GGIT_CRED (cred);
+      self->tried |= GGIT_CREDTYPE_SSH_INTERACTIVE;
+
+      if (ret != NULL)
+        return g_steal_pointer (&ret);
+    }
+
+  g_set_error (error,
+               G_IO_ERROR,
+               G_IO_ERROR_NOT_SUPPORTED,
+               _("Builder failed to provide appropriate credentials when cloning the repository."));
+
+  return NULL;
+}
+
+static void
+ipc_git_remote_callbacks_progress (GgitRemoteCallbacks *callbacks,
+                                   const gchar         *message)
+{
+  IpcGitRemoteCallbacks *self = (IpcGitRemoteCallbacks *)callbacks;
+
+  g_assert (IPC_IS_GIT_REMOTE_CALLBACKS (self));
+
+  if (self->progress != NULL)
+    ipc_git_progress_set_message (self->progress, message);
+}
+
+static void
+ipc_git_remote_callbacks_transfer_progress (GgitRemoteCallbacks  *callbacks,
+                                            GgitTransferProgress *stats)
+{
+  IpcGitRemoteCallbacks *self = (IpcGitRemoteCallbacks *)callbacks;
+
+  g_assert (IPC_IS_GIT_REMOTE_CALLBACKS (self));
+
+  if (self->progress != NULL)
+    {
+      gdouble fraction = 0.0;
+      guint total = ggit_transfer_progress_get_total_objects (stats);
+      guint acked = ggit_transfer_progress_get_received_objects (stats);
+
+      if (total > 0)
+        fraction = (gdouble)acked / (gdouble)total;
+
+      ipc_git_progress_set_fraction (self->progress, fraction);
+    }
+}
+
+static void
+ipc_git_remote_callbacks_finalize (GObject *object)
+{
+  IpcGitRemoteCallbacks *self = (IpcGitRemoteCallbacks *)object;
+
+  g_clear_object (&self->progress);
+
+  G_OBJECT_CLASS (ipc_git_remote_callbacks_parent_class)->finalize (object);
+}
+
+static void
+ipc_git_remote_callbacks_class_init (IpcGitRemoteCallbacksClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GgitRemoteCallbacksClass *callbacks_class = GGIT_REMOTE_CALLBACKS_CLASS (klass);
+
+  object_class->finalize = ipc_git_remote_callbacks_finalize;
+
+  callbacks_class->credentials = ipc_git_remote_callbacks_real_credentials;
+  callbacks_class->progress = ipc_git_remote_callbacks_progress;
+  callbacks_class->transfer_progress = ipc_git_remote_callbacks_transfer_progress;
+}
+
+static void
+ipc_git_remote_callbacks_init (IpcGitRemoteCallbacks *self)
+{
+}
+
+/**
+ * ipc_git_remote_callbacks_cancel:
+ *
+ * This function should be called when a clone was canceled so that we can
+ * avoid dispatching more events.
+ *
+ * Since: 3.32
+ */
+void
+ipc_git_remote_callbacks_cancel (IpcGitRemoteCallbacks *self)
+{
+  g_return_if_fail (IPC_IS_GIT_REMOTE_CALLBACKS (self));
+
+  self->cancelled  = TRUE;
+}
diff --git a/src/plugins/git/daemon/ipc-git-remote-callbacks.h 
b/src/plugins/git/daemon/ipc-git-remote-callbacks.h
new file mode 100644
index 000000000..b29362fa4
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-remote-callbacks.h
@@ -0,0 +1,36 @@
+/* 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
+ */
+
+#pragma once
+
+#include <libgit2-glib/ggit.h>
+
+#include "ipc-git-progress.h"
+
+G_BEGIN_DECLS
+
+#define IPC_TYPE_GIT_REMOTE_CALLBACKS (ipc_git_remote_callbacks_get_type())
+
+G_DECLARE_FINAL_TYPE (IpcGitRemoteCallbacks, ipc_git_remote_callbacks, IPC, GIT_REMOTE_CALLBACKS, 
GgitRemoteCallbacks)
+
+GgitRemoteCallbacks *ipc_git_remote_callbacks_new    (IpcGitProgress        *progress);
+void                 ipc_git_remote_callbacks_cancel (IpcGitRemoteCallbacks *self);
+
+G_END_DECLS
diff --git a/src/plugins/git/daemon/ipc-git-repository-impl.c 
b/src/plugins/git/daemon/ipc-git-repository-impl.c
new file mode 100644
index 000000000..a8aed4250
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-repository-impl.c
@@ -0,0 +1,1135 @@
+/* ipc-git-repository-impl.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 "ipc-git-repository-impl"
+
+#include <libgit2-glib/ggit.h>
+#include <git2.h>
+#include <stdlib.h>
+
+#include "ipc-git-change-monitor-impl.h"
+#include "ipc-git-config-impl.h"
+#include "ipc-git-index-monitor.h"
+#include "ipc-git-progress.h"
+#include "ipc-git-remote-callbacks.h"
+#include "ipc-git-repository-impl.h"
+#include "ipc-git-types.h"
+#include "ipc-git-util.h"
+
+#if LIBGIT2_SOVERSION >= 28
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (git_buf, git_buf_dispose)
+#else
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (git_buf, git_buf_free)
+#endif
+
+struct _IpcGitRepositoryImpl
+{
+  IpcGitRepositorySkeleton  parent;
+  GgitRepository           *repository;
+  GHashTable               *change_monitors;
+  GHashTable               *configs;
+  IpcGitIndexMonitor       *monitor;
+};
+
+static void
+ipc_git_repository_impl_monitor_changed_cb (IpcGitRepositoryImpl *self,
+                                            IpcGitIndexMonitor   *monitor)
+{
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (IPC_IS_GIT_INDEX_MONITOR (monitor));
+
+  ipc_git_repository_emit_changed (IPC_GIT_REPOSITORY (self));
+}
+
+static gchar *
+get_signing_key (IpcGitRepositoryImpl *self)
+{
+  g_autoptr(GgitConfig) config = NULL;
+  const gchar *ret = NULL;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+
+  if ((config = ggit_repository_get_config (self->repository, NULL)))
+    {
+      g_autoptr(GgitConfig) snapshot = ggit_config_snapshot (config, NULL);
+
+      if (snapshot != NULL)
+        ret = ggit_config_get_string (snapshot, "user.signingkey", NULL);
+    }
+
+  return g_strdup (ret);
+}
+
+static gint
+ipc_git_repository_impl_handle_list_status_cb (const gchar     *path,
+                                               GgitStatusFlags  flags,
+                                               gpointer         user_data)
+{
+  GVariantBuilder *builder = user_data;
+
+  g_assert (path != NULL);
+  g_assert (builder != NULL);
+
+  g_variant_builder_add (builder, "(su)", path, flags);
+
+  return GIT_OK;
+}
+
+static gboolean
+ipc_git_repository_impl_handle_list_status (IpcGitRepository      *repository,
+                                            GDBusMethodInvocation *invocation,
+                                            const gchar           *path)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(GgitStatusOptions) options = NULL;
+  g_autoptr(GError) error = NULL;
+  const gchar *paths[] = { path, NULL };
+  GVariantBuilder builder;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (path != NULL);
+
+  options = ggit_status_options_new (GGIT_STATUS_OPTION_DEFAULT,
+                                     GGIT_STATUS_SHOW_INDEX_AND_WORKDIR,
+                                     paths);
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(su)"));
+
+  if (!ggit_repository_file_status_foreach (self->repository,
+                                            options,
+                                            ipc_git_repository_impl_handle_list_status_cb,
+                                            &builder,
+                                            &error))
+    complete_wrapped_error (invocation, error);
+  else
+    ipc_git_repository_complete_list_status (repository,
+                                             invocation,
+                                             g_variant_builder_end (&builder));
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_repository_impl_handle_switch_branch (IpcGitRepository      *repository,
+                                              GDBusMethodInvocation *invocation,
+                                              const gchar           *branch)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(GgitCheckoutOptions) checkout_options = NULL;
+  g_autoptr(GgitObject) obj = NULL;
+  g_autoptr(GgitRef) ref = NULL;
+  g_autoptr(GError) error = NULL;
+  const gchar *shortname;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (branch != NULL);
+
+  checkout_options = ggit_checkout_options_new ();
+  ggit_checkout_options_set_strategy (checkout_options, GGIT_CHECKOUT_SAFE);
+
+  if (!(ref = ggit_repository_lookup_reference (self->repository, branch, &error)) ||
+      !(obj = ggit_ref_lookup (ref, &error)) ||
+      !ggit_repository_checkout_tree (self->repository, obj, checkout_options, &error) ||
+      !ggit_repository_set_head (self->repository, branch, &error))
+    return complete_wrapped_error (invocation, error);
+
+  if (!(shortname = ggit_ref_get_shorthand (ref)))
+    shortname = "master";
+
+  ipc_git_repository_set_branch (repository, shortname);
+  ipc_git_repository_complete_switch_branch (repository, invocation);
+
+  ipc_git_repository_emit_changed (repository);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_repository_impl_handle_path_is_ignored (IpcGitRepository      *repository,
+                                                GDBusMethodInvocation *invocation,
+                                                const gchar           *path)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(GError) error = NULL;
+  gboolean ret;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (path != NULL);
+
+  ret = ggit_repository_path_is_ignored (self->repository, path, &error);
+
+  if (error != NULL)
+    complete_wrapped_error (invocation, error);
+  else
+    ipc_git_repository_complete_path_is_ignored (repository, invocation, ret);
+
+  return TRUE;
+}
+
+static gint
+compare_tags (gconstpointer a,
+              gconstpointer b)
+{
+  return g_utf8_collate (*(const gchar **)a, *(const gchar **)b);
+}
+
+static gboolean
+ipc_git_repository_impl_handle_list_refs_by_kind (IpcGitRepository      *repository,
+                                                  GDBusMethodInvocation *invocation,
+                                                  guint                  kind)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(GPtrArray) ret = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (repository));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  if (kind != IPC_GIT_REF_BRANCH && kind != IPC_GIT_REF_TAG)
+    {
+      g_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.freedesktop.DBus.Error.InvalidArgs",
+                                                  "kind must be a tag or a branch");
+      return TRUE;
+    }
+
+  ret = g_ptr_array_new_with_free_func (g_free);
+
+  if (kind == IPC_GIT_REF_BRANCH)
+    {
+      g_autoptr(GgitBranchEnumerator) enumerator = NULL;
+
+      if (!(enumerator = ggit_repository_enumerate_branches (self->repository, GGIT_BRANCH_LOCAL, &error)))
+        return complete_wrapped_error (invocation, error);
+
+      while (ggit_branch_enumerator_next (enumerator))
+        {
+          g_autoptr(GgitRef) ref = ggit_branch_enumerator_get (enumerator);
+          const gchar *name = ggit_ref_get_name (ref);
+          g_ptr_array_add (ret, g_strdup (name));
+        }
+    }
+  else if (kind == IPC_GIT_REF_TAG)
+    {
+      g_autofree gchar **names = NULL;
+
+      if (!(names = ggit_repository_list_tags (self->repository, &error)))
+        return complete_wrapped_error (invocation, error);
+
+      qsort (names, g_strv_length (names), sizeof (gchar *), compare_tags);
+
+      for (guint i = 0; names[i] != NULL; i++)
+        g_ptr_array_add (ret, g_steal_pointer (&names[i]));
+    }
+  else
+    g_assert_not_reached ();
+
+  g_ptr_array_add (ret, NULL);
+
+  ipc_git_repository_complete_list_refs_by_kind (repository,
+                                                 invocation,
+                                                 (const gchar * const *)ret->pdata);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_repository_impl_handle_close (IpcGitRepository      *repository,
+                                      GDBusMethodInvocation *invocation)
+{
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (repository));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  /* Service will drop it's reference from the hashtable */
+  ipc_git_repository_emit_closed (IPC_GIT_REPOSITORY (repository));
+
+  g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (repository));
+  ipc_git_repository_complete_close (repository, invocation);
+
+  return TRUE;
+}
+
+static void
+on_monitor_closed_cb (IpcGitRepositoryImpl *self,
+                      IpcGitChangeMonitor  *monitor)
+{
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (IPC_IS_GIT_CHANGE_MONITOR (monitor));
+
+  if (self->change_monitors != NULL)
+    g_hash_table_remove (self->change_monitors, monitor);
+}
+
+static gboolean
+ipc_git_repository_impl_handle_create_change_monitor (IpcGitRepository      *repository,
+                                                      GDBusMethodInvocation *invocation,
+                                                      const gchar           *path)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(IpcGitChangeMonitor) monitor = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *guid = NULL;
+  g_autofree gchar *obj_path = NULL;
+  GDBusConnection *conn;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (repository));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (path != NULL);
+
+  conn = g_dbus_method_invocation_get_connection (invocation);
+  guid = g_dbus_generate_guid ();
+  obj_path = g_strdup_printf ("/org/gnome/Builder/Git/ChangeMonitor/%s", guid);
+  monitor = ipc_git_change_monitor_impl_new (self->repository, path);
+
+  if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (monitor), conn, obj_path, &error))
+    return complete_wrapped_error (invocation, error);
+
+  g_signal_connect_object (monitor,
+                           "closed",
+                           G_CALLBACK (on_monitor_closed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_hash_table_insert (self->change_monitors,
+                       g_steal_pointer (&monitor),
+                       g_strdup (obj_path));
+
+  ipc_git_repository_complete_create_change_monitor (repository, invocation, obj_path);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_repository_impl_handle_stage_file (IpcGitRepository      *repository,
+                                           GDBusMethodInvocation *invocation,
+                                           const gchar           *path)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(GgitIndex) index = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (path != NULL);
+
+  if (!(index = ggit_repository_get_index (self->repository, &error)) ||
+      !ggit_index_add_path (index, path, &error) ||
+      !ggit_index_write (index, &error))
+    return complete_wrapped_error (invocation, error);
+  else
+    ipc_git_repository_complete_stage_file (repository, invocation);
+
+  ipc_git_repository_emit_changed (repository);
+
+  return TRUE;
+}
+
+static GgitDiff *
+ipc_git_repository_impl_get_index_diff (IpcGitRepositoryImpl  *self,
+                                        GgitTree             **out_tree,
+                                        GError               **error)
+{
+  g_autoptr(GgitDiffOptions) options = NULL;
+  g_autoptr(GgitIndex) index = NULL;
+  g_autoptr(GgitDiff) diff = NULL;
+  g_autoptr(GgitTree) tree = NULL;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+
+  if (out_tree != NULL)
+    *out_tree = NULL;
+
+  options = ggit_diff_options_new ();
+  ggit_diff_options_set_flags (options,
+                               GGIT_DIFF_INCLUDE_UNTRACKED |
+                               GGIT_DIFF_DISABLE_PATHSPEC_MATCH |
+                               GGIT_DIFF_RECURSE_UNTRACKED_DIRS);
+  ggit_diff_options_set_n_context_lines (options, 3);
+  ggit_diff_options_set_n_interhunk_lines (options, 3);
+
+  if (!ggit_repository_is_empty (self->repository, error))
+    {
+      g_autoptr(GgitRef) head = NULL;
+      g_autoptr(GgitObject) obj = NULL;
+
+      if (!(head = ggit_repository_get_head (self->repository, error)) ||
+          !(obj = ggit_ref_lookup (head, error)))
+        return NULL;
+
+      tree = ggit_commit_get_tree (GGIT_COMMIT (obj));
+    }
+
+  if (!(index = ggit_repository_get_index (self->repository, error)) ||
+      !(diff = ggit_diff_new_tree_to_index (self->repository, tree, index, options, error)))
+    return NULL;
+
+  if (out_tree != NULL)
+    *out_tree = g_steal_pointer (&tree);
+
+  return g_steal_pointer (&diff);
+}
+
+static gboolean
+get_signed_data (const gchar  *data,
+                 const gchar  *key,
+                 gchar       **signed_data,
+                 GError      **error)
+{
+  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autofree gchar *stdout_buf = NULL;
+
+  g_assert (data);
+  g_assert (signed_data);
+
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE |
+                                        G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+  subprocess = g_subprocess_launcher_spawn (launcher,
+                                            error,
+                                            "gpg",
+                                            "--clear-sign",
+                                            "--default-key",
+                                            key,
+                                            "-",
+                                            NULL);
+  if (subprocess == NULL)
+    return FALSE;
+
+  if (!g_subprocess_communicate_utf8 (subprocess, data, NULL, &stdout_buf, NULL, error))
+    return FALSE;
+
+  *signed_data = g_steal_pointer (&stdout_buf);
+
+  return TRUE;
+}
+
+static GgitOId *
+commit_create_with_signature (GgitRepository  *repository,
+                              const gchar     *update_ref,
+                              GgitSignature   *author,
+                              GgitSignature   *committer,
+                              const gchar     *message_encoding,
+                              const gchar     *message,
+                              GgitTree        *tree,
+                              GgitCommit     **parents,
+                              gint             parent_count,
+                              const gchar     *gpg_key_id,
+                              GError         **error)
+{
+  g_autofree git_commit **parents_native = NULL;
+  g_autofree gchar *contents = NULL;
+  g_autoptr(GgitOId) wrapped = NULL;
+  g_auto(git_buf) buf = {0};
+  g_autoptr(GgitRef) head = NULL;
+  g_autoptr(GgitRef) new_head = NULL;
+  git_oid oid;
+  gint ret;
+
+  g_assert (GGIT_IS_REPOSITORY (repository));
+  g_assert (update_ref != NULL);
+  g_assert (GGIT_IS_SIGNATURE (author));
+  g_assert (GGIT_IS_SIGNATURE (committer));
+  g_assert (GGIT_IS_TREE (tree));
+  g_assert (parents != NULL);
+  g_assert (parent_count > 0);
+  g_assert (GGIT_IS_COMMIT (parents[0]));
+
+  parents_native = g_new0 (git_commit *, parent_count);
+
+  for (gint i = 0; i < parent_count; ++i)
+    parents_native[i] = _ggit_native_get (parents[i]);
+
+  ret = git_commit_create_buffer (&buf,
+                                  _ggit_native_get (repository),
+                                  _ggit_native_get (author),
+                                  _ggit_native_get (committer),
+                                  message_encoding,
+                                  message,
+                                  _ggit_native_get (tree),
+                                  parent_count,
+                                  (const git_commit **)parents_native);
+
+  if (ret != GIT_OK)
+    {
+      _ggit_error_set (error, ret);
+      return NULL;
+    }
+
+  if (!get_signed_data (buf.ptr, gpg_key_id, &contents, error))
+    return NULL;
+
+  ret = git_commit_create_with_signature (&oid,
+                                          _ggit_native_get (repository),
+                                          buf.ptr,
+                                          contents,
+                                          NULL);
+
+  if (ret != GIT_OK)
+    {
+      _ggit_error_set (error, ret);
+      return NULL;
+    }
+
+  wrapped = _ggit_oid_wrap (&oid);
+
+  if (!(head = ggit_repository_get_head (repository, error)))
+    return NULL;
+
+  if (!(new_head = ggit_ref_set_target (head, wrapped, "commit", error)))
+    return NULL;
+
+  return g_steal_pointer (&wrapped);
+}
+
+static gboolean
+ipc_git_repository_impl_commit (IpcGitRepositoryImpl  *self,
+                                GgitSignature         *author,
+                                GgitSignature         *committer,
+                                GgitDiff              *diff,
+                                const gchar           *message,
+                                IpcGitCommitFlags      flags,
+                                const gchar           *gpg_key_id,
+                                GError               **error)
+{
+  g_autofree gchar *stripped = NULL;
+  g_autofree gchar *signoff_message = NULL;
+  g_autoptr(GgitTree) tree = NULL;
+  g_autoptr(GgitIndex) index = NULL;
+  g_autoptr(GgitRef) head = NULL;
+  g_autoptr(GgitOId) written = NULL;
+  g_autoptr(GgitOId) oid = NULL;
+  g_autoptr(GgitOId) target = NULL;
+  g_autoptr(GgitCommit) commit = NULL;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (GGIT_IS_REPOSITORY (self->repository));
+  g_assert (GGIT_IS_SIGNATURE (author));
+  g_assert (GGIT_IS_SIGNATURE (committer));
+  g_assert (GGIT_IS_DIFF (diff));
+
+  if ((flags & IPC_GIT_COMMIT_FLAGS_AMEND) &&
+      (flags & IPC_GIT_COMMIT_FLAGS_GPG_SIGN))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_DATA,
+                   "Cannot set AMEND and GPG_SIGN flags");
+      return FALSE;
+    }
+
+  if ((flags & IPC_GIT_COMMIT_FLAGS_GPG_SIGN) && !gpg_key_id)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVALID_DATA,
+                   "Cannot sign commit without GPG_KEY_ID");
+      return FALSE;
+    }
+
+  /* Remove extra whitespace */
+  message = stripped = g_strstrip (g_strdup (message));
+
+  /* Maybe add sign-off message */
+  if (flags & IPC_GIT_COMMIT_FLAGS_SIGNOFF)
+    {
+      signoff_message = g_strdup_printf ("%s\n%sSigned-off-by: %s <%s>\n",
+                                         message,
+                                         /* Extra \n if first sign-off */
+                                         strstr (message, "\nSigned-off-by: ") ? "" : "\n",
+                                         ggit_signature_get_name (committer),
+                                         ggit_signature_get_email (committer));
+      message = signoff_message;
+    }
+
+  if (!(index = ggit_repository_get_index (self->repository, error)) ||
+      !(written = ggit_index_write_tree (index, error)) ||
+      !(tree = ggit_repository_lookup_tree (self->repository, written, error)) ||
+      !(head = ggit_repository_get_head (self->repository, error)) ||
+      !(target = ggit_ref_get_target (head)) ||
+      !(commit = ggit_repository_lookup_commit (self->repository, target, error)))
+    return FALSE;
+
+  /* TODO: Hooks */
+
+  if (flags & IPC_GIT_COMMIT_FLAGS_AMEND)
+    oid = ggit_commit_amend (commit,
+                             "HEAD",
+                             author,
+                             committer,
+                             NULL, /* UTF-8 */
+                             message,
+                             tree,
+                             error);
+  else if (flags & IPC_GIT_COMMIT_FLAGS_GPG_SIGN)
+    oid = commit_create_with_signature (self->repository,
+                                        "HEAD",
+                                        author,
+                                        committer,
+                                        NULL, /* UTF-8 */
+                                        message,
+                                        tree,
+                                        &commit,
+                                        1,
+                                        gpg_key_id,
+                                        error);
+  else
+    oid = ggit_repository_create_commit (self->repository,
+                                         "HEAD",
+                                         author,
+                                         committer,
+                                         NULL, /* UTF-8 */
+                                         message,
+                                         tree,
+                                         &commit,
+                                         1,
+                                         error);
+
+  return oid != NULL;
+}
+
+static gboolean
+ipc_git_repository_impl_handle_commit (IpcGitRepository      *repository,
+                                       GDBusMethodInvocation *invocation,
+                                       GVariant              *details,
+                                       guint                  flags)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(GgitTree) tree = NULL;
+  g_autoptr(GgitDiff) diff = NULL;
+  g_autoptr(GgitSignature) author_sig = NULL;
+  g_autoptr(GgitSignature) committer_sig = NULL;
+  g_autoptr(GError) error = NULL;
+  const gchar *author_name;
+  const gchar *author_email;
+  const gchar *committer_name;
+  const gchar *committer_email;
+  const gchar *commit_msg;
+  const gchar *gpg_key_id;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (details != NULL);
+
+  if (!g_variant_lookup (details, "GPG_KEY_ID", "&s", &gpg_key_id))
+    gpg_key_id = get_signing_key (self);
+
+  if (!g_variant_lookup (details, "AUTHOR_NAME", "&s", &author_name) ||
+      !g_variant_lookup (details, "AUTHOR_EMAIL", "&s", &author_email) ||
+      !g_variant_lookup (details, "COMMITTER_NAME", "&s", &committer_name) ||
+      !g_variant_lookup (details, "COMMITTER_EMAIL", "&s", &committer_email) ||
+      !g_variant_lookup (details, "COMMIT_MSG", "&s", &commit_msg))
+    {
+      g_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.freedesktop.DBus.Error.InvalidArgs",
+                                                  "Invalid details for commit");
+      return TRUE;
+    }
+
+  if (!(author_sig = ggit_signature_new_now (author_name, author_email, &error)) ||
+      !(committer_sig = ggit_signature_new_now (committer_name, committer_email, &error)) ||
+      !(diff = ipc_git_repository_impl_get_index_diff (self, &tree, &error)) ||
+      !ipc_git_repository_impl_commit (self, author_sig, committer_sig, diff, commit_msg, flags, gpg_key_id, 
&error))
+    return complete_wrapped_error (invocation, error);
+
+  ipc_git_repository_complete_commit (repository, invocation);
+  ipc_git_repository_emit_changed (repository);
+
+  return TRUE;
+}
+
+typedef struct
+{
+  GgitRemote           *remote;
+  GgitPushOptions      *push_options;
+  IpcGitProgress       *progress;
+  GgitRemoteCallbacks  *callbacks;
+  GgitProxyOptions     *proxy_options;
+  gchar               **ref_names;
+} Push;
+
+static void
+push_free (Push *push)
+{
+  g_clear_object (&push->callbacks);
+  g_clear_object (&push->progress);
+  g_clear_object (&push->proxy_options);
+  g_clear_object (&push->push_options);
+  g_clear_object (&push->remote);
+  g_clear_pointer (&push->ref_names, g_strfreev);
+  g_slice_free (Push, push);
+}
+
+static void
+push_worker (GTask        *task,
+             gpointer      source_object,
+             gpointer      task_data,
+             GCancellable *cancellable)
+{
+  g_autoptr(GError) error = NULL;
+  Push *push = task_data;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (source_object));
+  g_assert (push != NULL);
+  g_assert (GGIT_IS_REMOTE (push->remote));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ggit_remote_connect (push->remote,
+                       GGIT_DIRECTION_PUSH,
+                       push->callbacks,
+                       push->proxy_options,
+                       NULL,
+                       &error);
+
+  if (error != NULL)
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  ggit_remote_upload (push->remote,
+                      (const gchar * const *)push->ref_names,
+                      push->push_options,
+                      &error);
+
+  ggit_remote_disconnect (push->remote);
+
+  if (error != NULL)
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+static void
+ipc_git_repository_impl_handle_push_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+  IpcGitRepository *repository = (IpcGitRepository *)object;
+  g_autoptr(GDBusMethodInvocation) invocation = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (repository));
+  g_assert (G_IS_TASK (result));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  if (g_task_propagate_boolean (G_TASK (result), &error))
+    {
+      ipc_git_repository_complete_push (repository, invocation);
+      ipc_git_repository_emit_changed (repository);
+    }
+  else
+    {
+      complete_wrapped_error (invocation, error);
+    }
+}
+
+static gboolean
+ipc_git_repository_impl_handle_push (IpcGitRepository      *repository,
+                                     GDBusMethodInvocation *invocation,
+                                     const gchar           *url,
+                                     const gchar * const   *ref_names,
+                                     guint                  flags,
+                                     const gchar           *progress_path)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(IpcGitProgress) progress = NULL;
+  g_autoptr(GgitRemoteCallbacks) callbacks = NULL;
+  g_autoptr(GgitProxyOptions) proxy_options = NULL;
+  g_autoptr(GgitPushOptions) push_options = NULL;
+  g_autoptr(GgitRemote) remote = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = NULL;
+  Push *push;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (GGIT_IS_REPOSITORY (self->repository));
+  g_assert (url != NULL);
+  g_assert (ref_names != NULL);
+
+  if (flags & IPC_GIT_PUSH_FLAGS_ATOMIC)
+    {
+      g_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.freedesktop.DBus.Error.InvalidArgs",
+                                                  "atomic is not currently supported");
+      return TRUE;
+    }
+
+  /* Try to lookup the remote, or create a new anonyous URL for it */
+  if (!(remote = ggit_repository_lookup_remote (self->repository, url, &error)))
+    {
+      g_autoptr(GError) anon_error = NULL;
+
+      if (!(remote = ggit_remote_new_anonymous (self->repository, url, &anon_error)))
+        return complete_wrapped_error (invocation, error);
+
+      g_clear_error (&error);
+    }
+
+  progress = ipc_git_progress_proxy_new_sync (g_dbus_method_invocation_get_connection (invocation),
+                                              G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                                              NULL,
+                                              progress_path,
+                                              NULL,
+                                              &error);
+  if (progress == NULL)
+    return complete_wrapped_error (invocation, error);
+
+  callbacks = ipc_git_remote_callbacks_new (progress);
+
+  push_options = ggit_push_options_new ();
+  ggit_push_options_set_callbacks (push_options, callbacks);
+
+  push = g_slice_new0 (Push);
+  push->remote = g_steal_pointer (&remote);
+  push->progress = g_steal_pointer (&progress);
+  push->callbacks = g_steal_pointer (&callbacks);
+  push->proxy_options = g_steal_pointer (&proxy_options);
+  push->push_options = g_steal_pointer (&push_options);
+
+  task = g_task_new (self, NULL, ipc_git_repository_impl_handle_push_cb, g_object_ref (invocation));
+  g_task_set_source_tag (task, ipc_git_repository_impl_handle_push);
+  g_task_set_task_data (task, push, (GDestroyNotify)push_free);
+  g_task_run_in_thread (task, push_worker);
+
+  return TRUE;
+}
+
+static void
+ipc_git_repository_impl_config_closed_cb (IpcGitRepositoryImpl *self,
+                                          IpcGitConfigImpl     *config)
+{
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (IPC_IS_GIT_CONFIG_IMPL (config));
+
+  g_hash_table_remove (self->configs, config);
+}
+
+static gboolean
+ipc_git_repository_impl_handle_load_config (IpcGitRepository      *repository,
+                                            GDBusMethodInvocation *invocation)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(IpcGitConfig) config = NULL;
+  g_autoptr(GgitConfig) gconfig = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *uuid = NULL;
+  g_autofree gchar *obj_path = NULL;
+  GDBusConnection *conn;
+
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  if (!(gconfig = ggit_repository_get_config (self->repository, &error)))
+    return complete_wrapped_error (invocation, error);
+
+  config = ipc_git_config_impl_new (gconfig);
+
+  conn = g_dbus_method_invocation_get_connection (invocation);
+  uuid = g_dbus_generate_guid ();
+  obj_path = g_strdup_printf ("/org/gnome/Builder/Config/%s", uuid);
+  g_hash_table_insert (self->configs, g_object_ref (config), g_strdup (uuid));
+
+  g_signal_connect_object (config,
+                           "closed",
+                           G_CALLBACK (ipc_git_repository_impl_config_closed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (config), conn, obj_path, &error))
+    ipc_git_repository_complete_load_config (repository, invocation, obj_path);
+  else
+    complete_wrapped_error (invocation, error);
+
+  return TRUE;
+}
+
+typedef struct
+{
+  GFile                      *location;
+  GgitSubmoduleUpdateOptions *options;
+  GError                     *error;
+  guint                       init : 1;
+} UpdateSubmodules;
+
+static void
+update_submodules_free (UpdateSubmodules *state)
+{
+  g_clear_object (&state->location);
+  g_clear_object (&state->options);
+  g_clear_error (&state->error);
+  g_slice_free (UpdateSubmodules, state);
+}
+
+static gint
+ipc_git_repository_impl_submodule_foreach_cb (GgitSubmodule *submodule,
+                                              const gchar   *name,
+                                              gpointer       user_data)
+{
+  UpdateSubmodules *state = user_data;
+
+  g_assert (submodule != NULL);
+  g_assert (name != NULL);
+
+  g_printerr ("Callback \n");
+
+  if (state->error == NULL)
+    ggit_submodule_update (submodule, state->init, state->options, &state->error);
+
+  return GIT_OK;
+}
+
+static void
+update_submodules_worker (GTask        *task,
+                          gpointer      source_object,
+                          gpointer      task_data,
+                          GCancellable *cancellable)
+{
+  UpdateSubmodules *state = task_data;
+  g_autoptr(GgitRepository) repository = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IPC_IS_GIT_REPOSITORY (source_object));
+  g_assert (state != NULL);
+  g_assert (GGIT_IS_SUBMODULE_UPDATE_OPTIONS (state->options));
+
+  if (!(repository = ggit_repository_open (state->location, &error)))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  if (!ggit_repository_submodule_foreach (repository,
+                                          ipc_git_repository_impl_submodule_foreach_cb,
+                                          state,
+                                          &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  if (state->error != NULL)
+    g_task_return_error (task, g_steal_pointer (&state->error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+static void
+complete_update_submodules (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  IpcGitRepository *self = (IpcGitRepository *)object;
+  g_autoptr(GDBusMethodInvocation) invocation = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IPC_IS_GIT_REPOSITORY (self));
+  g_assert (G_IS_TASK (result));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  if (!g_task_propagate_boolean (G_TASK (result), &error))
+    {
+      complete_wrapped_error (invocation, error);
+      return;
+    }
+  else
+    {
+      ipc_git_repository_complete_update_submodules (self, invocation);
+      ipc_git_repository_emit_changed (self);
+    }
+}
+
+static gboolean
+ipc_git_repository_impl_handle_update_submodules (IpcGitRepository      *repository,
+                                                  GDBusMethodInvocation *invocation,
+                                                  gboolean               init,
+                                                  const gchar           *progress_path)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)repository;
+  g_autoptr(GgitRemoteCallbacks) callbacks = NULL;
+  g_autoptr(GgitSubmoduleUpdateOptions) update_options = NULL;
+  g_autoptr(IpcGitProgress) progress  = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = NULL;
+  UpdateSubmodules *state;
+  GgitFetchOptions *fetch_options = NULL;
+  GDBusConnection *conn = NULL;
+
+  g_assert (IPC_IS_GIT_REPOSITORY (repository));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (progress_path != NULL);
+
+  conn = g_dbus_method_invocation_get_connection (invocation);
+  progress = ipc_git_progress_proxy_new_sync (conn,
+                                              G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                                              NULL, progress_path, NULL, &error);
+  callbacks = ipc_git_remote_callbacks_new (progress);
+
+  update_options = ggit_submodule_update_options_new ();
+  ggit_submodule_update_options_set_fetch_options (update_options, fetch_options);
+
+  state = g_slice_new0 (UpdateSubmodules);
+  state->location = ggit_repository_get_location (self->repository);
+  state->options = g_steal_pointer (&update_options);
+  state->init = !!init;
+  state->error = NULL;
+
+  task = g_task_new (self, NULL, complete_update_submodules, g_object_ref (invocation));
+  g_task_set_source_tag (task, ipc_git_repository_impl_handle_update_submodules);
+  g_task_set_task_data (task, state, (GDestroyNotify)update_submodules_free);
+  g_task_run_in_thread (task, update_submodules_worker);
+
+  g_clear_pointer (&fetch_options, ggit_fetch_options_free);
+
+  return TRUE;
+}
+
+static void
+git_repository_iface_init (IpcGitRepositoryIface *iface)
+{
+  iface->handle_close = ipc_git_repository_impl_handle_close;
+  iface->handle_commit = ipc_git_repository_impl_handle_commit;
+  iface->handle_create_change_monitor = ipc_git_repository_impl_handle_create_change_monitor;
+  iface->handle_list_refs_by_kind = ipc_git_repository_impl_handle_list_refs_by_kind;
+  iface->handle_list_status = ipc_git_repository_impl_handle_list_status;
+  iface->handle_load_config = ipc_git_repository_impl_handle_load_config;
+  iface->handle_path_is_ignored = ipc_git_repository_impl_handle_path_is_ignored;
+  iface->handle_push = ipc_git_repository_impl_handle_push;
+  iface->handle_stage_file = ipc_git_repository_impl_handle_stage_file;
+  iface->handle_switch_branch = ipc_git_repository_impl_handle_switch_branch;
+  iface->handle_update_submodules = ipc_git_repository_impl_handle_update_submodules;
+}
+
+G_DEFINE_TYPE_WITH_CODE (IpcGitRepositoryImpl, ipc_git_repository_impl, IPC_TYPE_GIT_REPOSITORY_SKELETON,
+                         G_IMPLEMENT_INTERFACE (IPC_TYPE_GIT_REPOSITORY, git_repository_iface_init))
+
+static void
+ipc_git_repository_impl_finalize (GObject *object)
+{
+  IpcGitRepositoryImpl *self = (IpcGitRepositoryImpl *)object;
+
+  g_clear_object (&self->monitor);
+  g_clear_pointer (&self->change_monitors, g_hash_table_unref);
+  g_clear_pointer (&self->configs, g_hash_table_unref);
+  g_clear_object (&self->repository);
+
+  G_OBJECT_CLASS (ipc_git_repository_impl_parent_class)->finalize (object);
+}
+
+static void
+ipc_git_repository_impl_class_init (IpcGitRepositoryImplClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ipc_git_repository_impl_finalize;
+}
+
+static void
+ipc_git_repository_impl_init (IpcGitRepositoryImpl *self)
+{
+  self->configs = g_hash_table_new_full (NULL, NULL, g_object_unref, g_free);
+  self->change_monitors = g_hash_table_new_full (NULL, NULL, g_object_unref, g_free);
+}
+
+IpcGitRepository *
+ipc_git_repository_impl_open (GFile   *location,
+                              GError **error)
+{
+  g_autoptr(GgitRepository) repository = NULL;
+  g_autoptr(GFile) gitdir = NULL;
+  g_autofree gchar *branch = NULL;
+  IpcGitRepositoryImpl *ret;
+
+  g_return_val_if_fail (G_IS_FILE (location), NULL);
+
+  /* If @location is a regular file, we might have a git-worktree link */
+  if (g_file_query_file_type (location, 0, NULL) == G_FILE_TYPE_REGULAR)
+    {
+      g_autofree gchar *contents = NULL;
+      gsize len;
+
+      if (g_file_load_contents (location, NULL, &contents, &len, NULL, NULL))
+        {
+          g_auto(GStrv) lines = g_strsplit (contents, "\n", 0);
+
+          for (gsize i = 0; lines[i] != NULL; i++)
+            {
+              gchar *line = lines[i];
+
+              if (g_str_has_prefix (line, "gitdir: "))
+                {
+                  g_autoptr(GFile) location_parent = g_file_get_parent (location);
+                  const gchar *path = line + strlen ("gitdir: ");
+                  const gchar *sep;
+
+                  g_clear_object (&location);
+
+                  if (g_path_is_absolute (path))
+                    location = gitdir = g_file_new_for_path (path);
+                  else
+                    location = gitdir = g_file_resolve_relative_path (location_parent, path);
+
+                  /*
+                   * Worktrees only have a single branch, and it is the name
+                   * of the suffix of .git/worktrees/<name>
+                   */
+                  if ((sep = strrchr (line, G_DIR_SEPARATOR)))
+                    branch = g_strdup (sep + 1);
+
+                  break;
+                }
+            }
+        }
+    }
+
+  if (!(repository = ggit_repository_open (location, error)))
+    return NULL;
+
+  if (branch == NULL)
+    {
+      g_autoptr(GgitRef) ref = NULL;
+
+      if ((ref = ggit_repository_get_head (repository, NULL)))
+        branch = g_strdup (ggit_ref_get_shorthand (ref));
+
+      if (branch == NULL)
+        branch = g_strdup ("master");
+    }
+
+  ret = g_object_new (IPC_TYPE_GIT_REPOSITORY_IMPL,
+                      "branch", branch,
+                      "location", g_file_peek_path (location),
+                      NULL);
+  ret->repository = g_steal_pointer (&repository);
+  ret->monitor = ipc_git_index_monitor_new (location);
+
+  g_signal_connect_object (ret->monitor,
+                           "changed",
+                           G_CALLBACK (ipc_git_repository_impl_monitor_changed_cb),
+                           ret,
+                           G_CONNECT_SWAPPED);
+
+  return IPC_GIT_REPOSITORY (g_steal_pointer (&ret));
+}
diff --git a/src/plugins/git/daemon/ipc-git-repository-impl.h 
b/src/plugins/git/daemon/ipc-git-repository-impl.h
new file mode 100644
index 000000000..43c1fd1f0
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-repository-impl.h
@@ -0,0 +1,34 @@
+/* ipc-git-repository-impl.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 "ipc-git-repository.h"
+
+G_BEGIN_DECLS
+
+#define IPC_TYPE_GIT_REPOSITORY_IMPL (ipc_git_repository_impl_get_type())
+
+G_DECLARE_FINAL_TYPE (IpcGitRepositoryImpl, ipc_git_repository_impl, IPC, GIT_REPOSITORY_IMPL, 
IpcGitRepositorySkeleton)
+
+IpcGitRepository *ipc_git_repository_impl_open (GFile   *location,
+                                                GError **error);
+
+G_END_DECLS
diff --git a/src/plugins/git/daemon/ipc-git-service-impl.c b/src/plugins/git/daemon/ipc-git-service-impl.c
new file mode 100644
index 000000000..23c263ebb
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-service-impl.c
@@ -0,0 +1,300 @@
+/* ipc-git-service-impl.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 "ipc-git-service-impl"
+
+#include <libgit2-glib/ggit.h>
+
+#include "ipc-git-config-impl.h"
+#include "ipc-git-progress.h"
+#include "ipc-git-remote-callbacks.h"
+#include "ipc-git-repository-impl.h"
+#include "ipc-git-service-impl.h"
+#include "ipc-git-util.h"
+
+struct _IpcGitServiceImpl
+{
+  IpcGitServiceSkeleton  parent;
+  GHashTable            *repos;
+  GHashTable            *configs;
+};
+
+static gboolean
+ipc_git_service_impl_handle_discover (IpcGitService         *service,
+                                      GDBusMethodInvocation *invocation,
+                                      const gchar           *location)
+{
+  g_autoptr(GFile) file = NULL;
+  g_autoptr(GFile) found = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IPC_IS_GIT_SERVICE (service));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (location != NULL);
+
+  file = g_file_new_for_path (location);
+  found = ggit_repository_discover_full (file, TRUE, NULL, &error);
+
+  if (error != NULL)
+    complete_wrapped_error (invocation, error);
+  else
+    ipc_git_service_complete_discover (service, invocation, g_file_get_path (found));
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_service_impl_handle_create (IpcGitService         *service,
+                                    GDBusMethodInvocation *invocation,
+                                    const gchar           *location,
+                                    gboolean               is_bare)
+{
+  IpcGitServiceImpl *self = (IpcGitServiceImpl *)service;
+  g_autoptr(GgitRepository) repository = NULL;
+  g_autoptr(GFile) file = NULL;
+  g_autoptr(GFile) found = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *path = NULL;
+
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (location != NULL);
+
+  file = g_file_new_for_path (location);
+
+  if (!(repository = ggit_repository_init_repository (file, is_bare, &error)))
+    return complete_wrapped_error (invocation, error);
+
+  found = ggit_repository_get_location (repository);
+  path = g_file_get_path (found);
+
+  ipc_git_service_complete_create (service, invocation, path);
+
+  return TRUE;
+}
+
+static void
+ipc_git_service_impl_repository_closed_cb (IpcGitServiceImpl    *self,
+                                           IpcGitRepositoryImpl *repository)
+{
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (self));
+  g_assert (IPC_IS_GIT_REPOSITORY_IMPL (repository));
+
+  g_hash_table_remove (self->repos, repository);
+}
+
+static gboolean
+ipc_git_service_impl_handle_open (IpcGitService         *service,
+                                  GDBusMethodInvocation *invocation,
+                                  const gchar           *location)
+{
+  IpcGitServiceImpl *self = (IpcGitServiceImpl *)service;
+  g_autoptr(IpcGitRepository) repository = NULL;
+  g_autoptr(GFile) file = NULL;
+  g_autoptr(GFile) found = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *uuid = NULL;
+  g_autofree gchar *obj_path = NULL;
+  GDBusConnection *conn;
+
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (location != NULL);
+
+  file = g_file_new_for_path (location);
+
+  if (!(repository = ipc_git_repository_impl_open (file, &error)))
+    return complete_wrapped_error (invocation, error);
+
+  conn = g_dbus_method_invocation_get_connection (invocation);
+  uuid = g_dbus_generate_guid ();
+  obj_path = g_strdup_printf ("/org/gnome/Builder/Repository/%s", uuid);
+  g_hash_table_insert (self->repos, g_object_ref (repository), g_strdup (uuid));
+
+  g_signal_connect_object (repository,
+                           "closed",
+                           G_CALLBACK (ipc_git_service_impl_repository_closed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (repository), conn, obj_path, &error))
+    ipc_git_service_complete_open (service, invocation, obj_path);
+  else
+    complete_wrapped_error (invocation, error);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_git_service_impl_handle_clone (IpcGitService         *service,
+                                   GDBusMethodInvocation *invocation,
+                                   const gchar           *url,
+                                   const gchar           *location,
+                                   const gchar           *branch,
+                                   const gchar           *progress_path)
+{
+  g_autoptr(GgitRepository) repository = NULL;
+  g_autoptr(GgitCloneOptions) options = NULL;
+  g_autoptr(GgitRemoteCallbacks) callbacks = NULL;
+  g_autoptr(IpcGitProgress) progress = NULL;
+  GgitFetchOptions *fetch_options = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) file = NULL;
+  g_autoptr(GFile) clone_location = NULL;
+
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (service));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_assert (url != NULL);
+  g_assert (branch != NULL);
+  g_assert (location != NULL);
+
+  /* XXX: Make this threaded */
+
+  progress = ipc_git_progress_proxy_new_sync (g_dbus_method_invocation_get_connection (invocation),
+                                              G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                                              NULL,
+                                              progress_path,
+                                              NULL,
+                                              &error);
+  if (progress == NULL)
+    goto gerror;
+
+  file = g_file_new_for_path (location);
+
+  callbacks = ipc_git_remote_callbacks_new (progress);
+
+  fetch_options = ggit_fetch_options_new ();
+  ggit_fetch_options_set_remote_callbacks (fetch_options, callbacks);
+  ggit_fetch_options_set_download_tags (fetch_options, FALSE);
+
+  options = ggit_clone_options_new ();
+  ggit_clone_options_set_checkout_branch (options, branch);
+  ggit_clone_options_set_fetch_options (options, fetch_options);
+
+  if (!(repository = ggit_repository_clone (url, file, options, &error)))
+    goto gerror;
+
+  clone_location = ggit_repository_get_location (repository);
+  ipc_git_service_complete_clone (service,
+                                  invocation,
+                                  g_file_get_path (clone_location));
+
+gerror:
+  if (error != NULL)
+    complete_wrapped_error (invocation, error);
+
+  g_clear_pointer (&fetch_options, ggit_fetch_options_free);
+
+  return TRUE;
+}
+
+static void
+ipc_git_service_impl_config_closed_cb (IpcGitServiceImpl *self,
+                                       IpcGitConfigImpl  *config)
+{
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (self));
+  g_assert (IPC_IS_GIT_CONFIG_IMPL (config));
+
+  g_hash_table_remove (self->configs, config);
+}
+
+static gboolean
+ipc_git_service_impl_handle_load_config (IpcGitService         *service,
+                                         GDBusMethodInvocation *invocation)
+{
+  IpcGitServiceImpl *self = (IpcGitServiceImpl *)service;
+  g_autoptr(IpcGitConfig) config = NULL;
+  g_autoptr(GgitConfig) gconfig = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *uuid = NULL;
+  g_autofree gchar *obj_path = NULL;
+  GDBusConnection *conn;
+
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  if (!(gconfig = ggit_config_new_default (&error)))
+    return complete_wrapped_error (invocation, error);
+
+  config = ipc_git_config_impl_new (gconfig);
+
+  conn = g_dbus_method_invocation_get_connection (invocation);
+  uuid = g_dbus_generate_guid ();
+  obj_path = g_strdup_printf ("/org/gnome/Builder/Config/%s", uuid);
+  g_hash_table_insert (self->configs, g_object_ref (config), g_strdup (uuid));
+
+  g_signal_connect_object (config,
+                           "closed",
+                           G_CALLBACK (ipc_git_service_impl_config_closed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (config), conn, obj_path, &error))
+    ipc_git_service_complete_load_config (service, invocation, obj_path);
+  else
+    complete_wrapped_error (invocation, error);
+
+  return TRUE;
+}
+
+static void
+git_service_iface_init (IpcGitServiceIface *iface)
+{
+  iface->handle_discover = ipc_git_service_impl_handle_discover;
+  iface->handle_open = ipc_git_service_impl_handle_open;
+  iface->handle_create = ipc_git_service_impl_handle_create;
+  iface->handle_clone = ipc_git_service_impl_handle_clone;
+  iface->handle_load_config = ipc_git_service_impl_handle_load_config;
+}
+
+G_DEFINE_TYPE_WITH_CODE (IpcGitServiceImpl, ipc_git_service_impl, IPC_TYPE_GIT_SERVICE_SKELETON,
+                         G_IMPLEMENT_INTERFACE (IPC_TYPE_GIT_SERVICE, git_service_iface_init))
+
+static void
+ipc_git_service_impl_finalize (GObject *object)
+{
+  IpcGitServiceImpl *self = (IpcGitServiceImpl *)object;
+
+  g_clear_pointer (&self->configs, g_hash_table_unref);
+  g_clear_pointer (&self->repos, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ipc_git_service_impl_parent_class)->finalize (object);
+}
+
+static void
+ipc_git_service_impl_class_init (IpcGitServiceImplClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ipc_git_service_impl_finalize;
+}
+
+static void
+ipc_git_service_impl_init (IpcGitServiceImpl *self)
+{
+  self->configs = g_hash_table_new_full (NULL, NULL, g_object_unref, g_free);
+  self->repos = g_hash_table_new_full (NULL, NULL, g_object_unref, g_free);
+}
+
+IpcGitService *
+ipc_git_service_impl_new (void)
+{
+  return g_object_new (IPC_TYPE_GIT_SERVICE_IMPL, NULL);
+}
diff --git a/src/plugins/git/daemon/ipc-git-service-impl.h b/src/plugins/git/daemon/ipc-git-service-impl.h
new file mode 100644
index 000000000..f2e033707
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-service-impl.h
@@ -0,0 +1,33 @@
+/* ipc-git-service-impl.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 "ipc-git-service.h"
+
+G_BEGIN_DECLS
+
+#define IPC_TYPE_GIT_SERVICE_IMPL (ipc_git_service_impl_get_type())
+
+G_DECLARE_FINAL_TYPE (IpcGitServiceImpl, ipc_git_service_impl, IPC, GIT_SERVICE_IMPL, IpcGitServiceSkeleton)
+
+IpcGitService *ipc_git_service_impl_new (void);
+
+G_END_DECLS
diff --git a/src/plugins/git/daemon/ipc-git-types.h b/src/plugins/git/daemon/ipc-git-types.h
new file mode 100644
index 000000000..2a32bc9b0
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-types.h
@@ -0,0 +1,47 @@
+/* ipc-git-types.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 <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  IPC_GIT_REF_BRANCH = 0,
+  IPC_GIT_REF_TAG    = 1,
+} IpcGitRefKind;
+
+typedef enum
+{
+  IPC_GIT_COMMIT_FLAGS_NONE     = 0,
+  IPC_GIT_COMMIT_FLAGS_AMEND    = 1 << 0,
+  IPC_GIT_COMMIT_FLAGS_SIGNOFF  = 1 << 1,
+  IPC_GIT_COMMIT_FLAGS_GPG_SIGN = 1 << 2,
+} IpcGitCommitFlags;
+
+typedef enum
+{
+  IPC_GIT_PUSH_FLAGS_NONE   = 0,
+  IPC_GIT_PUSH_FLAGS_ATOMIC = 1 << 0,
+} IpcGitPushFlags;
+
+G_END_DECLS
diff --git a/src/plugins/git/daemon/ipc-git-util.h b/src/plugins/git/daemon/ipc-git-util.h
new file mode 100644
index 000000000..915e4f8e4
--- /dev/null
+++ b/src/plugins/git/daemon/ipc-git-util.h
@@ -0,0 +1,36 @@
+/* ipc-git-util.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
+
+static inline gboolean
+complete_wrapped_error (GDBusMethodInvocation *invocation,
+                        const GError          *error)
+{
+  g_autoptr(GError) wrapped = NULL;
+
+  wrapped = g_error_new (G_IO_ERROR,
+                         G_IO_ERROR_FAILED,
+                         "The operation failed. The original error was \"%s\"",
+                         error->message);
+  g_dbus_method_invocation_return_gerror (invocation, wrapped);
+
+  return TRUE;
+}
diff --git a/src/plugins/git/daemon/line-cache.c b/src/plugins/git/daemon/line-cache.c
new file mode 100644
index 000000000..fec8c19fd
--- /dev/null
+++ b/src/plugins/git/daemon/line-cache.c
@@ -0,0 +1,227 @@
+/* line-cache.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 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 "line-cache"
+
+#include <stdlib.h>
+
+#include "line-cache.h"
+
+struct _LineCache
+{
+  GArray *lines;
+};
+
+LineCache *
+line_cache_new (void)
+{
+  LineCache *self;
+
+  self = g_slice_new0 (LineCache);
+  self->lines = g_array_new (FALSE, FALSE, sizeof (LineEntry));
+
+  return self;
+}
+
+void
+line_cache_free (LineCache *self)
+{
+  if (self != NULL)
+    {
+      g_clear_pointer (&self->lines, g_array_unref);
+      g_slice_free (LineCache, self);
+    }
+}
+
+static LineEntry *
+get_entry (const LineCache *self,
+           gint             line)
+{
+  LineEntry empty = {0};
+  gint i;
+
+  /* Our access pattern is usally either the head (0) or somewhere
+   * near the end (ideally the end). So we try to access the elements
+   * in that order to avoid needless spinning.
+   */
+
+  if (line == 0)
+    {
+      if (self->lines->len == 0 ||
+          g_array_index (self->lines, LineEntry, 0).line != 0)
+        g_array_insert_val (self->lines, 0, empty);
+
+      return &g_array_index (self->lines, LineEntry, 0);
+    }
+
+  for (i = self->lines->len - 1; i >= 0; i--)
+    {
+      LineEntry *entry = &g_array_index (self->lines, LineEntry, i);
+
+      if (entry->line == line)
+        return entry;
+
+      if (entry->line < line)
+        break;
+    }
+
+  if (i < 0 || i < self->lines->len)
+    i++;
+
+  g_assert (i >= 0);
+  g_assert (i <= self->lines->len);
+
+  empty.line = line;
+  g_array_insert_val (self->lines, i, empty);
+  return &g_array_index (self->lines, LineEntry, i);
+}
+
+void
+line_cache_mark_range (LineCache *self,
+                       gint       start_line,
+                       gint       end_line,
+                       LineMark   mark)
+{
+  g_assert (self != NULL);
+  g_assert (end_line >= start_line);
+  g_assert (mark != 0);
+
+  do
+    {
+      LineEntry *entry = get_entry (self, start_line);
+      entry->mark |= mark;
+      start_line++;
+    }
+  while (start_line < end_line);
+}
+
+static gint
+compare_by_line (gconstpointer a,
+                 gconstpointer b)
+{
+  const gint *line = a;
+  const LineEntry *entry = b;
+
+  return *line - entry->line;
+}
+
+LineMark
+line_cache_get_mark (const LineCache *self,
+                     gint             line)
+{
+
+  const LineEntry *ret;
+
+  ret = bsearch (&line, (gconstpointer)self->lines->data,
+                 self->lines->len, sizeof (LineEntry),
+                 (GCompareFunc)compare_by_line);
+
+  return ret ? ret->mark : 0;
+}
+
+static const LineEntry *
+line_cache_first_in_range (const LineCache *self,
+                           gint             start_line,
+                           gint             end_line)
+{
+  gint L;
+  gint R;
+
+  if (self->lines->len == 0)
+    return NULL;
+
+  L = 0;
+  R = self->lines->len - 1;
+
+  while (L <= R)
+    {
+      gint m = (L + R) / 2;
+      const LineEntry *entry = &g_array_index (self->lines, LineEntry, m);
+
+      if (entry->line < start_line)
+        {
+          L = m + 1;
+          continue;
+        }
+      else if (entry->line > end_line)
+        {
+          R = m - 1;
+          continue;
+        }
+
+      for (gint p = m; p >= 0; p--)
+        {
+          const LineEntry *prev = &g_array_index (self->lines, LineEntry, p);
+
+          if (prev->line >= start_line)
+            entry = prev;
+        }
+
+      return entry;
+    }
+
+  return NULL;
+}
+
+void
+line_cache_foreach_in_range (const LineCache *self,
+                             gint             start_line,
+                             gint             end_line,
+                             GFunc            callback,
+                             gpointer         user_data)
+{
+  const LineEntry *base;
+  const LineEntry *end;
+  const LineEntry *entry = NULL;
+
+  g_assert (self != NULL);
+  g_assert (self->lines != NULL);
+
+  if (self->lines->len == 0)
+    return;
+
+  base = &g_array_index (self->lines, LineEntry, 0);
+  end = base + self->lines->len;
+
+  if ((entry = line_cache_first_in_range (self, start_line, end_line)))
+    {
+      for (; entry < end && entry->line <= end_line; entry++)
+        callback ((gpointer)entry, user_data);
+    }
+}
+
+GVariant *
+line_cache_to_variant (const LineCache *self)
+{
+  GVariantBuilder builder;
+
+  g_assert (self != NULL);
+  g_assert (self->lines != NULL);
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(uu)"));
+
+  for (guint i = 0; i < self->lines->len; i++)
+    {
+      const LineEntry *entry = &g_array_index (self->lines, LineEntry, i);
+      g_variant_builder_add (&builder, "(uu)", entry->line, entry->mark);
+    }
+
+  return g_variant_take_ref (g_variant_builder_end (&builder));
+}
diff --git a/src/plugins/git/daemon/line-cache.h b/src/plugins/git/daemon/line-cache.h
new file mode 100644
index 000000000..5ba864be8
--- /dev/null
+++ b/src/plugins/git/daemon/line-cache.h
@@ -0,0 +1,60 @@
+/* line-cache.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 <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _LineCache LineCache;
+
+typedef enum
+{
+  LINE_MARK_ADDED            = 1 << 0,
+  LINE_MARK_REMOVED          = 1 << 1,
+  LINE_MARK_CHANGED          = 1 << 2,
+  LINE_MARK_PREVIOUS_REMOVED = 1 << 3,
+} LineMark;
+
+typedef struct
+{
+  guint line : 28;
+  guint mark : 4;
+} LineEntry;
+
+LineCache *line_cache_new              (void);
+void       line_cache_free             (LineCache       *self);
+void       line_cache_mark_range       (LineCache       *self,
+                                        gint             start_line,
+                                        gint             end_line,
+                                        LineMark         mark);
+void       line_cache_foreach_in_range (const LineCache *self,
+                                        gint             start_line,
+                                        gint             end_line,
+                                        GFunc            callback,
+                                        gpointer         user_data);
+LineMark   line_cache_get_mark         (const LineCache *self,
+                                        gint             line);
+GVariant  *line_cache_to_variant       (const LineCache *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (LineCache, line_cache_free)
+
+G_END_DECLS
diff --git a/src/plugins/git/daemon/meson.build b/src/plugins/git/daemon/meson.build
new file mode 100644
index 000000000..4d7b14cea
--- /dev/null
+++ b/src/plugins/git/daemon/meson.build
@@ -0,0 +1,71 @@
+gnome_builder_git_deps = [
+  libgiounix_dep,
+  libgit_dep,
+]
+
+change_monitor_src = gnome.gdbus_codegen('ipc-git-change-monitor',
+           sources: 'org.gnome.Builder.Git.ChangeMonitor.xml',
+  interface_prefix: 'org.gnome.Builder.',
+         namespace: 'Ipc',
+)
+
+config_src = gnome.gdbus_codegen('ipc-git-config',
+           sources: 'org.gnome.Builder.Git.Config.xml',
+  interface_prefix: 'org.gnome.Builder.',
+         namespace: 'Ipc',
+)
+
+progress_src = gnome.gdbus_codegen('ipc-git-progress',
+           sources: 'org.gnome.Builder.Git.Progress.xml',
+  interface_prefix: 'org.gnome.Builder.',
+         namespace: 'Ipc',
+)
+
+repository_src = gnome.gdbus_codegen('ipc-git-repository',
+           sources: 'org.gnome.Builder.Git.Repository.xml',
+  interface_prefix: 'org.gnome.Builder.',
+         namespace: 'Ipc',
+)
+
+service_src = gnome.gdbus_codegen('ipc-git-service',
+           sources: 'org.gnome.Builder.Git.Service.xml',
+  interface_prefix: 'org.gnome.Builder.',
+         namespace: 'Ipc',
+)
+
+gnome_builder_git_sources = [
+  'gnome-builder-git.c',
+  'ipc-git-config-impl.c',
+  'ipc-git-change-monitor-impl.c',
+  'ipc-git-index-monitor.c',
+  'ipc-git-remote-callbacks.c',
+  'ipc-git-repository-impl.c',
+  'ipc-git-service-impl.c',
+  'line-cache.c',
+  change_monitor_src,
+  config_src,
+  progress_src,
+  repository_src,
+  service_src,
+]
+
+gnome_builder_git = executable('gnome-builder-git', gnome_builder_git_sources,
+           install: true,
+       install_dir: get_option('libexecdir'),
+      dependencies: gnome_builder_git_deps,
+)
+
+test_git_sources = [
+  'test-git.c',
+  change_monitor_src,
+  config_src,
+  progress_src,
+  repository_src,
+  service_src,
+]
+
+test_git = executable('test-git', 'test-git.c', test_git_sources,
+  dependencies: [ libgiounix_dep ],
+)
+
+# test('test-git', test_git)
diff --git a/src/plugins/git/daemon/org.gnome.Builder.Git.ChangeMonitor.xml 
b/src/plugins/git/daemon/org.gnome.Builder.Git.ChangeMonitor.xml
new file mode 100644
index 000000000..bdfbdf716
--- /dev/null
+++ b/src/plugins/git/daemon/org.gnome.Builder.Git.ChangeMonitor.xml
@@ -0,0 +1,34 @@
+<!DOCTYPE node PUBLIC
+        "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"; >
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <!--
+    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
+  -->
+  <interface name="org.gnome.Builder.Git.ChangeMonitor">
+    <property name="path" type="ay" access="read"/>
+    <signal name="Closed"/>
+    <method name="UpdateContent">
+      <arg name="contents" direction="in" type="ay"/>
+    </method>
+    <method name="ListChanges">
+      <arg name="changes" direction="out" type="a(uu)"/>
+    </method>
+    <method name="Close"/>
+  </interface>
+</node>
diff --git a/src/plugins/git/daemon/org.gnome.Builder.Git.Config.xml 
b/src/plugins/git/daemon/org.gnome.Builder.Git.Config.xml
new file mode 100644
index 000000000..e45fdf062
--- /dev/null
+++ b/src/plugins/git/daemon/org.gnome.Builder.Git.Config.xml
@@ -0,0 +1,35 @@
+<!DOCTYPE node PUBLIC
+        "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"; >
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <!--
+    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
+  -->
+  <interface name="org.gnome.Builder.Git.Config">
+    <signal name="Closed"/>
+    <method name="ReadKey">
+      <arg name="key" direction="in" type="s"/>
+      <arg name="value" direction="out" type="s"/>
+    </method>
+    <method name="WriteKey">
+      <arg name="key" direction="in" type="s"/>
+      <arg name="value" direction="in" type="s"/>
+    </method>
+    <method name="Close"/>
+  </interface>
+</node>
diff --git a/src/plugins/git/daemon/org.gnome.Builder.Git.Progress.xml 
b/src/plugins/git/daemon/org.gnome.Builder.Git.Progress.xml
new file mode 100644
index 000000000..ed55d6e15
--- /dev/null
+++ b/src/plugins/git/daemon/org.gnome.Builder.Git.Progress.xml
@@ -0,0 +1,27 @@
+<!DOCTYPE node PUBLIC
+        "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"; >
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <!--
+    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
+  -->
+  <interface name="org.gnome.Builder.Git.Progress">
+    <property name="Fraction" type="d" access="readwrite"/>
+    <property name="Message" type="s" access="readwrite"/>
+  </interface>
+</node>
diff --git a/src/plugins/git/daemon/org.gnome.Builder.Git.Repository.xml 
b/src/plugins/git/daemon/org.gnome.Builder.Git.Repository.xml
new file mode 100644
index 000000000..5ec8d83c0
--- /dev/null
+++ b/src/plugins/git/daemon/org.gnome.Builder.Git.Repository.xml
@@ -0,0 +1,169 @@
+<!DOCTYPE node PUBLIC
+        "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"; >
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <!--
+    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
+  -->
+  <interface name="org.gnome.Builder.Git.Repository">
+    <!--
+      Changed:
+
+      This signal is emitted when the repository is updated.
+      Clients may want to refresh their cached information.
+    -->
+    <signal name="Changed"/>>
+    <!--
+      Closed:
+
+      This signal is emitted right before the repository is removed form the connection.
+    -->
+    <signal name="Closed"/>
+    <!--
+      Branch:
+
+      The current branch of the repository. Updated if SwitchBranch is called.
+    -->
+    <property name="Branch" type="s" access="read"/>
+    <!--
+      Location:
+
+      The location of the .git directory for the repository.
+    -->
+    <property name="Location" type="ay" access="read"/>
+    <!--
+      Close:
+
+      Closes the repository, which will emit the "Closed" signal.
+      The repository will be removed from the connection after calling this method.
+    -->
+    <method name="Close"/>
+    <!--
+      PathIsIgnored:
+      @path: the path within the repository
+
+      Checks if @path is is ignored within the repository.
+    -->
+    <method name="PathIsIgnored">
+      <arg name="path" direction="in" type="ay"/>
+      <arg name="ignored" direction="out" type="b"/>
+    </method>
+    <!--
+      ListRefsByKind:
+      @kind: The kind of ref to list (branch or tag)
+
+      Lists refs based on their kind. 0 for branches, 1 for tags.
+    -->
+    <method name="ListRefsByKind">
+      <arg name="kind" direction="in" type="u"/>
+      <arg name="refs" direction="out" type="as"/>
+    </method>
+    <!--
+      ListStatus:
+      @path: the path within the repository to list
+      @recursive: if all descendants should be checked too
+
+      Lists the status of files within the repository, starting from @path
+    -->
+    <method name="ListStatus">
+      <arg name="path" direction="in" type="ay"/>
+      <arg name="files" direction="out" type="a(su)"/>
+    </method>
+    <!--
+      SwitchBranch:
+      @branch: the name of the branch, such as "refs/heads/master"
+
+      Switches the repository to the branch named @branch.
+    -->
+    <method name="SwitchBranch">
+      <arg name="name" direction="in" type="s"/>
+    </method>
+    <!--
+      CreateChangeMonitor:
+      @path: the path within the repository
+
+      Create a new org.gnome.Builder.Git.ChangeMonitor to quickly diff file contents for line changes.
+    -->
+    <method name="CreateChangeMonitor">
+      <arg name="path" direction="in" type="s"/>
+      <arg name="obj_path" direction="out" type="o"/>
+    </method>
+    <!--
+      StageFile:
+      @path: the file to be staged
+
+      Stages the file at @path.
+    -->
+    <method name="StageFile">
+      <arg name="path" direction="in" type="ay"/>
+    </method>
+    <!--
+      Commit:
+      @details: various author details
+      @flags: the GbpGitCommitFlags flags such as amend or signoff
+
+      Commits to the repository.
+
+      Details can be set with the following keys with string values.
+
+       - AUTHOR_NAME
+       - AUTHOR_EMAIL
+       - COMMITTER_NAME
+       - COMMITTER_EMAIL
+       - COMMIT_MSG
+
+    -->
+    <method name="Commit">
+      <arg name="details" direction="in" type="a{sv}"/>
+      <arg name="flags" direction="in" type="u"/>
+    </method>
+    <!--
+      Push:
+      @remote: the name or URL of the remote
+      @ref_names: a list of ref specs to push, such as "refs/heads/master" or 
"refs/heads/master:refs/heads/master"
+      @flags: the GbpGitPushFlags such as atomic
+      @progress_path: an object path to an org.gnome.Builder.Git.Progress
+
+      Pushes the repository to a remote URL (can be full URL or shortname like origin).
+    -->
+    <method name="Push">
+      <arg name="remote" direction="in" type="s"/>
+      <arg name="ref_specs" direction="in" type="as"/>
+      <arg name="flags" direction="in" type="u"/>
+      <arg name="progress_path" direction="in" type="o"/>
+    </method>
+    <!--
+      LoadConfig:
+
+      Loads a new org.gnome.Git.Config object and returns the path to it.
+    -->
+    <method name="LoadConfig">
+      <arg name="config_path" direction="out" type="o"/>
+    </method>
+    <!--
+      UpdateSubmodules:
+      @progress_path: object path to a org.gnome.Git.Progress to notify.
+
+      Updates the various submodules in a repository.
+    -->
+    <method name="UpdateSubmodules">
+      <arg name="init" direction="in" type="b"/>
+      <arg name="progress_path" direction="in" type="o"/>
+    </method>
+  </interface>
+</node>
diff --git a/src/plugins/git/daemon/org.gnome.Builder.Git.Service.xml 
b/src/plugins/git/daemon/org.gnome.Builder.Git.Service.xml
new file mode 100644
index 000000000..8382aeef8
--- /dev/null
+++ b/src/plugins/git/daemon/org.gnome.Builder.Git.Service.xml
@@ -0,0 +1,73 @@
+<!DOCTYPE node PUBLIC
+        "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"; >
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <!--
+    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
+  -->
+  <interface name="org.gnome.Builder.Git.Service">
+    <method name="Discover">
+      <arg name="location" direction="in" type="ay">
+        <doc:doc><doc:summary>The location at which to begin discovery of a 
repository.</doc:summary></doc:doc>
+      </arg>
+      <arg name="git_location" direction="out" type="ay">
+        <doc:doc><doc:summary>Location of the git repository.</doc:summary></doc:doc>
+      </arg>
+    </method>
+    <method name="Open">
+      <arg name="location" direction="in" type="ay">
+        <doc:doc><doc:summary>The location of the repository.</doc:summary></doc:doc>
+      </arg>
+      <arg name="repository" direction="out" type="o">
+        <doc:doc><doc:summary>A repository object.</doc:summary></doc:doc>
+      </arg>
+    </method>
+    <method name="Create">
+      <doc:doc><doc:summary>Creates a new repository.</doc:summary></doc:doc>
+      <arg name="location" direction="in" type="ay">
+        <doc:doc><doc:summary>The location for the repository.</doc:summary></doc:doc>
+      </arg>
+      <arg name="is_bare" direction="in" type="b">
+        <doc:doc><doc:summary>If a bare repository should be created.</doc:summary></doc:doc>
+      </arg>
+      <arg name="git_location" direction="out" type="ay">
+        <doc:doc><doc:summary>The location of the .git directory.</doc:summary></doc:doc>
+      </arg>
+    </method>
+    <method name="Clone">
+      <arg name="url" direction="in" type="s">
+        <doc:doc><doc:summary>The URL to the repository.</doc:summary></doc:doc>
+      </arg>
+      <arg name="location" direction="in" type="ay">
+        <doc:doc><doc:summary>The location to place the repository.</doc:summary></doc:doc>
+      </arg>
+      <arg name="branch" direction="in" type="s">
+        <doc:doc><doc:summary>The branch to clone.</doc:summary></doc:doc>
+      </arg>
+      <arg name="progress" direction="in" type="o">
+        <doc:doc><doc:summary>The path to an org.gnome.Builder.Git.Progress object on the callers 
connection.</doc:summary></doc:doc>
+      </arg>
+      <arg name="git_location" direction="out" type="ay">
+        <doc:doc><doc:summary>A repository object.</doc:summary></doc:doc>
+      </arg>
+    </method>
+    <method name="LoadConfig">
+      <arg name="config_path" direction="out" type="o"/>
+    </method>
+  </interface>
+</node>
diff --git a/src/plugins/git/daemon/test-git.c b/src/plugins/git/daemon/test-git.c
new file mode 100644
index 000000000..b79fc86d5
--- /dev/null
+++ b/src/plugins/git/daemon/test-git.c
@@ -0,0 +1,507 @@
+/* test-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
+ */
+
+#include <glib-unix.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "ipc-git-change-monitor.h"
+#include "ipc-git-config.h"
+#include "ipc-git-progress.h"
+#include "ipc-git-repository.h"
+#include "ipc-git-service.h"
+#include "ipc-git-types.h"
+
+#define PROGRESS_PATH "/org/gnome/Builder/Git/Progress/1"
+
+static GMainLoop *main_loop;
+static gchar tmpdir[] = { "test-git-XXXXXX" };
+static gchar tmpdir_push[] = { "test-git-bare-XXXXXX" };
+
+static void
+cleanup_dir (void)
+{
+  g_autofree gchar *command = NULL;
+  command = g_strdup_printf ("rm -rf '%s' '%s'", tmpdir, tmpdir_push);
+  g_message ("%s", command);
+  system (command);
+}
+
+static void
+notify_fraction_cb (IpcGitProgress *progress)
+{
+  g_message ("Fraction = %lf", ipc_git_progress_get_fraction (progress));
+}
+
+static void
+notify_message_cb (IpcGitProgress *progress)
+{
+  g_message ("Message = %s", ipc_git_progress_get_message (progress));
+}
+
+static void
+do_test_config (IpcGitConfig *config)
+{
+  g_autoptr(GError) error = NULL;
+  /* we need all of these to run test-git succesfully (for gpg) */
+  static const gchar *keys[] = { "user.name", "user.email", "user.signingkey" };
+  gboolean ret;
+
+  g_assert (IPC_IS_GIT_CONFIG (config));
+
+  g_message ("Checking for keys required by test-git");
+
+  for (guint i = 0; i < G_N_ELEMENTS (keys); i++)
+    {
+      g_autofree gchar *value = NULL;
+
+      ret = ipc_git_config_call_read_key_sync (config, keys[i], &value, NULL, &error);
+      g_assert_no_error (error);
+      g_assert_true (ret);
+
+      g_message ("  %s = %s", keys[i], value);
+    }
+
+  g_message ("Closing config");
+  ret = ipc_git_config_call_close_sync (config, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+}
+
+static void
+test_config (IpcGitService *service)
+{
+  g_autofree gchar *config_path = NULL;
+  g_autoptr(IpcGitConfig) config = NULL;
+  g_autoptr(GError) error = NULL;
+  /* we need all of these to run test-git succesfully (for gpg) */
+  GDBusConnection *conn;
+  gboolean ret;
+
+  g_assert (IPC_IS_GIT_SERVICE (service));
+
+  g_message ("Creating global config");
+  ret = ipc_git_service_call_load_config_sync (service, &config_path, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Config created at %s", config_path);
+  conn = g_dbus_proxy_get_connection (G_DBUS_PROXY (service));
+  config = ipc_git_config_proxy_new_sync (conn, 0, NULL, config_path, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (IPC_IS_GIT_CONFIG (config));
+
+  do_test_config (config);
+}
+
+static void
+test_push (IpcGitService    *service,
+           IpcGitRepository *repository)
+{
+  g_autofree gchar *location = NULL;
+  g_autofree gchar *url = NULL;
+  g_autoptr(GError) error = NULL;
+  static const gchar *ref_names[] = { "refs/heads/master:refs/heads/master", NULL };
+  IpcGitPushFlags flags = IPC_GIT_PUSH_FLAGS_NONE;
+  gboolean ret;
+
+  g_message ("Creating bare repository for push");
+  ret = ipc_git_service_call_create_sync (service, tmpdir_push, TRUE, &location, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+  g_message ("Bare repository created at %s", location);
+
+  url = g_strdup_printf ("file://%s/%s", g_get_current_dir (), tmpdir_push);
+
+  g_message ("Pushing to %s", url);
+  ret = ipc_git_repository_call_push_sync (repository, url, ref_names, flags, PROGRESS_PATH, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+  g_message ("  Pushed");
+}
+
+static GVariant *
+create_commit_details (const gchar *commit_msg)
+{
+  GVariantDict dict;
+
+  g_variant_dict_init (&dict, NULL);
+  g_variant_dict_insert (&dict, "AUTHOR_NAME", "s", "Me Myself");
+  g_variant_dict_insert (&dict, "AUTHOR_EMAIL", "s", "me@localhost");
+  g_variant_dict_insert (&dict, "COMMITTER_NAME", "s", "Me Myself");
+  g_variant_dict_insert (&dict, "COMMITTER_EMAIL", "s", "me@localhost");
+  g_variant_dict_insert (&dict, "COMMIT_MSG", "s", commit_msg ?: "");
+
+  return g_variant_take_ref (g_variant_dict_end (&dict));
+}
+
+static void
+test_clone (IpcGitService *service)
+{
+  g_autoptr(IpcGitProgress) progress = NULL;
+  g_autoptr(IpcGitRepository) repository = NULL;
+  g_autoptr(IpcGitChangeMonitor) monitor = NULL;
+  g_autoptr(IpcGitConfig) config = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GVariant) files = NULL;
+  g_autoptr(GVariant) details = NULL;
+  g_autofree gchar *testfile = NULL;
+  g_autofree gchar *location = NULL;
+  g_autofree gchar *obj_path = NULL;
+  g_autofree gchar *config_path = NULL;
+  g_auto(GStrv) tags = NULL;
+  g_auto(GStrv) branches = NULL;
+  g_autoptr(GVariant) changes = NULL;
+  g_autofree gchar *monitor_path = NULL;
+  GDBusConnection *conn;
+  GVariantIter iter;
+  gboolean ret;
+
+  g_assert (IPC_IS_GIT_SERVICE (service));
+
+  g_message ("Creating local progress object");
+  conn = g_dbus_proxy_get_connection (G_DBUS_PROXY (service));
+  progress = ipc_git_progress_skeleton_new ();
+  g_signal_connect (progress, "notify::fraction", G_CALLBACK (notify_fraction_cb), NULL);
+  g_signal_connect (progress, "notify::message", G_CALLBACK (notify_message_cb), NULL);
+  g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (progress),
+                                    conn,
+                                    PROGRESS_PATH,
+                                    &error);
+  g_assert_no_error (error);
+
+  g_message ("Cloning hello");
+  ret = ipc_git_service_call_clone_sync (service,
+                                         "https://gitlab.gnome.org/chergert/hello";,
+                                         tmpdir,
+                                         "master",
+                                         PROGRESS_PATH,
+                                         &location,
+                                         NULL,
+                                         &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+
+  g_message ("Cloned to %s", location);
+
+  ret = ipc_git_service_call_open_sync (service, location, &obj_path, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+
+  repository = ipc_git_repository_proxy_new_sync (conn, 0, NULL, obj_path, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (IPC_IS_GIT_REPOSITORY (repository));
+
+  g_message ("Initializing submodules");
+  ret = ipc_git_repository_call_update_submodules_sync (repository, TRUE, PROGRESS_PATH, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Creating repository config");
+  ret = ipc_git_repository_call_load_config_sync (repository, &config_path, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Repository config created at %s", config_path);
+  conn = g_dbus_proxy_get_connection (G_DBUS_PROXY (service));
+  config = ipc_git_config_proxy_new_sync (conn, 0, NULL, config_path, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (IPC_IS_GIT_CONFIG (config));
+
+  do_test_config (config);
+
+  ret = ipc_git_repository_call_list_refs_by_kind_sync (repository, IPC_GIT_REF_BRANCH, &branches, NULL, 
&error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Branches:");
+  for (guint i = 0; branches[i]; i++)
+    g_message ("  %s", branches[i]);
+
+  ret = ipc_git_repository_call_list_refs_by_kind_sync (repository, IPC_GIT_REF_TAG, &tags, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Tags:");
+  for (guint i = 0; tags[i]; i++)
+    g_message ("  %s", tags[i]);
+
+  g_message ("Switching to branch %s", branches[0]);
+  ret = ipc_git_repository_call_switch_branch_sync (repository, branches[0], NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  testfile = g_build_filename (tmpdir, "foobar", NULL);
+  g_message ("Creating empty file in tree '%s'", testfile);
+  ret = g_file_set_contents (testfile, "test", 4, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Listing status");
+  ret = ipc_git_repository_call_list_status_sync (repository, "", &files, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  if (g_variant_iter_init (&iter, files) > 0)
+    {
+      const gchar *path;
+      guint32 state;
+
+      while (g_variant_iter_next (&iter, "(&su)", &path, &state))
+        g_message ("  %s: %u", path, state);
+    }
+
+  g_message ("Staging foobar");
+  ret = ipc_git_repository_call_stage_file_sync (repository, "foobar", NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Committing to local repository");
+  details = create_commit_details ("My commit message");
+  ret = ipc_git_repository_call_commit_sync (repository, details, 0, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_file_set_contents (testfile, "test test", 9, &error);
+  g_assert_no_error (error);
+  g_message ("Staging foobar");
+  ret = ipc_git_repository_call_stage_file_sync (repository, "foobar", NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Committing with gpg sign");
+  details = create_commit_details ("My signed message");
+  ret = ipc_git_repository_call_commit_sync (repository, details, IPC_GIT_COMMIT_FLAGS_GPG_SIGN, NULL, 
&error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Amending previous commit");
+  details = create_commit_details ("My amended commit message");
+  ret = ipc_git_repository_call_commit_sync (repository, details,
+                                             IPC_GIT_COMMIT_FLAGS_AMEND |
+                                             IPC_GIT_COMMIT_FLAGS_SIGNOFF,
+                                             NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Creating change monitor");
+  ret = ipc_git_repository_call_create_change_monitor_sync (repository, "foobar", &monitor_path, NULL, 
&error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("  Created at path %s", monitor_path);
+  monitor = ipc_git_change_monitor_proxy_new_sync (conn, 0, NULL, monitor_path, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (IPC_IS_GIT_CHANGE_MONITOR (monitor));
+
+  g_message ("  Updating file contents");
+  ret = ipc_git_change_monitor_call_update_content_sync (monitor, "this\nis\nsome\ntext\nhere", NULL, 
&error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("  Listing file changes");
+  ret = ipc_git_change_monitor_call_list_changes_sync (monitor, &changes, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+  g_assert_nonnull (changes);
+  g_assert_true (g_variant_is_of_type (changes, G_VARIANT_TYPE ("a(uu)")));
+
+  {
+    g_autofree gchar *str = g_variant_print (changes, TRUE);
+    g_message ("    %s", str);
+  }
+
+  g_message ("Closing change monitor");
+  ret = ipc_git_change_monitor_call_close_sync (monitor, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  test_push (service, repository);
+
+  g_message ("Closing");
+  ret = ipc_git_repository_call_close_sync (repository, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  cleanup_dir ();
+
+  g_main_loop_quit (main_loop);
+}
+
+static void
+open_cb (IpcGitService *service,
+         GAsyncResult  *result,
+         gpointer       user_data)
+{
+  g_autoptr(IpcGitRepository) repository = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *path = NULL;
+  GDBusConnection *connection;
+  gboolean ignored = FALSE;
+  gboolean ret;
+
+  g_assert (IPC_IS_GIT_SERVICE (service));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  ret = ipc_git_service_call_open_finish (service, &path, result, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Opened %s", path);
+
+  connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (service));
+  repository = ipc_git_repository_proxy_new_sync (connection,
+                                                  0,
+                                                  NULL,
+                                                  path,
+                                                  NULL,
+                                                  &error);
+  g_assert_no_error (error);
+  g_assert_true (IPC_IS_GIT_REPOSITORY (repository));
+
+  g_message ("Branch: %s", ipc_git_repository_get_branch (repository));
+  g_message ("Location: %s", ipc_git_repository_get_location (repository));
+
+  ret = ipc_git_repository_call_path_is_ignored_sync (repository, "build", &ignored, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("\"build\" ignored? %d", ignored);
+
+  ret = ipc_git_repository_call_close_sync (repository, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Repository closed");
+
+  cleanup_dir ();
+
+  g_message ("Testing cloning");
+  test_clone (service);
+}
+
+static void
+discover_cb (IpcGitService *service,
+             GAsyncResult  *result,
+             gpointer       user_data)
+{
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *location = NULL;
+  gboolean ret;
+
+  g_assert (IPC_IS_GIT_SERVICE (service));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  ret = ipc_git_service_call_discover_finish (service, &location, result, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Discover => %s", location);
+
+  ipc_git_service_call_open (service,
+                             location,
+                             NULL,
+                             (GAsyncReadyCallback) open_cb,
+                             NULL);
+}
+
+static void
+create_cb (IpcGitService *service,
+           GAsyncResult  *result,
+           gpointer       user_data)
+{
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *location = NULL;
+  gboolean ret;
+
+  g_assert (IPC_IS_GIT_SERVICE (service));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  ret = ipc_git_service_call_create_finish (service, &location, result, &error);
+  g_assert_no_error (error);
+  g_assert_true (ret);
+
+  g_message ("Created repository at %s", location);
+
+  ipc_git_service_call_discover (service,
+                                 location,
+                                 NULL,
+                                 (GAsyncReadyCallback) discover_cb,
+                                 NULL);
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GIOStream) stream = NULL;
+  g_autoptr(GInputStream) stdout_stream = NULL;
+  g_autoptr(GOutputStream) stdin_stream = NULL;
+  g_autoptr(GDBusConnection) connection = NULL;
+  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(IpcGitService) service = NULL;
+
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+  subprocess = g_subprocess_launcher_spawn (launcher, &error, "./gnome-builder-git", NULL);
+
+  if (subprocess == NULL)
+    g_error ("%s", error->message);
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+  stdin_stream = g_subprocess_get_stdin_pipe (subprocess);
+  stdout_stream = g_subprocess_get_stdout_pipe (subprocess);
+  stream = g_simple_io_stream_new (stdout_stream, stdin_stream);
+  connection = g_dbus_connection_new_sync (stream,
+                                           NULL,
+                                           G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING,
+                                           NULL,
+                                           NULL,
+                                           &error);
+  g_assert_no_error (error);
+  g_assert_true (G_IS_DBUS_CONNECTION (connection));
+
+  g_dbus_connection_set_exit_on_close (connection, FALSE);
+  g_dbus_connection_start_message_processing (connection);
+
+  service = ipc_git_service_proxy_new_sync (connection, 0, NULL, "/org/gnome/Builder/Git", NULL, &error);
+  g_assert_no_error (error);
+  g_assert_true (IPC_IS_GIT_SERVICE (service));
+
+  g_mkdtemp (tmpdir);
+  g_mkdtemp (tmpdir_push);
+
+  test_config (service);
+
+  ipc_git_service_call_create (service,
+                               tmpdir,
+                               FALSE,
+                               NULL,
+                               (GAsyncReadyCallback) create_cb,
+                               NULL);
+
+  g_main_loop_run (main_loop);
+
+  return EXIT_SUCCESS;
+}
diff --git a/src/plugins/git/meson.build b/src/plugins/git/meson.build
index 894da68ac..e501d517f 100644
--- a/src/plugins/git/meson.build
+++ b/src/plugins/git/meson.build
@@ -1,5 +1,7 @@
 if get_option('plugin_git')
 
+subdir('daemon')
+
 plugins_sources += files([
   'git-plugin.c',
   'gbp-git-branch.c',



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]