[gnome-builder/wip/chergert/gnome-builder-git] daemon: add gnome-builder-git daemon
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/gnome-builder-git] daemon: add gnome-builder-git daemon
- Date: Sat, 6 Apr 2019 00:35:08 +0000 (UTC)
commit 3904de96a0d48a41c285202effb7036ed73248c6
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]