[gnome-builder/wip/gtk4-port] plugins/git: implement listing remote branches



commit 96c09026dc891d474ffb0101c78c0bc98410cb32
Author: Christian Hergert <chergert redhat com>
Date:   Tue May 31 16:02:44 2022 -0700

    plugins/git: implement listing remote branches
    
    Particularly from the IdeVcsCloner interface, but we could use this
    elsewhere should we need to.
    
    Note that by using this interface, the git plugin will create a
    gnome-builder-git daemon instance for the IdeContext. Not really a big
    deal in most cases, but might be strange if you tried to list branches,
    then wen't back and opened some project without git vcs in that the
    process will linger until the context ends.

 src/plugins/git/daemon/ipc-git-service-impl.c      | 161 ++++++++++++++++++++-
 .../git/daemon/org.gnome.Builder.Git.Service.xml   |   5 +
 src/plugins/git/gbp-git-vcs-cloner.c               | 159 +++++++++++++++++++-
 3 files changed, 323 insertions(+), 2 deletions(-)
---
diff --git a/src/plugins/git/daemon/ipc-git-service-impl.c b/src/plugins/git/daemon/ipc-git-service-impl.c
index be2b6d876..b991dfd8c 100644
--- a/src/plugins/git/daemon/ipc-git-service-impl.c
+++ b/src/plugins/git/daemon/ipc-git-service-impl.c
@@ -27,6 +27,7 @@
 #include "ipc-git-remote-callbacks.h"
 #include "ipc-git-repository-impl.h"
 #include "ipc-git-service-impl.h"
+#include "ipc-git-types.h"
 #include "ipc-git-util.h"
 
 struct _IpcGitServiceImpl
@@ -320,6 +321,163 @@ ipc_git_service_impl_handle_load_config (IpcGitService         *service,
   return TRUE;
 }
 
+static void
+rm_rf (const char *dir)
+{
+  g_spawn_sync (NULL,
+                (char **)(const char * const[]) { "rm", "-rf", dir, NULL },
+                NULL,
+                G_SPAWN_SEARCH_PATH,
+                NULL, NULL,
+                NULL, NULL, NULL, NULL);
+}
+
+typedef GgitRemoteHead **RemoteHeadList;
+
+static void
+remote_head_list_free (RemoteHeadList list)
+{
+  for (guint i = 0; list[i]; i++)
+    ggit_remote_head_unref (list[i]);
+  g_free (list);
+}
+
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC (RemoteHeadList, remote_head_list_free, NULL)
+
+typedef struct
+{
+  GgitRemoteCallbacks *callbacks;
+  char                *uri;
+  IpcGitRefKind        kind;
+} ListRemoteRefsByKind;
+
+static void
+list_remote_refs_by_kind_free (gpointer data)
+{
+  ListRemoteRefsByKind *state = data;
+
+  g_clear_pointer (&state->uri, g_free);
+  g_clear_object (&state->callbacks);
+  g_slice_free (ListRemoteRefsByKind, state);
+}
+
+static void
+list_remote_refs_by_kind_worker (GTask        *task,
+                                 gpointer      source_object,
+                                 gpointer      task_data,
+                                 GCancellable *cancellable)
+{
+  ListRemoteRefsByKind *state = task_data;
+  g_autoptr(GgitRepository) repo = NULL;
+  g_autoptr(GgitRemote) remote = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) repodir = NULL;
+  g_autofree char *tmpdir = NULL;
+  g_auto(RemoteHeadList) refs = NULL;
+  GPtrArray *ar;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (source_object));
+  g_assert (task_data != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  /* Only branches are currently supported, tags are ignored */
+
+  if (!(tmpdir = g_dir_make_tmp (".libgit2-glib-remote-ls-XXXXXX", &error)))
+    goto handle_gerror;
+
+  repodir = g_file_new_for_path (tmpdir);
+
+  if (!(repo = ggit_repository_init_repository (repodir, TRUE, &error)))
+    goto handle_gerror;
+
+  if (!(remote = ggit_remote_new_anonymous (repo, state->uri, &error)))
+    goto handle_gerror;
+
+  ggit_remote_connect (remote,
+                       GGIT_DIRECTION_FETCH,
+                       state->callbacks,
+                       NULL, NULL, &error);
+  if (error != NULL)
+    goto handle_gerror;
+
+  if (!(refs = ggit_remote_list (remote, &error)))
+    goto handle_gerror;
+
+  ar = g_ptr_array_new ();
+  for (guint i = 0; refs[i]; i++)
+    g_ptr_array_add (ar, g_strdup (ggit_remote_head_get_name (refs[i])));
+  g_ptr_array_add (ar, NULL);
+
+  g_task_return_pointer (task,
+                         g_ptr_array_free (ar, FALSE),
+                         (GDestroyNotify) g_strfreev);
+
+  goto cleanup_tmpdir;
+
+handle_gerror:
+  g_task_return_error (task, g_steal_pointer (&error));
+
+cleanup_tmpdir:
+  if (tmpdir != NULL)
+    rm_rf (tmpdir);
+}
+
+static void
+list_remote_refs_cb (GObject      *object,
+                     GAsyncResult *result,
+                     gpointer      user_data)
+{
+  IpcGitServiceImpl *self = (IpcGitServiceImpl *)object;
+  g_autoptr(GDBusMethodInvocation) invocation = user_data;
+  g_autoptr(GError) error = NULL;
+  g_auto(GStrv) refs = NULL;
+
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (self));
+  g_assert (G_IS_TASK (result));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  if (!(refs = g_task_propagate_pointer (G_TASK (result), &error)))
+    complete_wrapped_error (g_steal_pointer (&invocation), error);
+  else
+    ipc_git_service_complete_list_remote_refs_by_kind (IPC_GIT_SERVICE (self),
+                                                       g_steal_pointer (&invocation),
+                                                       (const char * const *)refs);
+}
+
+static gboolean
+ipc_git_service_impl_list_remote_refs_by_kind (IpcGitService         *service,
+                                               GDBusMethodInvocation *invocation,
+                                               const char            *uri,
+                                               guint                  kind)
+{
+  IpcGitServiceImpl *self = (IpcGitServiceImpl *)service;
+  g_autoptr(GTask) task = NULL;
+  ListRemoteRefsByKind *state;
+
+  g_assert (IPC_IS_GIT_SERVICE_IMPL (self));
+  g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation));
+
+  if (kind != IPC_GIT_REF_BRANCH)
+    {
+      g_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.freedesktop.DBus.Error.InvalidArgs",
+                                                  "kind must be a branch, tags are unsupported");
+      return TRUE;
+    }
+
+  state = g_slice_new0 (ListRemoteRefsByKind);
+  state->callbacks = ipc_git_remote_callbacks_new (NULL);
+  state->uri = g_strdup (uri);
+  state->kind = kind;
+
+  task = g_task_new (service, NULL, list_remote_refs_cb, g_steal_pointer (&invocation));
+  g_task_set_task_data (task, state, list_remote_refs_by_kind_free);
+  g_task_run_in_thread (task, list_remote_refs_by_kind_worker);
+
+  return TRUE;
+}
+
 static void
 git_service_iface_init (IpcGitServiceIface *iface)
 {
@@ -328,10 +486,11 @@ git_service_iface_init (IpcGitServiceIface *iface)
   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;
+  iface->handle_list_remote_refs_by_kind = ipc_git_service_impl_list_remote_refs_by_kind;
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (IpcGitServiceImpl, ipc_git_service_impl, IPC_TYPE_GIT_SERVICE_SKELETON,
-                         G_IMPLEMENT_INTERFACE (IPC_TYPE_GIT_SERVICE, git_service_iface_init))
+                               G_IMPLEMENT_INTERFACE (IPC_TYPE_GIT_SERVICE, git_service_iface_init))
 
 static void
 ipc_git_service_impl_finalize (GObject *object)
diff --git a/src/plugins/git/daemon/org.gnome.Builder.Git.Service.xml 
b/src/plugins/git/daemon/org.gnome.Builder.Git.Service.xml
index e14f2b594..b00a4915e 100644
--- a/src/plugins/git/daemon/org.gnome.Builder.Git.Service.xml
+++ b/src/plugins/git/daemon/org.gnome.Builder.Git.Service.xml
@@ -70,5 +70,10 @@
     <method name="LoadConfig">
       <arg name="config_path" direction="out" type="o"/>
     </method>
+    <method name="ListRemoteRefsByKind">
+      <arg name="url" direction="in" type="s"/>
+      <arg name="kind" direction="in" type="u"/>
+      <arg name="refs" direction="out" type="as"/>
+    </method>
   </interface>
 </node>
diff --git a/src/plugins/git/gbp-git-vcs-cloner.c b/src/plugins/git/gbp-git-vcs-cloner.c
index 9b850aaa9..b6dd3b62d 100644
--- a/src/plugins/git/gbp-git-vcs-cloner.c
+++ b/src/plugins/git/gbp-git-vcs-cloner.c
@@ -26,7 +26,9 @@
 #include <libide-threading.h>
 
 #include "daemon/ipc-git-service.h"
+#include "daemon/ipc-git-types.h"
 
+#include "gbp-git-branch.h"
 #include "gbp-git-client.h"
 #include "gbp-git-progress.h"
 #include "gbp-git-vcs-cloner.h"
@@ -49,7 +51,21 @@ typedef struct
 static void vcs_cloner_iface_init (IdeVcsClonerInterface *iface);
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpGitVcsCloner, gbp_git_vcs_cloner, IDE_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_VCS_CLONER, vcs_cloner_iface_init))
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_VCS_CLONER, vcs_cloner_iface_init))
+
+static const char *
+guess_ssh_user_from_host (const char *host)
+{
+  if (host != NULL)
+    {
+      /* TODO: check .ssh/config for User mappings */
+      if (strstr (host, "gitlab.") != NULL ||
+          strstr (host, "github.") != NULL)
+        return "git";
+    }
+
+  return g_get_user_name ();
+}
 
 static void
 clone_request_free (gpointer data)
@@ -287,6 +303,145 @@ gbp_git_vcs_cloner_clone_finish (IdeVcsCloner  *cloner,
   return ide_task_propagate_boolean (IDE_TASK (result), error);
 }
 
+static gboolean
+should_ignore (const char *name)
+{
+  if (name == NULL)
+    return TRUE;
+
+  if (g_str_has_prefix (name, "refs/merge-requests/"))
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+gbp_git_vcs_cloner_list_remote_refs_by_kind_cb (GObject      *object,
+                                                GAsyncResult *result,
+                                                gpointer      user_data)
+{
+  IpcGitService *service = (IpcGitService *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GListStore) store = NULL;
+  g_auto(GStrv) refs = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IPC_IS_GIT_SERVICE (service));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!ipc_git_service_call_list_remote_refs_by_kind_finish (service, &refs, result, &error))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  g_qsort_with_data (refs, g_strv_length (refs), sizeof (char *),
+                     (GCompareDataFunc)g_strcmp0, NULL);
+  store = g_list_store_new (GBP_TYPE_GIT_BRANCH);
+
+  for (guint i = 0; refs[i]; i++)
+    {
+      g_autoptr(GbpGitBranch) branch = NULL;
+
+      if (should_ignore (refs[i]))
+        continue;
+
+      branch = gbp_git_branch_new (refs[i]);
+      g_list_store_append (store, branch);
+    }
+
+  ide_task_return_pointer (task, g_steal_pointer (&store), g_object_unref);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_git_vcs_cloner_list_branches_async (IdeVcsCloner        *cloner,
+                                        IdeVcsUri           *uri,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
+{
+  GbpGitVcsCloner *self = (GbpGitVcsCloner *)cloner;
+  g_autoptr(IpcGitService) service = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree char *uri_str = NULL;
+  GbpGitClient *client;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_GIT_VCS_CLONER (cloner));
+  g_assert (uri != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_git_vcs_cloner_list_branches_async);
+
+  if (!(context = ide_object_get_context (IDE_OBJECT (self))) ||
+      !(client = gbp_git_client_from_context (context)))
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_FAILED,
+                                 "Failed to locate git client object within context");
+      IDE_EXIT;
+    }
+
+  /* We can make this async if/when necessary. It just spawns the process and
+   * sets up a GDBusProxy to the subprocess. Not ideal, but we do it elsewhere
+   * too when accessing the service.
+   */
+  if (!(service = gbp_git_client_get_service (client, cancellable, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  /* Always set a username if the transport is SSH */
+  if (g_strcmp0 ("ssh", ide_vcs_uri_get_scheme (uri)) == 0)
+    {
+      if (ide_vcs_uri_get_user (uri) == NULL)
+        {
+          const char *host = ide_vcs_uri_get_host (uri);
+          ide_vcs_uri_set_user (uri, guess_ssh_user_from_host (host));
+        }
+    }
+
+  uri_str = ide_vcs_uri_to_string (uri);
+
+  ipc_git_service_call_list_remote_refs_by_kind (service,
+                                                 uri_str,
+                                                 IPC_GIT_REF_BRANCH,
+                                                 cancellable,
+                                                 gbp_git_vcs_cloner_list_remote_refs_by_kind_cb,
+                                                 g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+static GListModel *
+gbp_git_vcs_cloner_list_branches_finish (IdeVcsCloner  *cloner,
+                                         GAsyncResult  *result,
+                                         GError       **error)
+{
+  GListModel *ret;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_GIT_VCS_CLONER (cloner));
+  g_assert (IDE_IS_TASK (result));
+
+  ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+  g_assert (!ret || G_IS_LIST_MODEL (ret));
+
+  IDE_RETURN (ret);
+}
+
 static void
 vcs_cloner_iface_init (IdeVcsClonerInterface *iface)
 {
@@ -294,4 +449,6 @@ vcs_cloner_iface_init (IdeVcsClonerInterface *iface)
   iface->validate_uri = gbp_git_vcs_cloner_validate_uri;
   iface->clone_async = gbp_git_vcs_cloner_clone_async;
   iface->clone_finish = gbp_git_vcs_cloner_clone_finish;
+  iface->list_branches_async = gbp_git_vcs_cloner_list_branches_async;
+  iface->list_branches_finish = gbp_git_vcs_cloner_list_branches_finish;
 }


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