[gnome-builder] plugins/git: port to GTK 4



commit 646dfd77b71c2cffa9c116885a6191b20d43356b
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 22:47:46 2022 -0700

    plugins/git: port to GTK 4
    
     - Remove libdazzle usage
     - Prefer "main" for new repositories over "master"
     - Add support for listing branches from uncloned remote
     - Fix typo in xml dbus description
     - Add preferences addin for git config
     - Improve cloner implementation
     - Update .plugin

 .../git/daemon/ipc-git-change-monitor-impl.c       |   2 +-
 src/plugins/git/daemon/ipc-git-remote-callbacks.c  |   2 -
 src/plugins/git/daemon/ipc-git-repository-impl.c   |  23 +-
 src/plugins/git/daemon/ipc-git-service-impl.c      | 161 +++++++++++++-
 .../daemon/org.gnome.Builder.Git.Repository.xml    |   2 +-
 .../git/daemon/org.gnome.Builder.Git.Service.xml   |   5 +
 src/plugins/git/gbp-git-branch.c                   | 105 ++++++---
 src/plugins/git/gbp-git-branch.h                   |   2 +-
 src/plugins/git/gbp-git-buffer-change-monitor.c    |  27 ++-
 src/plugins/git/gbp-git-preferences-addin.c        | 247 +++++++++++++++++++++
 src/plugins/git/gbp-git-preferences-addin.h        |  31 +++
 src/plugins/git/gbp-git-tag.c                      |  44 +++-
 src/plugins/git/gbp-git-vcs-cloner.c               | 245 +++++++++++++++++++-
 src/plugins/git/gbp-git-vcs-config.c               |  42 +++-
 src/plugins/git/gbp-git-vcs.c                      |  13 +-
 src/plugins/git/gbp-git-workbench-addin.c          |   6 +-
 src/plugins/git/git-plugin.c                       |   6 +-
 src/plugins/git/git.plugin                         |   2 +
 src/plugins/git/meson.build                        |   1 +
 19 files changed, 892 insertions(+), 74 deletions(-)
---
diff --git a/src/plugins/git/daemon/ipc-git-change-monitor-impl.c 
b/src/plugins/git/daemon/ipc-git-change-monitor-impl.c
index c9df257d6..de84f41c4 100644
--- a/src/plugins/git/daemon/ipc-git-change-monitor-impl.c
+++ b/src/plugins/git/daemon/ipc-git-change-monitor-impl.c
@@ -298,7 +298,7 @@ git_change_monitor_iface_init (IpcGitChangeMonitorIface *iface)
 }
 
 G_DEFINE_FINAL_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))
+                               G_IMPLEMENT_INTERFACE (IPC_TYPE_GIT_CHANGE_MONITOR, 
git_change_monitor_iface_init))
 
 static void
 ipc_git_change_monitor_impl_finalize (GObject *object)
diff --git a/src/plugins/git/daemon/ipc-git-remote-callbacks.c 
b/src/plugins/git/daemon/ipc-git-remote-callbacks.c
index 1ad6d50cd..28774ff76 100644
--- a/src/plugins/git/daemon/ipc-git-remote-callbacks.c
+++ b/src/plugins/git/daemon/ipc-git-remote-callbacks.c
@@ -227,8 +227,6 @@ ipc_git_remote_callbacks_init (IpcGitRemoteCallbacks *self)
  *
  * 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)
diff --git a/src/plugins/git/daemon/ipc-git-repository-impl.c 
b/src/plugins/git/daemon/ipc-git-repository-impl.c
index 50f5d9c2b..8fe7b60cc 100644
--- a/src/plugins/git/daemon/ipc-git-repository-impl.c
+++ b/src/plugins/git/daemon/ipc-git-repository-impl.c
@@ -64,9 +64,10 @@ static void
 ipc_git_repository_impl_monitor_changed_cb (IpcGitRepositoryImpl *self,
                                             IpcGitIndexMonitor   *monitor)
 {
+  g_autoptr(GgitRef) head_ref = NULL;
+  const char *shortname = NULL;
   GHashTableIter iter;
   gpointer key;
-  g_autoptr(GgitRef) head_ref = NULL;
 
   g_assert (IPC_IS_GIT_REPOSITORY_IMPL (self));
   g_assert (IPC_IS_GIT_INDEX_MONITOR (monitor));
@@ -79,9 +80,16 @@ ipc_git_repository_impl_monitor_changed_cb (IpcGitRepositoryImpl *self,
       ipc_git_change_monitor_impl_reset (change_monitor);
     }
 
-  head_ref = ggit_repository_get_head (self->repository, NULL);
-  g_assert (GGIT_IS_REF (head_ref));
-  ipc_git_repository_set_branch ((IpcGitRepository *)self, ggit_ref_get_shorthand (head_ref));
+  if ((head_ref = ggit_repository_get_head (self->repository, NULL)))
+    {
+      g_assert (GGIT_IS_REF (head_ref));
+      shortname = ggit_ref_get_shorthand (head_ref);
+    }
+
+  if (shortname == NULL)
+    shortname = "main";
+
+  ipc_git_repository_set_branch ((IpcGitRepository *)self, shortname);
   ipc_git_repository_emit_changed (IPC_GIT_REPOSITORY (self));
 }
 
@@ -124,6 +132,7 @@ translate_status (GgitStatusFlags flags)
     case GGIT_STATUS_INDEX_TYPECHANGE:
     case GGIT_STATUS_WORKING_TREE_MODIFIED:
     case GGIT_STATUS_WORKING_TREE_TYPECHANGE:
+    case GGIT_STATUS_CONFLICTED:
       return FILE_STATUS_CHANGED;
 
     case GGIT_STATUS_IGNORED:
@@ -132,6 +141,8 @@ translate_status (GgitStatusFlags flags)
     case GGIT_STATUS_CURRENT:
       return FILE_STATUS_UNCHANGED;
 
+    case GGIT_STATUS_WORKING_TREE_RENAMED:
+    case GGIT_STATUS_WORKING_TREE_UNREADABLE:
     default:
       return FILE_STATUS_UNTRACKED;
     }
@@ -223,7 +234,7 @@ ipc_git_repository_impl_handle_switch_branch (IpcGitRepository      *repository,
     return complete_wrapped_error (invocation, error);
 
   if (!(shortname = ggit_ref_get_shorthand (ref)))
-    shortname = "master";
+    shortname = "main";
 
   workdir = ggit_repository_get_workdir (self->repository);
 
@@ -1172,7 +1183,7 @@ ipc_git_repository_impl_open (GFile   *location,
         branch = g_strdup (ggit_ref_get_shorthand (ref));
 
       if (branch == NULL)
-        branch = g_strdup ("master");
+        branch = g_strdup ("main");
     }
 
   workdir = ggit_repository_get_workdir (repository);
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.Repository.xml 
b/src/plugins/git/daemon/org.gnome.Builder.Git.Repository.xml
index 2d1c4956a..0512c21dd 100644
--- a/src/plugins/git/daemon/org.gnome.Builder.Git.Repository.xml
+++ b/src/plugins/git/daemon/org.gnome.Builder.Git.Repository.xml
@@ -27,7 +27,7 @@
       This signal is emitted when the repository is updated.
       Clients may want to refresh their cached information.
     -->
-    <signal name="Changed"/>>
+    <signal name="Changed"/>
     <!--
       Closed:
 
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-branch.c b/src/plugins/git/gbp-git-branch.c
index 1e62d17aa..bb7358b2d 100644
--- a/src/plugins/git/gbp-git-branch.c
+++ b/src/plugins/git/gbp-git-branch.c
@@ -30,44 +30,84 @@
 struct _GbpGitBranch
 {
   GObject parent_instance;
-  gchar *id;
+  char *id;
 };
 
-static gchar *
-gbp_git_branch_get_id (IdeVcsBranch *branch)
-{
-  return g_strdup (GBP_GIT_BRANCH (branch)->id);
-}
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpGitBranch, gbp_git_branch, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_VCS_BRANCH, NULL))
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_NAME,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
 
-static gchar *
-gbp_git_branch_get_name (IdeVcsBranch *branch)
+static const char *
+gbp_git_branch_get_name (GbpGitBranch *self)
 {
-  const gchar *id = GBP_GIT_BRANCH (branch)->id;
+  const char *id;
 
-  if (id && g_str_has_prefix (id, "refs/heads/"))
+  g_assert (GBP_IS_GIT_BRANCH (self));
+
+  if ((id = self->id) && g_str_has_prefix (id, "refs/heads/"))
     id += strlen ("refs/heads/");
 
-  return g_strdup (id);
+  return id;
 }
 
 static void
-vcs_branch_iface_init (IdeVcsBranchInterface *iface)
+gbp_git_branch_dispose (GObject *object)
 {
-  iface->get_name = gbp_git_branch_get_name;
-  iface->get_id = gbp_git_branch_get_id;
+  GbpGitBranch *self = (GbpGitBranch *)object;
+
+  ide_clear_string (&self->id);
+
+  G_OBJECT_CLASS (gbp_git_branch_parent_class)->dispose (object);
 }
 
-G_DEFINE_FINAL_TYPE_WITH_CODE (GbpGitBranch, gbp_git_branch, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_VCS_BRANCH, vcs_branch_iface_init))
+static void
+gbp_git_branch_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  GbpGitBranch *self = GBP_GIT_BRANCH (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, self->id);
+      break;
+
+    case PROP_NAME:
+      g_value_set_string (value, gbp_git_branch_get_name (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
 
 static void
-gbp_git_branch_finalize (GObject *object)
+gbp_git_branch_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
 {
-  GbpGitBranch *self = (GbpGitBranch *)object;
+  GbpGitBranch *self = GBP_GIT_BRANCH (object);
 
-  g_clear_pointer (&self->id, g_free);
+  switch (prop_id)
+    {
+    case PROP_ID:
+      self->id = g_value_dup_string (value);
+      break;
 
-  G_OBJECT_CLASS (gbp_git_branch_parent_class)->finalize (object);
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
 }
 
 static void
@@ -75,7 +115,19 @@ gbp_git_branch_class_init (GbpGitBranchClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  object_class->finalize = gbp_git_branch_finalize;
+  object_class->dispose = gbp_git_branch_dispose;
+  object_class->get_property = gbp_git_branch_get_property;
+  object_class->set_property = gbp_git_branch_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id", NULL, NULL, NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name", NULL, NULL, NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
 static void
@@ -84,12 +136,9 @@ gbp_git_branch_init (GbpGitBranch *self)
 }
 
 GbpGitBranch *
-gbp_git_branch_new (const gchar *id)
+gbp_git_branch_new (const char *id)
 {
-  GbpGitBranch *self;
-
-  self = g_object_new (GBP_TYPE_GIT_BRANCH, NULL);
-  self->id = g_strdup (id);
-
-  return g_steal_pointer (&self);
+  return g_object_new (GBP_TYPE_GIT_BRANCH,
+                       "id", id,
+                       NULL);
 }
diff --git a/src/plugins/git/gbp-git-branch.h b/src/plugins/git/gbp-git-branch.h
index b7518d611..7f6ce2a36 100644
--- a/src/plugins/git/gbp-git-branch.h
+++ b/src/plugins/git/gbp-git-branch.h
@@ -28,6 +28,6 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (GbpGitBranch, gbp_git_branch, GBP, GIT_BRANCH, GObject)
 
-GbpGitBranch *gbp_git_branch_new (const gchar *id);
+GbpGitBranch *gbp_git_branch_new (const char *id);
 
 G_END_DECLS
diff --git a/src/plugins/git/gbp-git-buffer-change-monitor.c b/src/plugins/git/gbp-git-buffer-change-monitor.c
index b606d121f..e12606e03 100644
--- a/src/plugins/git/gbp-git-buffer-change-monitor.c
+++ b/src/plugins/git/gbp-git-buffer-change-monitor.c
@@ -22,7 +22,6 @@
 
 #include "config.h"
 
-#include <dazzle.h>
 #include <glib/gi18n.h>
 #include <string.h>
 
@@ -35,7 +34,7 @@ struct _GbpGitBufferChangeMonitor
 {
   IdeBufferChangeMonitor  parent;
   IpcGitChangeMonitor    *proxy;
-  DzlSignalGroup         *buffer_signals;
+  IdeSignalGroup         *buffer_signals;
   LineCache              *cache;
   guint                   last_change_count;
   guint                   queued_source;
@@ -80,11 +79,11 @@ gbp_git_buffer_change_monitor_queue_update (GbpGitBufferChangeMonitor *self,
   g_clear_handle_id (&self->queued_source, g_source_remove);
 
   self->queued_source =
-    gdk_threads_add_timeout_full (G_PRIORITY_HIGH,
-                                  delay,
-                                  (GSourceFunc) queued_update_source_cb,
-                                  g_object_ref (self),
-                                  g_object_unref);
+    g_timeout_add_full (G_PRIORITY_HIGH,
+                        delay,
+                        (GSourceFunc) queued_update_source_cb,
+                        g_object_ref (self),
+                        g_object_unref);
 }
 
 static void
@@ -94,7 +93,7 @@ gbp_git_buffer_change_monitor_destroy (IdeObject *object)
 
   if (self->buffer_signals)
     {
-      dzl_signal_group_set_target (self->buffer_signals, NULL);
+      ide_signal_group_set_target (self->buffer_signals, NULL);
       g_clear_object (&self->buffer_signals);
     }
 
@@ -120,7 +119,7 @@ gbp_git_buffer_change_monitor_load (IdeBufferChangeMonitor *monitor,
   g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
   g_assert (IDE_IS_BUFFER (buffer));
 
-  dzl_signal_group_set_target (self->buffer_signals, buffer);
+  ide_signal_group_set_target (self->buffer_signals, buffer);
   gbp_git_buffer_change_monitor_queue_update (self, FAST);
 }
 
@@ -348,24 +347,24 @@ gbp_git_buffer_change_monitor_class_init (GbpGitBufferChangeMonitorClass *klass)
 static void
 gbp_git_buffer_change_monitor_init (GbpGitBufferChangeMonitor *self)
 {
-  self->buffer_signals = dzl_signal_group_new (IDE_TYPE_BUFFER);
+  self->buffer_signals = ide_signal_group_new (IDE_TYPE_BUFFER);
 
-  dzl_signal_group_connect_object (self->buffer_signals,
+  ide_signal_group_connect_object (self->buffer_signals,
                                    "insert-text",
                                    G_CALLBACK (buffer_insert_text_after_cb),
                                    self,
                                    G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-  dzl_signal_group_connect_object (self->buffer_signals,
+  ide_signal_group_connect_object (self->buffer_signals,
                                    "delete-range",
                                    G_CALLBACK (buffer_delete_range_cb),
                                    self,
                                    G_CONNECT_SWAPPED);
-  dzl_signal_group_connect_object (self->buffer_signals,
+  ide_signal_group_connect_object (self->buffer_signals,
                                    "delete-range",
                                    G_CALLBACK (buffer_delete_range_after_cb),
                                    self,
                                    G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-  dzl_signal_group_connect_object (self->buffer_signals,
+  ide_signal_group_connect_object (self->buffer_signals,
                                    "changed",
                                    G_CALLBACK (buffer_changed_after_cb),
                                    self,
diff --git a/src/plugins/git/gbp-git-preferences-addin.c b/src/plugins/git/gbp-git-preferences-addin.c
new file mode 100644
index 000000000..f8f23c640
--- /dev/null
+++ b/src/plugins/git/gbp-git-preferences-addin.c
@@ -0,0 +1,247 @@
+/* gbp-git-preferences-addin.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-git-preferences-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-gui.h>
+
+#include "gbp-git-preferences-addin.h"
+#include "gbp-git-vcs.h"
+#include "gbp-git-vcs-config.h"
+
+struct _GbpGitPreferencesAddin
+{
+  GObject parent_instance;
+};
+
+typedef struct
+{
+  IdeVcsConfig *config;
+  IdeVcsConfigType key;
+} ConfigKeyState;
+
+static void
+config_key_state_free (gpointer data)
+{
+  ConfigKeyState *state = data;
+
+  ide_clear_and_destroy_object (&state->config);
+  g_slice_free (ConfigKeyState, state);
+}
+
+static void
+entry_config_changed (GtkEditable    *editable,
+                      ConfigKeyState *state)
+{
+  g_auto(GValue) value = G_VALUE_INIT;
+  const char *text;
+
+  g_assert (GTK_IS_EDITABLE (editable));
+  g_assert (state != NULL);
+  g_assert (IDE_IS_VCS_CONFIG (state->config));
+
+  text = gtk_editable_get_text (editable);
+
+  g_value_init (&value, G_TYPE_STRING);
+  g_value_set_string (&value, text);
+
+  ide_vcs_config_set_config (state->config, state->key, &value);
+}
+
+static GtkWidget *
+create_entry (IdeVcsConfig     *config,
+              const char       *title,
+              IdeVcsConfigType  type)
+{
+  g_auto(GValue) value = G_VALUE_INIT;
+  AdwEntryRow *entry;
+  const char *text = NULL;
+  ConfigKeyState *state;
+
+  g_assert (GBP_IS_GIT_VCS_CONFIG (config));
+
+  g_value_init (&value, G_TYPE_STRING);
+  ide_vcs_config_get_config (config, type, &value);
+  text = g_value_get_string (&value);
+
+  entry = g_object_new (ADW_TYPE_ENTRY_ROW,
+                        "title", title,
+                        "text", text,
+                        NULL);
+
+  state = g_slice_new (ConfigKeyState);
+  state->config = g_object_ref (config);
+  state->key = type;
+
+  g_signal_connect_data (entry,
+                         "changed",
+                         G_CALLBACK (entry_config_changed),
+                         state,
+                         (GClosureNotify)config_key_state_free,
+                         G_CONNECT_AFTER);
+
+  return GTK_WIDGET (entry);
+}
+
+static void
+create_entry_row (const char                   *page_name,
+                  const IdePreferenceItemEntry *entry,
+                  AdwPreferencesGroup          *group,
+                  gpointer                      user_data)
+{
+  g_autoptr(IdeVcsConfig) config = NULL;
+  IdePreferencesWindow *window = user_data;
+  IdePreferencesMode mode;
+  IdeContext *context;
+
+  g_assert (IDE_IS_PREFERENCES_WINDOW (window));
+
+  /* We should always have a context, even if we're showing
+   * global preferences.
+   */
+  mode = ide_preferences_window_get_mode (window);
+  context = ide_preferences_window_get_context (window);
+
+  g_assert (IDE_IS_CONTEXT (context));
+
+  if (mode == IDE_PREFERENCES_MODE_PROJECT)
+    {
+      IdeVcs *vcs = ide_vcs_from_context (context);
+
+      if (!GBP_IS_GIT_VCS (vcs))
+        return;
+
+      if (!(config = ide_vcs_get_config (vcs)))
+        return;
+
+      gbp_git_vcs_config_set_global (GBP_GIT_VCS_CONFIG (config), FALSE);
+    }
+  else
+    {
+      config = g_object_new (GBP_TYPE_GIT_VCS_CONFIG,
+                             "parent", context,
+                             NULL);
+    }
+
+  g_assert (GBP_IS_GIT_VCS_CONFIG (config));
+
+  if (g_strcmp0 (entry->name, "name") == 0)
+    {
+      adw_preferences_group_add (group, create_entry (config, _("Author"), IDE_VCS_CONFIG_FULL_NAME));
+      return;
+    }
+
+  if (g_strcmp0 (entry->name, "email") == 0)
+    {
+      const char *title;
+      GtkWidget *label;
+
+      adw_preferences_group_add (group, create_entry (config, _("Email"), IDE_VCS_CONFIG_EMAIL));
+
+      /* After the email row, we want to add a blurb about whether this
+       * will affect the global or per-project settings.
+       */
+      if (mode == IDE_PREFERENCES_MODE_PROJECT)
+        title = _("The Git configuration options above effect current project only.");
+      else
+        title = _("The Git configuration options above effect global defaults.");
+
+      label = g_object_new (GTK_TYPE_LABEL,
+                            "css-classes", IDE_STRV_INIT ("caption", "dim-label"),
+                            "xalign", .0f,
+                            "margin-top", 15,
+                            "single-line-mode", TRUE,
+                            "label", title,
+                            NULL);
+      adw_preferences_group_add (group, label);
+
+      return;
+    }
+}
+
+static const IdePreferencePageEntry pages[] = {
+  { NULL, "projects", "git", "builder-vcs-git-symbolic", 210, N_("Version Control") },
+};
+
+static const IdePreferenceGroupEntry groups[] = {
+  { "git", "author", 0, N_("Authorship") },
+};
+
+static const IdePreferenceItemEntry items[] = {
+  { "git", "author", "name", 0, create_entry_row, N_("Full Name") },
+  { "git", "author", "email", 10, create_entry_row, N_("Email Address") },
+};
+
+static void
+gbp_git_preferences_addin_load (IdePreferencesAddin  *addin,
+                                IdePreferencesWindow *window,
+                                IdeContext           *context)
+{
+  g_assert (GBP_IS_GIT_PREFERENCES_ADDIN (addin));
+  g_assert (IDE_IS_PREFERENCES_WINDOW (window));
+  g_assert (!context || IDE_IS_CONTEXT (context));
+
+  /* We can only show Git information if we have a project open as we need
+   * access to a gnome-builder-git daemon. If no context is available, then
+   * that means we got here by showing preferences with --preferences or
+   * soemthing like that. In that (unlikely) case, we have to bail.
+   */
+  if (context == NULL)
+    return;
+
+  ide_preferences_window_add_pages (window, pages, G_N_ELEMENTS (pages), NULL);
+  ide_preferences_window_add_groups (window, groups, G_N_ELEMENTS (groups), NULL);
+  ide_preferences_window_add_items (window, items, G_N_ELEMENTS (items), window, NULL);
+}
+
+static void
+gbp_git_preferences_addin_unload (IdePreferencesAddin  *addin,
+                                  IdePreferencesWindow *window,
+                                  IdeContext           *context)
+{
+  g_assert (GBP_IS_GIT_PREFERENCES_ADDIN (addin));
+  g_assert (IDE_IS_PREFERENCES_WINDOW (window));
+  g_assert (!context || IDE_IS_CONTEXT (context));
+
+}
+
+static void
+preferences_addin_iface_init (IdePreferencesAddinInterface *iface)
+{
+  iface->load = gbp_git_preferences_addin_load;
+  iface->unload = gbp_git_preferences_addin_unload;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpGitPreferencesAddin, gbp_git_preferences_addin, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_PREFERENCES_ADDIN, 
preferences_addin_iface_init))
+
+static void
+gbp_git_preferences_addin_class_init (GbpGitPreferencesAddinClass *klass)
+{
+}
+
+static void
+gbp_git_preferences_addin_init (GbpGitPreferencesAddin *self)
+{
+}
diff --git a/src/plugins/git/gbp-git-preferences-addin.h b/src/plugins/git/gbp-git-preferences-addin.h
new file mode 100644
index 000000000..1d30f4aee
--- /dev/null
+++ b/src/plugins/git/gbp-git-preferences-addin.h
@@ -0,0 +1,31 @@
+/* gbp-git-preferences-addin.h
+ *
+ * Copyright 2022 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
+
+#define GBP_TYPE_GIT_PREFERENCES_ADDIN (gbp_git_preferences_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGitPreferencesAddin, gbp_git_preferences_addin, GBP, GIT_PREFERENCES_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/git/gbp-git-tag.c b/src/plugins/git/gbp-git-tag.c
index 9746fefa2..1ca85fa54 100644
--- a/src/plugins/git/gbp-git-tag.c
+++ b/src/plugins/git/gbp-git-tag.c
@@ -29,11 +29,11 @@
 struct _GbpGitTag
 {
   GObject parent_instance;
-  gchar *name;
+  char *name;
 };
 
-static gchar *
-gbp_git_tag_get_name (IdeVcsTag *tag)
+static char *
+gbp_git_tag_dup_name (IdeVcsTag *tag)
 {
   return g_strdup (GBP_GIT_TAG (tag)->name);
 }
@@ -41,11 +41,19 @@ gbp_git_tag_get_name (IdeVcsTag *tag)
 static void
 vcs_tag_iface_init (IdeVcsTagInterface *iface)
 {
-  iface->get_name = gbp_git_tag_get_name;
+  iface->dup_name = gbp_git_tag_dup_name;
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpGitTag, gbp_git_tag, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_VCS_TAG, vcs_tag_iface_init))
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_VCS_TAG, vcs_tag_iface_init))
+
+enum {
+  PROP_0,
+  PROP_NAME,
+  N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS];
 
 static void
 gbp_git_tag_finalize (GObject *object)
@@ -57,12 +65,38 @@ gbp_git_tag_finalize (GObject *object)
   G_OBJECT_CLASS (gbp_git_tag_parent_class)->finalize (object);
 }
 
+static void
+gbp_git_tag_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  GbpGitTag *self = GBP_GIT_TAG (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, self->name);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
 static void
 gbp_git_tag_class_init (GbpGitTagClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   object_class->finalize = gbp_git_tag_finalize;
+  object_class->get_property = gbp_git_tag_get_property;
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name", NULL, NULL, NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
 static void
diff --git a/src/plugins/git/gbp-git-vcs-cloner.c b/src/plugins/git/gbp-git-vcs-cloner.c
index 9b850aaa9..4ff950773 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,230 @@ 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/") ||
+      g_str_has_prefix (name, "refs/tags/"))
+    return TRUE;
+
+  return FALSE;
+}
+
+enum {
+  CLASS_HEAD,
+  CLASS_MAIN,
+  CLASS_MASTER,
+  CLASS_FEATURE,
+  CLASS_GNOME,
+  CLASS_OTHER,
+  CLASS_WIP,
+};
+
+static int
+classify_branch_name (const char *branch)
+{
+  switch (branch[0])
+    {
+    case 'H':
+      if (strcmp (branch, "HEAD") == 0)
+        return CLASS_HEAD;
+      break;
+
+    case 'm':
+      if (strcmp (branch, "main") == 0)
+        return CLASS_MAIN;
+      else if (strcmp (branch, "master") == 0)
+        return CLASS_MASTER;
+      break;
+
+    case 'w':
+      if (g_str_has_prefix (branch, "wip/"))
+        return CLASS_WIP;
+      break;
+
+    case 'f':
+      if (g_str_has_prefix (branch, "feature/"))
+        return CLASS_FEATURE;
+      break;
+
+    case 'g':
+      if (g_str_has_prefix (branch, "gnome-"))
+        return CLASS_GNOME;
+      break;
+
+    default:
+      break;
+    }
+
+  return CLASS_OTHER;
+}
+
+static inline const char *
+get_name (const char *ref)
+{
+  if (ref[0] == 'r' && g_str_has_prefix (ref, "refs/heads/"))
+    return ref + strlen ("refs/heads/");
+  return ref;
+}
+
+static int
+strptrcmp (const char * const *ptra,
+           const char * const *ptrb)
+{
+  const char *name_a = get_name (*ptra);
+  const char *name_b = get_name (*ptrb);
+  int class_a = classify_branch_name (name_a);
+  int class_b = classify_branch_name (name_b);
+
+  if (class_a < class_b)
+    return -1;
+  else if (class_a > class_b)
+    return 1;
+  else
+    return strcmp (name_a, name_b);
+}
+
+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)strptrcmp, 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 char *
+gbp_git_vcs_cloner_get_directory_name (IdeVcsCloner *cloner,
+                                       IdeVcsUri    *uri)
+{
+  g_assert (GBP_IS_GIT_VCS_CLONER (cloner));
+  g_assert (uri != NULL);
+
+  return ide_vcs_uri_get_clone_name (uri);
+}
+
 static void
 vcs_cloner_iface_init (IdeVcsClonerInterface *iface)
 {
@@ -294,4 +534,7 @@ 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;
+  iface->get_directory_name = gbp_git_vcs_cloner_get_directory_name;
 }
diff --git a/src/plugins/git/gbp-git-vcs-config.c b/src/plugins/git/gbp-git-vcs-config.c
index f110696d5..859d325a2 100644
--- a/src/plugins/git/gbp-git-vcs-config.c
+++ b/src/plugins/git/gbp-git-vcs-config.c
@@ -29,6 +29,7 @@
 #include "daemon/ipc-git-config.h"
 
 #include "gbp-git-client.h"
+#include "gbp-git-vcs.h"
 #include "gbp-git-vcs-config.h"
 
 struct _GbpGitVcsConfig
@@ -73,18 +74,46 @@ get_config (GbpGitVcsConfig  *self,
   context = ide_object_get_context (IDE_OBJECT (self));
   client = gbp_git_client_from_context (context);
 
-  if (!self->is_global)
-    {
-      /* TODO: get config from repository */
-    }
+  g_assert (IDE_IS_CONTEXT (context));
+  g_assert (GBP_IS_GIT_CLIENT (client));
 
   if (!(service = gbp_git_client_get_service (client, cancellable, error)))
     return NULL;
 
-  if (!ipc_git_service_call_load_config_sync (service, &obj_path, cancellable, error))
-    return NULL;
+  if (!self->is_global)
+    {
+      IdeVcs *vcs;
+      IpcGitRepository *repository;
+
+      vcs = ide_vcs_from_context (context);
+      g_assert (GBP_IS_GIT_VCS (vcs));
+
+      repository = gbp_git_vcs_get_repository (GBP_GIT_VCS (vcs));
+      g_assert (!repository || IPC_IS_GIT_REPOSITORY (repository));
+
+      if (repository == NULL)
+        {
+          g_set_error (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_NOT_SUPPORTED,
+                       "Failed to load git repository");
+          return NULL;
+        }
+
+      if (!ipc_git_repository_call_load_config_sync (repository, &obj_path, cancellable, error))
+        return NULL;
+    }
+  else
+    {
+      if (!ipc_git_service_call_load_config_sync (service, &obj_path, cancellable, error))
+        return NULL;
+    }
+
+  g_assert (obj_path != NULL);
+  g_assert (g_variant_is_object_path (obj_path));
 
   connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (service));
+  g_assert (G_IS_DBUS_CONNECTION (connection));
 
   return ipc_git_config_proxy_new_sync (connection,
                                         G_DBUS_PROXY_FLAGS_NONE,
@@ -92,7 +121,6 @@ get_config (GbpGitVcsConfig  *self,
                                         obj_path,
                                         cancellable,
                                         error);
-
 }
 
 static void
diff --git a/src/plugins/git/gbp-git-vcs.c b/src/plugins/git/gbp-git-vcs.c
index 36d89733d..81d0162e2 100644
--- a/src/plugins/git/gbp-git-vcs.c
+++ b/src/plugins/git/gbp-git-vcs.c
@@ -160,7 +160,7 @@ gbp_git_vcs_switch_branch_async (IdeVcs              *vcs,
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, gbp_git_vcs_switch_branch_async);
 
-  branch_id = ide_vcs_branch_get_id (branch);
+  branch_id = ide_vcs_branch_dup_id (branch);
 
   ipc_git_repository_call_switch_branch (self->repository,
                                          branch_id,
@@ -233,8 +233,8 @@ gbp_git_vcs_push_branch_async (IdeVcs              *vcs,
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, gbp_git_vcs_push_branch_async);
 
-  branch_id = ide_vcs_branch_get_id (branch);
-  name = ide_vcs_branch_get_name (branch);
+  branch_id = ide_vcs_branch_dup_id (branch);
+  name = ide_vcs_branch_dup_name (branch);
 
   ref_specs = g_ptr_array_new_with_free_func (g_free);
   g_ptr_array_add (ref_specs, g_strdup_printf ("%s:%s", branch_id, branch_id));
@@ -530,9 +530,16 @@ gbp_git_vcs_list_status_finish (IdeVcs        *vcs,
   return ide_task_propagate_object (IDE_TASK (result), error);
 }
 
+static char *
+gbp_git_vcs_get_display_name (IdeVcs *vcs)
+{
+  return g_strdup (_("Git"));
+}
+
 static void
 vcs_iface_init (IdeVcsInterface *iface)
 {
+  iface->get_display_name = gbp_git_vcs_get_display_name;
   iface->get_workdir = gbp_git_vcs_get_workdir;
   iface->is_ignored = gbp_git_vcs_is_ignored;
   iface->get_config = gbp_git_vcs_get_config;
diff --git a/src/plugins/git/gbp-git-workbench-addin.c b/src/plugins/git/gbp-git-workbench-addin.c
index 0dbca9731..a64177895 100644
--- a/src/plugins/git/gbp-git-workbench-addin.c
+++ b/src/plugins/git/gbp-git-workbench-addin.c
@@ -112,8 +112,8 @@ gbp_git_workbench_addin_load_project_discover_cb (GObject      *object,
 
   if (!ipc_git_service_call_discover_finish (service, &git_location, result, &error))
     {
-      g_dbus_error_strip_remote_error (error);
-      ide_task_return_error (task, g_steal_pointer (&error));
+      g_debug ("Not a git repository: %s", error->message);
+      ide_task_return_unsupported_error (task);
       return;
     }
 
@@ -239,7 +239,7 @@ workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpGitWorkbenchAddin, gbp_git_workbench_addin, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
 
 static void
 gbp_git_workbench_addin_class_init (GbpGitWorkbenchAddinClass *klass)
diff --git a/src/plugins/git/git-plugin.c b/src/plugins/git/git-plugin.c
index c77425f12..d0d5fec05 100644
--- a/src/plugins/git/git-plugin.c
+++ b/src/plugins/git/git-plugin.c
@@ -23,13 +23,14 @@
 #include "config.h"
 
 #include <libpeas/peas.h>
-#include <libide-editor.h>
+#include <libide-gui.h>
 #include <libide-foundry.h>
 #include <libide-vcs.h>
 
 #include "gbp-git-buffer-addin.h"
 #include "gbp-git-dependency-updater.h"
 #include "gbp-git-pipeline-addin.h"
+#include "gbp-git-preferences-addin.h"
 #include "gbp-git-vcs-cloner.h"
 #include "gbp-git-vcs-config.h"
 #include "gbp-git-vcs-initializer.h"
@@ -61,4 +62,7 @@ _gbp_git_register_types (PeasObjectModule *module)
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_WORKBENCH_ADDIN,
                                               GBP_TYPE_GIT_WORKBENCH_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_PREFERENCES_ADDIN,
+                                              GBP_TYPE_GIT_PREFERENCES_ADDIN);
 }
diff --git a/src/plugins/git/git.plugin b/src/plugins/git/git.plugin
index 8592d5293..7565e9dd5 100644
--- a/src/plugins/git/git.plugin
+++ b/src/plugins/git/git.plugin
@@ -6,3 +6,5 @@ Description=Support for the Git version control system
 Embedded=_gbp_git_register_types
 Module=git
 Name=Git
+X-Category=vcs
+X-Preferences-Kind=application;project;
diff --git a/src/plugins/git/meson.build b/src/plugins/git/meson.build
index d08ba06c5..ad9ce6450 100644
--- a/src/plugins/git/meson.build
+++ b/src/plugins/git/meson.build
@@ -10,6 +10,7 @@ plugins_sources += files([
   'gbp-git-client.c',
   'gbp-git-dependency-updater.c',
   'gbp-git-pipeline-addin.c',
+  'gbp-git-preferences-addin.c',
   'gbp-git-progress.c',
   'gbp-git-submodule-stage.c',
   'gbp-git-tag.c',


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