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



commit f9ea46184667753a3752ade77bba8c3de1a8d108
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 23:28:40 2022 -0700

    plugins/vcsui: port to GTK 4
    
     - Remove libdazzle usage
     - Add new clone page to greeter
     - Add --greeter command line option w/ application addin
     - Wire up new GSV variable API
     - Add switcher popover (but dont actually switch yet, still more work
       to be done before release).
     - Add workspace addin to add switcher
     - Update .plugin

 src/plugins/vcsui/gbp-vcsui-application-addin.c | 117 +++++++
 src/plugins/vcsui/gbp-vcsui-application-addin.h |  31 ++
 src/plugins/vcsui/gbp-vcsui-clone-page.c        | 444 ++++++++++++++++++++++++
 src/plugins/vcsui/gbp-vcsui-clone-page.h        |  36 ++
 src/plugins/vcsui/gbp-vcsui-clone-page.ui       | 303 ++++++++++++++++
 src/plugins/vcsui/gbp-vcsui-editor-page-addin.c |  22 +-
 src/plugins/vcsui/gbp-vcsui-switcher-popover.c  | 244 +++++++++++++
 src/plugins/vcsui/gbp-vcsui-switcher-popover.h  |  38 ++
 src/plugins/vcsui/gbp-vcsui-switcher-popover.ui | 151 ++++++++
 src/plugins/vcsui/gbp-vcsui-tree-addin.c        |  22 +-
 src/plugins/vcsui/gbp-vcsui-workbench-addin.c   |  16 +-
 src/plugins/vcsui/gbp-vcsui-workspace-addin.c   | 194 +++++++++++
 src/plugins/vcsui/gbp-vcsui-workspace-addin.h   |  31 ++
 src/plugins/vcsui/meson.build                   |   6 +-
 src/plugins/vcsui/vcsui-plugin.c                |   9 +
 src/plugins/vcsui/vcsui.gresource.xml           |   2 +
 src/plugins/vcsui/vcsui.plugin                  |   7 +-
 17 files changed, 1642 insertions(+), 31 deletions(-)
---
diff --git a/src/plugins/vcsui/gbp-vcsui-application-addin.c b/src/plugins/vcsui/gbp-vcsui-application-addin.c
new file mode 100644
index 000000000..a874573b8
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-application-addin.c
@@ -0,0 +1,117 @@
+/* gbp-vcsui-application-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-vcsui-application-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-greeter.h>
+
+#include "gbp-vcsui-application-addin.h"
+#include "gbp-vcsui-clone-page.h"
+
+struct _GbpVcsuiApplicationAddin
+{
+  GObject parent_instance;
+};
+
+static void
+gbp_vcsui_application_addin_handle_command_line (IdeApplicationAddin     *addin,
+                                                 IdeApplication          *app,
+                                                 GApplicationCommandLine *cmdline)
+{
+  g_auto(GStrv) argv = NULL;
+  GVariantDict *dict;
+  const char *clone_uri = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_VCSUI_APPLICATION_ADDIN (addin));
+  g_assert (IDE_IS_APPLICATION (app));
+  g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+  dict = g_application_command_line_get_options_dict (cmdline);
+  argv = ide_application_get_argv (app, cmdline);
+
+  /*
+   * If the --clone=URI option was provided, switch the greeter to the
+   * clone page and begin cloning.
+   */
+  if (dict != NULL && g_variant_dict_lookup (dict, "clone", "&s", &clone_uri))
+    {
+      IdeGreeterWorkspace *workspace;
+      IdeWorkbench *workbench;
+      GtkWidget *page;
+
+      workbench = ide_workbench_new ();
+      ide_application_add_workbench (app, workbench);
+
+      workspace = ide_greeter_workspace_new (app);
+      ide_workbench_add_workspace (workbench, IDE_WORKSPACE (workspace));
+
+      ide_greeter_workspace_set_page_name (workspace, "clone");
+      page = ide_greeter_workspace_get_page_named (workspace, "clone");
+
+      if (GBP_IS_VCSUI_CLONE_PAGE (page))
+        gbp_vcsui_clone_page_set_uri (GBP_VCSUI_CLONE_PAGE (page), clone_uri);
+
+      ide_workbench_focus_workspace (workbench, IDE_WORKSPACE (workspace));
+    }
+
+  IDE_EXIT;
+}
+
+static void
+gbp_vcsui_application_addin_add_option_entries (IdeApplicationAddin *addin,
+                                                IdeApplication      *app)
+{
+  g_assert (GBP_IS_VCSUI_APPLICATION_ADDIN (addin));
+  g_assert (IDE_IS_APPLICATION (app));
+
+  g_application_add_main_option (G_APPLICATION (app),
+                                 "clone",
+                                 0,
+                                 G_OPTION_FLAG_IN_MAIN,
+                                 G_OPTION_ARG_STRING,
+                                 _("Begin cloning project from URI"),
+                                 "URI");
+}
+
+static void
+application_addin_iface_init (IdeApplicationAddinInterface *iface)
+{
+  iface->add_option_entries = gbp_vcsui_application_addin_add_option_entries;
+  iface->handle_command_line = gbp_vcsui_application_addin_handle_command_line;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpVcsuiApplicationAddin, gbp_vcsui_application_addin, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_APPLICATION_ADDIN, 
application_addin_iface_init))
+
+static void
+gbp_vcsui_application_addin_class_init (GbpVcsuiApplicationAddinClass *klass)
+{
+}
+
+static void
+gbp_vcsui_application_addin_init (GbpVcsuiApplicationAddin *self)
+{
+}
diff --git a/src/plugins/vcsui/gbp-vcsui-application-addin.h b/src/plugins/vcsui/gbp-vcsui-application-addin.h
new file mode 100644
index 000000000..e41f67cd8
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-application-addin.h
@@ -0,0 +1,31 @@
+/* gbp-vcsui-application-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_VCSUI_APPLICATION_ADDIN (gbp_vcsui_application_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpVcsuiApplicationAddin, gbp_vcsui_application_addin, GBP, VCSUI_APPLICATION_ADDIN, 
GObject)
+
+G_END_DECLS
diff --git a/src/plugins/vcsui/gbp-vcsui-clone-page.c b/src/plugins/vcsui/gbp-vcsui-clone-page.c
new file mode 100644
index 000000000..5f3c3a647
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-clone-page.c
@@ -0,0 +1,444 @@
+/* gbp-vcsui-clone-page.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-vcsui-clone-page"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <adwaita.h>
+#include <vte/vte.h>
+
+#include <libide-greeter.h>
+#include <libide-gtk.h>
+#include <libide-gui.h>
+#include <libide-projects.h>
+
+#include "gbp-vcsui-clone-page.h"
+
+struct _GbpVcsuiClonePage
+{
+  GtkWidget           parent_instance;
+
+  AdwEntryRow        *author_email_row;
+  AdwEntryRow        *author_name_row;
+  GtkMenuButton      *branch_button;
+  GtkLabel           *branch_label;
+  AdwEntryRow        *location_row;
+  GtkWidget          *main;
+  GtkStack           *stack;
+  VteTerminal        *terminal;
+  AdwEntryRow        *uri_row;
+  IdeProgressIcon    *progress;
+  GtkLabel           *failure_message;
+
+  IdeVcsCloneRequest *request;
+};
+
+G_DEFINE_FINAL_TYPE (GbpVcsuiClonePage, gbp_vcsui_clone_page, GTK_TYPE_WIDGET)
+
+static void
+location_row_changed_cb (GbpVcsuiClonePage *self,
+                         GtkEditable       *editable)
+{
+  g_autofree char *expanded = NULL;
+  g_autoptr(GFile) directory = NULL;
+  const char *text;
+
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+  g_assert (GTK_IS_EDITABLE (editable));
+
+  text = gtk_editable_get_text (editable);
+  expanded = ide_path_expand (text);
+  directory = g_file_new_for_path (expanded);
+
+  ide_vcs_clone_request_set_directory (self->request, directory);
+}
+
+static void
+select_folder_response_cb (GbpVcsuiClonePage    *self,
+                           int                   response_id,
+                           GtkFileChooserNative *native)
+{
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+  g_assert (GTK_IS_FILE_CHOOSER_NATIVE (native));
+
+  if (response_id == GTK_RESPONSE_ACCEPT)
+    {
+      g_autoptr(GFile) file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (native));
+      g_autofree char *path = ide_path_collapse (g_file_peek_path (file));
+
+      gtk_editable_set_text (GTK_EDITABLE (self->location_row), path);
+    }
+
+  gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (native));
+}
+
+static void
+select_folder_action (GtkWidget  *widget,
+                      const char *action_name,
+                      GVariant   *param)
+{
+  GbpVcsuiClonePage *self = (GbpVcsuiClonePage *)widget;
+  GtkFileChooserNative *native;
+  GtkRoot *root;
+
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+
+  root = gtk_widget_get_root (widget);
+  native = gtk_file_chooser_native_new (_("Select Location"),
+                                        GTK_WINDOW (root),
+                                        GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+                                        _("Select"),
+                                        _("Cancel"));
+  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (native),
+                                       ide_vcs_clone_request_get_directory (self->request),
+                                       NULL);
+  g_signal_connect_object (native,
+                           "response",
+                           G_CALLBACK (select_folder_response_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  gtk_native_dialog_show (GTK_NATIVE_DIALOG (native));
+}
+
+static void
+gbp_vcsui_clone_page_clone_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+  IdeVcsCloneRequest *request = (IdeVcsCloneRequest *)object;
+  g_autoptr(GbpVcsuiClonePage) self = user_data;
+  IdeGreeterWorkspace *greeter;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) directory = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_VCS_CLONE_REQUEST (request));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+
+  greeter = IDE_GREETER_WORKSPACE (ide_widget_get_workspace (GTK_WIDGET (self)));
+
+  gtk_widget_hide (GTK_WIDGET (self->progress));
+
+  if (!(directory = ide_vcs_clone_request_clone_finish (request, result, &error)))
+    {
+      g_message ("Failed to clone repository: %s", error->message);
+      gtk_stack_set_visible_child_name (self->stack, "details");
+      gtk_label_set_label (self->failure_message,
+                           _("A failure occurred while cloning the repository."));
+      IDE_GOTO (failure);
+    }
+  else
+    {
+      g_autoptr(IdeProjectInfo) project_info = NULL;
+
+      g_debug ("Clone request complete\n");
+
+      project_info = ide_project_info_new ();
+      ide_project_info_set_file (project_info, directory);
+      ide_project_info_set_directory (project_info, directory);
+
+      ide_greeter_workspace_open_project (greeter, project_info);
+    }
+
+failure:
+  ide_greeter_workspace_end (greeter);
+
+  IDE_EXIT;
+}
+
+static void
+notify_progress_cb (IdeNotification *notif,
+                    GParamSpec      *pspec,
+                    IdeProgressIcon *icon)
+{
+  IdeAnimation *anim;
+  double progress;
+
+  g_assert (IDE_IS_NOTIFICATION (notif));
+  g_assert (IDE_IS_PROGRESS_ICON (icon));
+
+  anim = g_object_get_data (G_OBJECT (icon), "ANIMATION");
+  if (anim != NULL)
+    ide_animation_stop (anim);
+
+  progress = ide_notification_get_progress (notif);
+  anim = ide_object_animate (icon,
+                             IDE_ANIMATION_LINEAR,
+                             200,
+                             NULL,
+                             "progress", progress,
+                             NULL);
+  g_object_set_data_full (G_OBJECT (icon),
+                          "ANIMATION",
+                          g_object_ref (anim),
+                          g_object_unref);
+}
+
+static void
+notify_body_cb (IdeNotification *notif,
+                GParamSpec      *pspec,
+                VteTerminal     *terminal)
+{
+  g_autofree char *body = NULL;
+
+  g_assert (IDE_IS_NOTIFICATION (notif));
+  g_assert (VTE_IS_TERMINAL (terminal));
+
+  /* TODO: we need to plumb something better than IdeNotification to pass
+   * essentially PTY data between the worker and the UI process. but this
+   * will be fine for now until we can get to it.
+   */
+
+  if ((body = ide_notification_dup_body (notif)))
+    vte_terminal_feed (terminal, body, -1);
+}
+
+static void
+clone_action (GtkWidget  *widget,
+              const char *action_name,
+              GVariant   *param)
+{
+  GbpVcsuiClonePage *self = (GbpVcsuiClonePage *)widget;
+  g_autoptr(IdeNotification) notif = NULL;
+  IdeGreeterWorkspace *greeter;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+
+  gtk_stack_set_visible_child_name (self->stack, "progress");
+  gtk_widget_show (GTK_WIDGET (self->progress));
+
+  notif = ide_notification_new ();
+  g_signal_connect_object (notif,
+                           "notify::progress",
+                           G_CALLBACK (notify_progress_cb),
+                           self->progress,
+                           0);
+  g_signal_connect_object (notif,
+                           "notify::body",
+                           G_CALLBACK (notify_body_cb),
+                           self->terminal,
+                           0);
+
+  greeter = IDE_GREETER_WORKSPACE (ide_widget_get_workspace (widget));
+  ide_greeter_workspace_begin (greeter);
+  gtk_widget_action_set_enabled (widget, "clone-page.clone", FALSE);
+
+  gtk_label_set_label (self->failure_message, NULL);
+
+  ide_vcs_clone_request_clone_async (self->request,
+                                     notif,
+                                     NULL,
+                                     gbp_vcsui_clone_page_clone_cb,
+                                     g_object_ref (self));
+
+  IDE_EXIT;
+}
+
+static void
+branch_activated_cb (GbpVcsuiClonePage *self,
+                     guint              position,
+                     GtkListView       *list_view)
+{
+  g_autoptr(IdeVcsBranch) branch = NULL;
+  g_autofree char *branch_id = NULL;
+  GListModel *model;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+  g_assert (GTK_IS_LIST_VIEW (list_view));
+
+  model = G_LIST_MODEL (gtk_list_view_get_model (list_view));
+  branch = g_list_model_get_item (model, position);
+  branch_id = ide_vcs_branch_dup_id (branch);
+
+  ide_vcs_clone_request_set_branch_name (self->request, branch_id);
+
+  gtk_menu_button_popdown (self->branch_button);
+
+  IDE_EXIT;
+}
+
+static void
+branch_popover_show_cb (GbpVcsuiClonePage *self,
+                        GtkPopover        *popover)
+{
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+  g_assert (GTK_IS_POPOVER (popover));
+
+  ide_vcs_clone_request_populate_branches (self->request);
+
+  IDE_EXIT;
+}
+
+static void
+branch_name_changed_cb (GbpVcsuiClonePage  *self,
+                        GParamSpec         *pspec,
+                        IdeVcsCloneRequest *request)
+{
+  const char *branch_name;
+  gboolean empty;
+
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+  g_assert (IDE_IS_VCS_CLONE_REQUEST (request));
+
+  branch_name = ide_vcs_clone_request_get_branch_name (request);
+
+  /* Very much a git-ism, but that's all we support right now */
+  if (branch_name != NULL && g_str_has_prefix (branch_name, "refs/heads/"))
+    branch_name += strlen ("refs/heads/");
+
+  empty = ide_str_empty0 (branch_name);
+
+  gtk_widget_set_tooltip_text (GTK_WIDGET (self->branch_label),
+                               empty ? NULL : branch_name);
+  gtk_label_set_label (self->branch_label, branch_name);
+  gtk_widget_set_visible (GTK_WIDGET (self->branch_label), !empty);
+}
+
+static void
+request_notify_cb (GbpVcsuiClonePage  *self,
+                   GParamSpec         *pspec,
+                   IdeVcsCloneRequest *request)
+{
+  IdeVcsCloneRequestValidation flags = 0;
+
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+  g_assert (IDE_IS_VCS_CLONE_REQUEST (request));
+
+  flags = ide_vcs_clone_request_validate (request);
+
+  if (flags & IDE_VCS_CLONE_REQUEST_INVAL_URI)
+    gtk_widget_add_css_class (GTK_WIDGET (self->uri_row), "error");
+  else
+    gtk_widget_remove_css_class (GTK_WIDGET (self->uri_row), "error");
+
+  if (flags & IDE_VCS_CLONE_REQUEST_INVAL_DIRECTORY)
+    gtk_widget_add_css_class (GTK_WIDGET (self->location_row), "error");
+  else
+    gtk_widget_remove_css_class (GTK_WIDGET (self->location_row), "error");
+
+  if (flags & IDE_VCS_CLONE_REQUEST_INVAL_EMAIL)
+    gtk_widget_add_css_class (GTK_WIDGET (self->author_email_row), "error");
+  else
+    gtk_widget_remove_css_class (GTK_WIDGET (self->author_email_row), "error");
+
+  gtk_widget_action_set_enabled (GTK_WIDGET (self), "clone-page.clone", flags == 0);
+}
+
+static void
+gbp_vcsui_clone_page_root (GtkWidget *widget)
+{
+  GbpVcsuiClonePage *self = (GbpVcsuiClonePage *)widget;
+  IdeContext *context;
+
+  g_assert (GBP_IS_VCSUI_CLONE_PAGE (self));
+
+  GTK_WIDGET_CLASS (gbp_vcsui_clone_page_parent_class)->root (widget);
+
+  if ((context = ide_widget_get_context (widget)))
+    ide_object_append (IDE_OBJECT (context), IDE_OBJECT (self->request));
+}
+
+static void
+gbp_vcsui_clone_page_dispose (GObject *object)
+{
+  GbpVcsuiClonePage *self = (GbpVcsuiClonePage *)object;
+
+  ide_object_destroy (IDE_OBJECT (self->request));
+  g_clear_pointer (&self->main, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (gbp_vcsui_clone_page_parent_class)->dispose (object);
+}
+
+static void
+gbp_vcsui_clone_page_class_init (GbpVcsuiClonePageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gbp_vcsui_clone_page_dispose;
+
+  widget_class->root = gbp_vcsui_clone_page_root;
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/vcsui/gbp-vcsui-clone-page.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, author_email_row);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, author_name_row);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, branch_button);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, branch_label);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, failure_message);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, location_row);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, main);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, progress);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, request);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, stack);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, terminal);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiClonePage, uri_row);
+
+  gtk_widget_class_bind_template_callback (widget_class, location_row_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, branch_activated_cb);
+  gtk_widget_class_bind_template_callback (widget_class, branch_name_changed_cb);
+  gtk_widget_class_bind_template_callback (widget_class, branch_popover_show_cb);
+  gtk_widget_class_bind_template_callback (widget_class, request_notify_cb);
+
+  gtk_widget_class_install_action (widget_class, "clone-page.select-folder", NULL, select_folder_action);
+  gtk_widget_class_install_action (widget_class, "clone-page.clone", NULL, clone_action);
+
+  g_type_ensure (IDE_TYPE_PROGRESS_ICON);
+  g_type_ensure (VTE_TYPE_TERMINAL);
+  g_type_ensure (IDE_TYPE_VCS_CLONE_REQUEST);
+}
+
+static void
+gbp_vcsui_clone_page_init (GbpVcsuiClonePage *self)
+{
+  g_autofree char *projects_dir = ide_path_collapse (ide_get_projects_dir ());
+  static GdkRGBA transparent = {0, 0, 0, 0};
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_editable_set_text (GTK_EDITABLE (self->location_row), projects_dir);
+  gtk_editable_set_text (GTK_EDITABLE (self->author_name_row), g_get_real_name ());
+
+  vte_terminal_set_colors (self->terminal, NULL, &transparent, NULL, 0);
+}
+
+void
+gbp_vcsui_clone_page_set_uri (GbpVcsuiClonePage *self,
+                              const char        *uri)
+{
+  g_return_if_fail (GBP_IS_VCSUI_CLONE_PAGE (self));
+
+  if (uri == NULL)
+    uri = "";
+
+  gtk_editable_set_text (GTK_EDITABLE (self->uri_row), uri);
+}
diff --git a/src/plugins/vcsui/gbp-vcsui-clone-page.h b/src/plugins/vcsui/gbp-vcsui-clone-page.h
new file mode 100644
index 000000000..02289653f
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-clone-page.h
@@ -0,0 +1,36 @@
+/* gbp-vcsui-clone-page.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 <gtk/gtk.h>
+
+#include <libide-vcs.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_VCSUI_CLONE_PAGE (gbp_vcsui_clone_page_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpVcsuiClonePage, gbp_vcsui_clone_page, GBP, VCSUI_CLONE_PAGE, GtkWidget)
+
+void gbp_vcsui_clone_page_set_uri (GbpVcsuiClonePage *self,
+                                   const char        *uri);
+
+G_END_DECLS
diff --git a/src/plugins/vcsui/gbp-vcsui-clone-page.ui b/src/plugins/vcsui/gbp-vcsui-clone-page.ui
new file mode 100644
index 000000000..81783d896
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-clone-page.ui
@@ -0,0 +1,303 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="Adw" version="1.0"/>
+  <template class="GbpVcsuiClonePage" parent="GtkWidget">
+    <child>
+      <object class="AdwClamp" id="main">
+        <property name="orientation">horizontal</property>
+        <property name="maximum-size">550</property>
+        <child>
+          <object class="GtkBox">
+            <property name="margin-top">64</property>
+            <property name="margin-bottom">64</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">24</property>
+            <child>
+              <object class="AdwPreferencesGroup">
+                <child>
+                  <object class="AdwEntryRow" id="uri_row">
+                    <property name="title" translatable="yes">Repository URL</property>
+                    <property name="text" bind-source="request" bind-property="uri" 
bind-flags="sync-create|bidirectional"/>
+                    <child type="suffix">
+                      <object class="GtkLabel" id="branch_label">
+                        <property name="ellipsize">end</property>
+                        <property name="selectable">true</property>
+                        <property name="max-width-chars">10</property>
+                        <property name="visible">false</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                    </child>
+                    <child type="suffix">
+                      <object class="GtkMenuButton" id="branch_button">
+                        <style>
+                          <class name="flat"/>
+                        </style>
+                        <property name="icon-name">builder-vcs-branch-symbolic</property>
+                        <property name="tooltip-text" translatable="yes">Choose an alternate 
branch</property>
+                        <property name="valign">center</property>
+                        <property name="popover">
+                          <object class="GtkPopover">
+                            <style>
+                              <class name="menu"/>
+                            </style>
+                            <signal name="show" handler="branch_popover_show_cb" swapped="true" 
object="GbpVcsuiClonePage"/>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkBox">
+                                    <property name="margin-start">18</property>
+                                    <property name="margin-end">18</property>
+                                    <property name="margin-top">12</property>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="label" translatable="yes">Branches</property>
+                                        <property name="hexpand">true</property>
+                                        <property name="xalign">0</property>
+                                        <style>
+                                          <class name="heading"/>
+                                          <class name="dim-label"/>
+                                        </style>
+                                      </object>
+                                    </child>
+                                    <child>
+                                      <object class="GtkSpinner">
+                                        <property name="spinning" bind-source="request" 
bind-property="branch-model-busy"/>
+                                        <property name="visible" bind-source="request" 
bind-property="branch-model-busy"/>
+                                      </object>
+                                    </child>
+                                  </object>
+                                </child>
+                                <child>
+                                  <object class="GtkScrolledWindow">
+                                    <property name="propagate-natural-height">true</property>
+                                    <property name="propagate-natural-width">true</property>
+                                    <property name="min-content-width">300</property>
+                                    <property name="max-content-width">300</property>
+                                    <property name="max-content-height">500</property>
+                                    <child>
+                                      <object class="GtkListView">
+                                        <signal name="activate" handler="branch_activated_cb" swapped="true" 
object="GbpVcsuiClonePage"/>
+                                        <property name="model">
+                                          <object class="GtkSingleSelection">
+                                            <property name="model" bind-source="request" 
bind-property="branch-model" bind-flags="sync-create"/>
+                                          </object>
+                                        </property>
+                                        <property name="orientation">vertical</property>
+                                        <property name="single-click-activate">true</property>
+                                        <property name="factory">
+                                          <object class="GtkBuilderListItemFactory">
+                                            <property name="bytes"><![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GtkListItem">
+    <property name="child">
+      <object class="GtkBox">
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="xalign">0</property>
+            <property name="ellipsize">end</property>
+            <property name="hexpand">true</property>
+            <binding name="label">
+              <lookup name="name" type="IdeVcsBranch">
+                <lookup name="item">GtkListItem</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+      </object>
+    </property>
+  </template>
+</interface>
+]]>
+                                            </property>
+                                          </object>
+                                        </property>
+                                      </object>
+                                    </child>
+                                  </object>
+                                </child>
+                              </object>
+                            </child>
+                          </object>
+                        </property>
+                        <property name="sensitive" bind-source="request" bind-property="can-select-branch" 
bind-flags="sync-create"/>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="xalign">0</property>
+                    <property name="margin-top">12</property>
+                    <property name="wrap">true</property>
+                    <property name="label" translatable="yes">Enter the URL of the source code repository 
for the project you would like to clone.</property>
+                    <style>
+                      <class name="caption"/>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="AdwPreferencesGroup">
+                <child>
+                  <object class="AdwEntryRow" id="location_row">
+                    <property name="title" translatable="yes">Location</property>
+                    <signal name="changed" handler="location_row_changed_cb" swapped="true" 
object="GbpVcsuiClonePage"/>
+                    <child type="suffix">
+                      <object class="GtkButton">
+                        <property name="action-name">clone-page.select-folder</property>
+                        <property name="valign">center</property>
+                        <property name="icon-name">folder-symbolic</property>
+                        <style>
+                          <class name="flat"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel">
+                    <property name="label" translatable="yes">The repository will be cloned into a new 
subdirectory.</property>
+                    <property name="margin-top">12</property>
+                    <property name="wrap">true</property>
+                    <property name="xalign">0</property>
+                    <style>
+                      <class name="caption"/>
+                      <class name="dim-label"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStack" id="stack">
+                <property name="hhomogeneous">true</property>
+                <property name="vhomogeneous">true</property>
+                <property name="transition-type">crossfade</property>
+                <property name="transition-duration">300</property>
+                <property name="vexpand">false</property>
+                <child>
+                  <object class="GtkStackPage">
+                    <property name="name">details</property>
+                    <property name="child">
+                      <object class="AdwPreferencesGroup">
+                        <property name="title" translatable="yes">Author Details</property>
+                        <child>
+                          <object class="AdwEntryRow" id="author_name_row">
+                            <property name="title" translatable="yes">Name</property>
+                            <property name="text" bind-source="request" bind-property="author-name" 
bind-flags="sync-create|bidirectional"/>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="AdwEntryRow" id="author_email_row">
+                            <property name="title" translatable="yes">Email</property>
+                            <property name="text" bind-source="request" bind-property="author-email" 
bind-flags="sync-create|bidirectional"/>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="xalign">0</property>
+                            <property name="margin-top">12</property>
+                            <property name="wrap">true</property>
+                            <property name="label" translatable="yes">You may specify authorship information 
to override defaults.</property>
+                            <style>
+                              <class name="caption"/>
+                              <class name="dim-label"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkStackPage">
+                    <property name="name">progress</property>
+                    <property name="child">
+                      <object class="AdwPreferencesGroup">
+                        <property name="title" translatable="yes">Status</property>
+                        <child>
+                          <object class="GtkScrolledWindow">
+                            <style>
+                              <class name="card"/>
+                            </style>
+                            <property name="has-frame">false</property>
+                            <property name="min-content-height">100</property>
+                            <property name="max-content-height">300</property>
+                            <property name="vscrollbar-policy">external</property>
+                            <property name="hscrollbar-policy">never</property>
+                            <property name="vexpand">true</property>
+                            <child>
+                              <object class="VteTerminal" id="terminal">
+                                <property name="margin-top">9</property>
+                                <property name="margin-bottom">9</property>
+                                <property name="margin-start">9</property>
+                                <property name="margin-end">9</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="margin-top">12</property>
+                <property name="spacing">16</property>
+                <child>
+                  <object class="GtkLabel" id="failure_message">
+                    <property name="wrap">true</property>
+                    <property name="hexpand">true</property>
+                    <property name="xalign">0</property>
+                    <property name="valign">center</property>
+                    <style>
+                      <class name="error"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="IdeProgressIcon" id="progress">
+                    <property name="visible">false</property>
+                    <property name="valign">center</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="action-name">clone-page.clone</property>
+                    <property name="label" translatable="yes">Clone Repository</property>
+                    <property name="valign">center</property>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="IdeVcsCloneRequest" id="request">
+    <!--
+      Currently only `git` clone is supported. Happy to support other
+      version-control systems if someone 1) writes the IdeVcs backend
+      and 2) sticks around to maintain it with me.
+    -->
+    <property name="module-name">git</property>
+    <signal name="notify::branch-name" handler="branch_name_changed_cb" swapped="true" 
object="GbpVcsuiClonePage"/>
+    <!-- All the following queue validation requests -->
+    <signal name="notify::author-email" handler="request_notify_cb" swapped="true" 
object="GbpVcsuiClonePage"/>
+    <signal name="notify::directory" handler="request_notify_cb" swapped="true" object="GbpVcsuiClonePage"/>
+    <signal name="notify::uri" handler="request_notify_cb" swapped="true" object="GbpVcsuiClonePage"/>
+  </object>
+</interface>
diff --git a/src/plugins/vcsui/gbp-vcsui-editor-page-addin.c b/src/plugins/vcsui/gbp-vcsui-editor-page-addin.c
index e08d57826..03dcf50e6 100644
--- a/src/plugins/vcsui/gbp-vcsui-editor-page-addin.c
+++ b/src/plugins/vcsui/gbp-vcsui-editor-page-addin.c
@@ -33,25 +33,27 @@ struct _GbpVcsuiEditorPageAddin
 
 static void
 on_push_snippet_cb (GbpVcsuiEditorPageAddin *self,
-                    IdeSnippet              *snippet,
+                    GtkSourceSnippet        *snippet,
                     GtkTextIter             *iter,
                     IdeSourceView           *source_view)
 {
   g_autoptr(IdeVcsConfig) vcs_config = NULL;
   g_autoptr(IdeContext) ide_context = NULL;
-  IdeSnippetContext *context;
+  GtkSourceSnippetContext *context;
   IdeBuffer *buffer;
   IdeVcs *vcs;
 
+  IDE_ENTRY;
+
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (GBP_IS_VCSUI_EDITOR_PAGE_ADDIN (self));
-  g_assert (IDE_IS_SNIPPET (snippet));
+  g_assert (GTK_SOURCE_IS_SNIPPET (snippet));
   g_assert (iter != NULL);
   g_assert (IDE_IS_SOURCE_VIEW (source_view));
 
   buffer = IDE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view)));
   ide_context = ide_buffer_ref_context (buffer);
-  context = ide_snippet_get_context (snippet);
+  context = gtk_source_snippet_get_context (snippet);
 
   if ((vcs = ide_vcs_from_context (ide_context)) &&
        (vcs_config = ide_vcs_get_config (vcs)))
@@ -64,9 +66,9 @@ on_push_snippet_cb (GbpVcsuiEditorPageAddin *self,
 
       if (!ide_str_empty0 (g_value_get_string (&value)))
         {
-          ide_snippet_context_add_shared_variable (context, "author", g_value_get_string (&value));
-          ide_snippet_context_add_shared_variable (context, "fullname", g_value_get_string (&value));
-          ide_snippet_context_add_shared_variable (context, "username", g_value_get_string (&value));
+          gtk_source_snippet_context_set_variable (context, "author", g_value_get_string (&value));
+          gtk_source_snippet_context_set_variable (context, "fullname", g_value_get_string (&value));
+          gtk_source_snippet_context_set_variable (context, "username", g_value_get_string (&value));
         }
 
       g_value_reset (&value);
@@ -74,10 +76,12 @@ on_push_snippet_cb (GbpVcsuiEditorPageAddin *self,
       ide_vcs_config_get_config (vcs_config, IDE_VCS_CONFIG_EMAIL, &value);
 
       if (!ide_str_empty0 (g_value_get_string (&value)))
-        ide_snippet_context_add_shared_variable (context, "email", g_value_get_string (&value));
+        gtk_source_snippet_context_set_variable (context, "email", g_value_get_string (&value));
 
       g_value_unset (&value);
     }
+
+  IDE_EXIT;
 }
 
 static void
@@ -124,7 +128,7 @@ editor_page_addin_iface_init (IdeEditorPageAddinInterface *iface)
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpVcsuiEditorPageAddin, gbp_vcsui_editor_page_addin, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_EDITOR_PAGE_ADDIN, editor_page_addin_iface_init))
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_EDITOR_PAGE_ADDIN, 
editor_page_addin_iface_init))
 
 static void
 gbp_vcsui_editor_page_addin_class_init (GbpVcsuiEditorPageAddinClass *klass)
diff --git a/src/plugins/vcsui/gbp-vcsui-switcher-popover.c b/src/plugins/vcsui/gbp-vcsui-switcher-popover.c
new file mode 100644
index 000000000..ea7048a1c
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-switcher-popover.c
@@ -0,0 +1,244 @@
+/* gbp-vcsui-switcher-popover.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-vcsui-switcher-popover"
+
+#include "config.h"
+
+#include "gbp-vcsui-switcher-popover.h"
+
+struct _GbpVcsuiSwitcherPopover
+{
+  GtkPopover parent_instance;
+
+  IdeVcs *vcs;
+
+  GtkListView *branches_view;
+  GListStore  *branches_model;
+  GtkListView *tags_view;
+  GListStore  *tags_model;
+};
+
+enum {
+  PROP_0,
+  PROP_VCS,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (GbpVcsuiSwitcherPopover, gbp_vcsui_switcher_popover, GTK_TYPE_POPOVER)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_vcsui_switcher_popover_list_branches_cb (GObject      *object,
+                                             GAsyncResult *result,
+                                             gpointer      user_data)
+{
+  IdeVcs *vcs = (IdeVcs *)object;
+  g_autoptr(GbpVcsuiSwitcherPopover) self = user_data;
+  g_autoptr(GPtrArray) ar = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_VCS (vcs));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (GBP_IS_VCSUI_SWITCHER_POPOVER (self));
+
+  if (!(ar = ide_vcs_list_branches_finish (vcs, result, &error)))
+    {
+      g_warning ("Failed to list branches: %s\n", error->message);
+      IDE_EXIT;
+    }
+
+  g_ptr_array_set_free_func (ar, g_object_unref);
+  g_list_store_remove_all (self->branches_model);
+
+  for (guint i = 0; i < ar->len; i++)
+    g_list_store_append (self->branches_model, g_ptr_array_index (ar, i));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_vcsui_switcher_popover_list_tags_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  IdeVcs *vcs = (IdeVcs *)object;
+  g_autoptr(GbpVcsuiSwitcherPopover) self = user_data;
+  g_autoptr(GPtrArray) ar = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_VCS (vcs));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (GBP_IS_VCSUI_SWITCHER_POPOVER (self));
+
+  if (!(ar = ide_vcs_list_tags_finish (vcs, result, &error)))
+    {
+      g_warning ("Failed to list tags: %s\n", error->message);
+      IDE_EXIT;
+    }
+
+  g_ptr_array_set_free_func (ar, g_object_unref);
+  g_list_store_remove_all (self->tags_model);
+
+  for (guint i = 0; i < ar->len; i++)
+    g_list_store_append (self->tags_model, g_ptr_array_index (ar, i));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_vcsui_switcher_popover_dispose (GObject *object)
+{
+  GbpVcsuiSwitcherPopover *self = (GbpVcsuiSwitcherPopover *)object;
+
+  g_clear_object (&self->vcs);
+
+  G_OBJECT_CLASS (gbp_vcsui_switcher_popover_parent_class)->dispose (object);
+}
+
+static void
+gbp_vcsui_switcher_popover_show (GtkWidget *widget)
+{
+  GbpVcsuiSwitcherPopover *self = (GbpVcsuiSwitcherPopover *)widget;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_VCSUI_SWITCHER_POPOVER (self));
+
+  if (self->vcs != NULL)
+    {
+      ide_vcs_list_branches_async (self->vcs,
+                                   NULL,
+                                   gbp_vcsui_switcher_popover_list_branches_cb,
+                                   g_object_ref (self));
+      ide_vcs_list_tags_async (self->vcs,
+                               NULL,
+                               gbp_vcsui_switcher_popover_list_tags_cb,
+                               g_object_ref (self));
+    }
+
+  GTK_WIDGET_CLASS (gbp_vcsui_switcher_popover_parent_class)->show (widget);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_vcsui_switcher_popover_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+  GbpVcsuiSwitcherPopover *self = GBP_VCSUI_SWITCHER_POPOVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_VCS:
+      g_value_set_object (value, self->vcs);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_vcsui_switcher_popover_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec)
+{
+  GbpVcsuiSwitcherPopover *self = GBP_VCSUI_SWITCHER_POPOVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_VCS:
+      gbp_vcsui_switcher_popover_set_vcs (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_vcsui_switcher_popover_class_init (GbpVcsuiSwitcherPopoverClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = gbp_vcsui_switcher_popover_dispose;
+  object_class->get_property = gbp_vcsui_switcher_popover_get_property;
+  object_class->set_property = gbp_vcsui_switcher_popover_set_property;
+
+  widget_class->show = gbp_vcsui_switcher_popover_show;
+
+  properties [PROP_VCS] =
+    g_param_spec_object ("vcs",
+                         "Vcs",
+                         "The version control system",
+                         IDE_TYPE_VCS,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/plugins/vcsui/gbp-vcsui-switcher-popover.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiSwitcherPopover, branches_view);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiSwitcherPopover, branches_model);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiSwitcherPopover, tags_view);
+  gtk_widget_class_bind_template_child (widget_class, GbpVcsuiSwitcherPopover, tags_model);
+}
+
+static void
+gbp_vcsui_switcher_popover_init (GbpVcsuiSwitcherPopover *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+IdeVcs *
+gbp_vcsui_switcher_popover_get_vcs (GbpVcsuiSwitcherPopover *self)
+{
+  g_return_val_if_fail (GBP_IS_VCSUI_SWITCHER_POPOVER (self), NULL);
+
+  return self->vcs;
+}
+
+void
+gbp_vcsui_switcher_popover_set_vcs (GbpVcsuiSwitcherPopover *self,
+                                    IdeVcs                  *vcs)
+{
+  g_return_if_fail (GBP_IS_VCSUI_SWITCHER_POPOVER (self));
+  g_return_if_fail (!vcs || IDE_IS_VCS (vcs));
+
+  if (g_set_object (&self->vcs, vcs))
+    {
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VCS]);
+    }
+}
+
+GtkWidget *
+gbp_vcsui_switcher_popover_new (void)
+{
+  return g_object_new (GBP_TYPE_VCSUI_SWITCHER_POPOVER, NULL);
+}
diff --git a/src/plugins/vcsui/gbp-vcsui-switcher-popover.h b/src/plugins/vcsui/gbp-vcsui-switcher-popover.h
new file mode 100644
index 000000000..3948133b5
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-switcher-popover.h
@@ -0,0 +1,38 @@
+/* gbp-vcsui-switcher-popover.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 <gtk/gtk.h>
+
+#include <libide-vcs.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_VCSUI_SWITCHER_POPOVER (gbp_vcsui_switcher_popover_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpVcsuiSwitcherPopover, gbp_vcsui_switcher_popover, GBP, VCSUI_SWITCHER_POPOVER, 
GtkPopover)
+
+GtkWidget *gbp_vcsui_switcher_popover_new     (void);
+IdeVcs    *gbp_vcsui_switcher_popover_get_vcs (GbpVcsuiSwitcherPopover *self);
+void       gbp_vcsui_switcher_popover_set_vcs (GbpVcsuiSwitcherPopover *self,
+                                               IdeVcs                  *vcs);
+
+G_END_DECLS
diff --git a/src/plugins/vcsui/gbp-vcsui-switcher-popover.ui b/src/plugins/vcsui/gbp-vcsui-switcher-popover.ui
new file mode 100644
index 000000000..1ebddfdef
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-switcher-popover.ui
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpVcsuiSwitcherPopover" parent="GtkPopover">
+    <style>
+      <class name="menu"/>
+    </style>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkStackSwitcher">
+            <property name="stack">stack</property>
+            <property name="margin-top">6</property>
+            <property name="margin-start">6</property>
+            <property name="margin-end">6</property>
+            <property name="margin-bottom">6</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="stack">
+            <property name="vhomogeneous">false</property>
+            <property name="interpolate-size">true</property>
+            <property name="transition-type">crossfade</property>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">branches</property>
+                <property name="title" translatable="yes">_Branches</property>
+                <property name="use-underline">true</property>
+                <property name="child">
+                  <object class="GtkScrolledWindow">
+                    <property name="propagate-natural-height">true</property>
+                    <property name="propagate-natural-width">true</property>
+                    <property name="min-content-height">100</property>
+                    <property name="max-content-height">600</property>
+                    <property name="min-content-width">300</property>
+                    <property name="max-content-width">300</property>
+                    <child>
+                      <object class="GtkListView" id="branches_view">
+                        <!--signal name="activate" handler="activate_branch_cb" swapped="true" 
object="GbpVcsuiSwitcherPopover"/-->
+                        <property name="orientation">vertical</property>
+                        <property name="single-click-activate">true</property>
+                        <property name="model">
+                          <object class="GtkNoSelection">
+                            <property name="model">branches_model</property>
+                          </object>
+                        </property>
+                        <property name="factory">
+                          <object class="GtkBuilderListItemFactory">
+                            <property name="bytes"><![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GtkListItem">
+    <property name="child">
+      <object class="GtkBox">
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="halign">start</property>
+            <property name="hexpand">true</property>
+            <property name="ellipsize">start</property>
+            <binding name="label">
+              <lookup name="name" type="IdeVcsBranch">
+                <lookup name="item">GtkListItem</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+      </object>
+    </property>
+  </template>
+</interface>
+]]>
+                            </property>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                  </object>
+                </property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">tags</property>
+                <property name="title" translatable="yes">_Tags</property>
+                <property name="use-underline">true</property>
+                <property name="child">
+                  <object class="GtkScrolledWindow">
+                    <property name="propagate-natural-height">true</property>
+                    <property name="propagate-natural-width">true</property>
+                    <property name="min-content-height">100</property>
+                    <property name="max-content-height">600</property>
+                    <property name="min-content-width">300</property>
+                    <property name="max-content-width">300</property>
+                    <child>
+                      <object class="GtkListView" id="tags_view">
+                        <!--signal name="activate" handler="activate_tag_cb" swapped="true" 
object="GbpVcsuiSwitcherPopover"/-->
+                        <property name="orientation">vertical</property>
+                        <property name="single-click-activate">true</property>
+                        <property name="model">
+                          <object class="GtkNoSelection">
+                            <property name="model">tags_model</property>
+                          </object>
+                        </property>
+                        <property name="factory">
+                          <object class="GtkBuilderListItemFactory">
+                            <property name="bytes"><![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GtkListItem">
+    <property name="child">
+      <object class="GtkBox">
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel">
+            <property name="halign">start</property>
+            <property name="hexpand">true</property>
+            <property name="ellipsize">start</property>
+            <binding name="label">
+              <lookup name="name" type="IdeVcsTag">
+                <lookup name="item">GtkListItem</lookup>
+              </lookup>
+            </binding>
+          </object>
+        </child>
+      </object>
+    </property>
+  </template>
+</interface>
+]]>
+                            </property>
+                          </object>
+                        </property>
+                      </object>
+                    </child>
+                  </object>
+                </property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GListStore" id="branches_model">
+    <property name="item-type">IdeVcsBranch</property>
+  </object>
+  <object class="GListStore" id="tags_model">
+    <property name="item-type">IdeVcsTag</property>
+  </object>
+</interface>
diff --git a/src/plugins/vcsui/gbp-vcsui-tree-addin.c b/src/plugins/vcsui/gbp-vcsui-tree-addin.c
index 9b8adac71..70c833698 100644
--- a/src/plugins/vcsui/gbp-vcsui-tree-addin.c
+++ b/src/plugins/vcsui/gbp-vcsui-tree-addin.c
@@ -37,6 +37,8 @@ struct _GbpVcsuiTreeAddin
 {
   GObject        parent_instance;
 
+  GActionMap    *actions;
+
   IdeTree       *tree;
   IdeTreeModel  *model;
   IdeVcs        *vcs;
@@ -170,6 +172,7 @@ gbp_vcsui_tree_addin_load (IdeTreeAddin *addin,
   gtk_widget_insert_action_group (GTK_WIDGET (tree),
                                   "vcsui",
                                   G_ACTION_GROUP (group));
+  self->actions = g_object_ref (G_ACTION_MAP (group));
 
   if ((workbench = ide_widget_get_workbench (GTK_WIDGET (tree))) &&
       (vcs = ide_workbench_get_vcs (workbench)) &&
@@ -199,6 +202,7 @@ gbp_vcsui_tree_addin_unload (IdeTreeAddin *addin,
 
   gtk_widget_insert_action_group (GTK_WIDGET (tree), "vcsui", NULL);
 
+  g_clear_object (&self->actions);
   g_clear_object (&self->monitor);
   g_clear_object (&self->vcs);
   self->model = NULL;
@@ -211,6 +215,7 @@ gbp_vcsui_tree_addin_selection_changed (IdeTreeAddin *addin,
 {
   GbpVcsuiTreeAddin *self = (GbpVcsuiTreeAddin *)addin;
   gboolean is_branch = FALSE;
+  GAction *action;
 
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (GBP_IS_VCSUI_TREE_ADDIN (self));
@@ -219,12 +224,11 @@ gbp_vcsui_tree_addin_selection_changed (IdeTreeAddin *addin,
   if (node != NULL)
     is_branch = ide_tree_node_holds (node, IDE_TYPE_VCS_BRANCH);
 
-  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "vcsui", "switch-branch",
-                             "enabled", is_branch,
-                             NULL);
-  dzl_gtk_widget_action_set (GTK_WIDGET (self->tree), "vcsui", "push-branch",
-                             "enabled", is_branch,
-                             NULL);
+  action = g_action_map_lookup_action (self->actions, "switch-branch");
+  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), is_branch);
+
+  action = g_action_map_lookup_action (self->actions, "push-branch");
+  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), is_branch);
 }
 
 static void
@@ -289,7 +293,7 @@ gbp_vcsui_tree_addin_list_branches_cb (GObject      *object,
       for (guint i = 0; i < branches->len; i++)
         {
           IdeVcsBranch *branch = g_ptr_array_index (branches, i);
-          g_autofree gchar *name = ide_vcs_branch_get_name (branch);
+          g_autofree gchar *name = ide_vcs_branch_dup_name (branch);
           g_autoptr(IdeTreeNode) child = NULL;
 
           child = g_object_new (IDE_TYPE_TREE_NODE,
@@ -329,7 +333,7 @@ gbp_vcsui_tree_addin_list_tags_cb (GObject      *object,
       for (guint i = 0; i < tags->len; i++)
         {
           IdeVcsTag *tag = g_ptr_array_index (tags, i);
-          g_autofree gchar *name = ide_vcs_tag_get_name (tag);
+          g_autofree gchar *name = ide_vcs_tag_dup_name (tag);
           g_autoptr(IdeTreeNode) child = NULL;
 
           child = g_object_new (IDE_TYPE_TREE_NODE,
@@ -461,7 +465,7 @@ tree_addin_iface_init (IdeTreeAddinInterface *iface)
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpVcsuiTreeAddin, gbp_vcsui_tree_addin, G_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_TREE_ADDIN, tree_addin_iface_init))
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_TREE_ADDIN, tree_addin_iface_init))
 
 static void
 gbp_vcsui_tree_addin_class_init (GbpVcsuiTreeAddinClass *klass)
diff --git a/src/plugins/vcsui/gbp-vcsui-workbench-addin.c b/src/plugins/vcsui/gbp-vcsui-workbench-addin.c
index 6fa7e1cec..8e04beee0 100644
--- a/src/plugins/vcsui/gbp-vcsui-workbench-addin.c
+++ b/src/plugins/vcsui/gbp-vcsui-workbench-addin.c
@@ -22,7 +22,6 @@
 
 #include "config.h"
 
-#include <dazzle.h>
 #include <libide-gui.h>
 #include <libide-vcs.h>
 
@@ -30,9 +29,8 @@
 
 struct _GbpVcsuiWorkbenchAddin
 {
-  GObject parent_instance;
-
-  DzlSignalGroup *vcs_signals;
+  GObject         parent_instance;
+  IdeSignalGroup *vcs_signals;
 };
 
 static void
@@ -71,7 +69,7 @@ gbp_vcsui_workbench_addin_vcs_changed (IdeWorkbenchAddin *addin,
   g_assert (GBP_IS_VCSUI_WORKBENCH_ADDIN (self));
   g_assert (!vcs || IDE_IS_VCS (vcs));
 
-  dzl_signal_group_set_target (self->vcs_signals, vcs);
+  ide_signal_group_set_target (self->vcs_signals, vcs);
 
   if (vcs != NULL)
     on_notify_branch_name (self, NULL, vcs);
@@ -87,8 +85,8 @@ gbp_vcsui_workbench_addin_load (IdeWorkbenchAddin *addin,
   g_assert (GBP_IS_VCSUI_WORKBENCH_ADDIN (self));
   g_assert (IDE_IS_WORKBENCH (workbench));
 
-  self->vcs_signals = dzl_signal_group_new (G_TYPE_OBJECT);
-  dzl_signal_group_connect_object (self->vcs_signals,
+  self->vcs_signals = ide_signal_group_new (G_TYPE_OBJECT);
+  ide_signal_group_connect_object (self->vcs_signals,
                                    "notify::branch-name",
                                    G_CALLBACK (on_notify_branch_name),
                                    self,
@@ -104,7 +102,7 @@ gbp_vcsui_workbench_addin_unload (IdeWorkbenchAddin *addin,
   g_assert (GBP_IS_VCSUI_WORKBENCH_ADDIN (self));
   g_assert (IDE_IS_WORKBENCH (workbench));
 
-  dzl_signal_group_set_target (self->vcs_signals, NULL);
+  ide_signal_group_set_target (self->vcs_signals, NULL);
   g_clear_object (&self->vcs_signals);
 }
 
@@ -117,7 +115,7 @@ workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpVcsuiWorkbenchAddin, gbp_vcsui_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_vcsui_workbench_addin_class_init (GbpVcsuiWorkbenchAddinClass *klass)
diff --git a/src/plugins/vcsui/gbp-vcsui-workspace-addin.c b/src/plugins/vcsui/gbp-vcsui-workspace-addin.c
new file mode 100644
index 000000000..c9930fe0a
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-workspace-addin.c
@@ -0,0 +1,194 @@
+/* gbp-vcsui-workspace-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-vcsui-workspace-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include <libide-gui.h>
+#include <libide-greeter.h>
+
+#include "gbp-vcsui-clone-page.h"
+#include "gbp-vcsui-switcher-popover.h"
+#include "gbp-vcsui-workspace-addin.h"
+
+struct _GbpVcsuiWorkspaceAddin
+{
+  GObject              parent_instance;
+
+  GbpVcsuiClonePage   *clone;
+
+  GtkMenuButton       *branch_button;
+  GtkLabel            *branch_label;
+  IdeBindingGroup     *vcs_bindings;
+};
+
+static gboolean
+greeter_open_project_cb (GbpVcsuiWorkspaceAddin *self,
+                         IdeProjectInfo         *project_info,
+                         IdeGreeterWorkspace    *greeter)
+{
+  const char *vcs_uri;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_VCSUI_WORKSPACE_ADDIN (self));
+  g_assert (IDE_IS_PROJECT_INFO (project_info));
+  g_assert (IDE_IS_GREETER_WORKSPACE (greeter));
+
+  if (!ide_project_info_get_file (project_info) &&
+      !ide_project_info_get_directory (project_info) &&
+      (vcs_uri = ide_project_info_get_vcs_uri (project_info)))
+    {
+      gbp_vcsui_clone_page_set_uri (self->clone, vcs_uri);
+      ide_greeter_workspace_set_page_name (greeter, "clone");
+      IDE_RETURN (TRUE);
+    }
+
+  IDE_RETURN (FALSE);
+}
+
+static void
+gbp_vcsui_workspace_addin_load (IdeWorkspaceAddin *addin,
+                                IdeWorkspace      *workspace)
+{
+  GbpVcsuiWorkspaceAddin *self = (GbpVcsuiWorkspaceAddin *)addin;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_VCSUI_WORKSPACE_ADDIN (self));
+
+  if (IDE_IS_GREETER_WORKSPACE (workspace))
+    {
+      g_signal_connect_object (workspace,
+                               "open-project",
+                               G_CALLBACK (greeter_open_project_cb),
+                               self,
+                               G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+      self->clone = g_object_new (GBP_TYPE_VCSUI_CLONE_PAGE,
+                                  NULL);
+      ide_greeter_workspace_add_page (IDE_GREETER_WORKSPACE (workspace),
+                                      GTK_WIDGET (self->clone),
+                                      "clone",
+                                      _("Clone Repository"));
+      ide_greeter_workspace_add_button (IDE_GREETER_WORKSPACE (workspace),
+                                        g_object_new (GTK_TYPE_BUTTON,
+                                                      "label", _("_Clone Repository…"),
+                                                      "action-name", "greeter.page",
+                                                      "action-target", g_variant_new_string ("clone"),
+                                                      "use-underline", TRUE,
+                                                      NULL),
+                                        100);
+    }
+  else if (IDE_IS_PRIMARY_WORKSPACE (workspace))
+    {
+      PanelStatusbar *statusbar;
+      IdeWorkbench *workbench;
+      GtkWidget *popover;
+      GtkImage *image;
+      GtkBox *box;
+
+      workbench = ide_workspace_get_workbench (workspace);
+      statusbar = ide_workspace_get_statusbar (workspace);
+
+      box = g_object_new (GTK_TYPE_BOX,
+                          "orientation", GTK_ORIENTATION_HORIZONTAL,
+                          "spacing", 6,
+                          NULL);
+      image = g_object_new (GTK_TYPE_IMAGE,
+                            "icon-name", "builder-vcs-branch-symbolic",
+                            "pixel-size", 16,
+                            NULL);
+      self->branch_label = g_object_new (GTK_TYPE_LABEL,
+                                         "xalign", .0f,
+                                         NULL);
+      gtk_box_append (box, GTK_WIDGET (image));
+      gtk_box_append (box, GTK_WIDGET (self->branch_label));
+
+      popover = gbp_vcsui_switcher_popover_new ();
+      g_object_bind_property (workbench, "vcs",
+                              popover, "vcs",
+                              G_BINDING_SYNC_CREATE);
+
+      self->branch_button = g_object_new (GTK_TYPE_MENU_BUTTON,
+                                          "child", box,
+                                          "direction", GTK_ARROW_UP,
+                                          "popover", popover,
+                                          NULL);
+      panel_statusbar_add_prefix (statusbar, G_MININT, GTK_WIDGET (self->branch_button));
+
+      self->vcs_bindings = ide_binding_group_new ();
+      ide_binding_group_bind (self->vcs_bindings, "branch-name",
+                              self->branch_label, "label",
+                              G_BINDING_SYNC_CREATE);
+      g_object_bind_property (workbench, "vcs",
+                              self->vcs_bindings, "source",
+                              G_BINDING_SYNC_CREATE);
+    }
+
+  IDE_EXIT;
+}
+
+static void
+gbp_vcsui_workspace_addin_unload (IdeWorkspaceAddin *addin,
+                                  IdeWorkspace      *workspace)
+{
+  GbpVcsuiWorkspaceAddin *self = (GbpVcsuiWorkspaceAddin *)addin;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_VCSUI_WORKSPACE_ADDIN (self));
+
+  if (IDE_IS_GREETER_WORKSPACE (workspace))
+    {
+      ide_greeter_workspace_remove_page (IDE_GREETER_WORKSPACE (workspace),
+                                         GTK_WIDGET (self->clone));
+      self->clone = NULL;
+    }
+  else if (IDE_IS_PRIMARY_WORKSPACE (workspace))
+    {
+      g_clear_object (&self->vcs_bindings);
+    }
+
+  IDE_EXIT;
+}
+
+static void
+workspace_addin_iface_init (IdeWorkspaceAddinInterface *iface)
+{
+  iface->load = gbp_vcsui_workspace_addin_load;
+  iface->unload = gbp_vcsui_workspace_addin_unload;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpVcsuiWorkspaceAddin, gbp_vcsui_workspace_addin, G_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKSPACE_ADDIN, workspace_addin_iface_init))
+
+static void
+gbp_vcsui_workspace_addin_class_init (GbpVcsuiWorkspaceAddinClass *klass)
+{
+  g_type_ensure (GBP_TYPE_VCSUI_SWITCHER_POPOVER);
+}
+
+static void
+gbp_vcsui_workspace_addin_init (GbpVcsuiWorkspaceAddin *self)
+{
+}
diff --git a/src/plugins/vcsui/gbp-vcsui-workspace-addin.h b/src/plugins/vcsui/gbp-vcsui-workspace-addin.h
new file mode 100644
index 000000000..1a3b3ba5a
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-workspace-addin.h
@@ -0,0 +1,31 @@
+/* gbp-vcsui-workspace-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_VCSUI_WORKSPACE_ADDIN (gbp_vcsui_workspace_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpVcsuiWorkspaceAddin, gbp_vcsui_workspace_addin, GBP, VCSUI_WORKSPACE_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/vcsui/meson.build b/src/plugins/vcsui/meson.build
index 35d44e02c..f860da30a 100644
--- a/src/plugins/vcsui/meson.build
+++ b/src/plugins/vcsui/meson.build
@@ -1,8 +1,12 @@
 plugins_sources += files([
   'vcsui-plugin.c',
-  'gbp-vcsui-tree-addin.c',
+  'gbp-vcsui-application-addin.c',
+  'gbp-vcsui-clone-page.c',
   'gbp-vcsui-editor-page-addin.c',
+  'gbp-vcsui-switcher-popover.c',
+  'gbp-vcsui-tree-addin.c',
   'gbp-vcsui-workbench-addin.c',
+  'gbp-vcsui-workspace-addin.c',
 ])
 
 plugin_vcsui_resources = gnome.compile_resources(
diff --git a/src/plugins/vcsui/vcsui-plugin.c b/src/plugins/vcsui/vcsui-plugin.c
index 0645ba3cc..5e5a46067 100644
--- a/src/plugins/vcsui/vcsui-plugin.c
+++ b/src/plugins/vcsui/vcsui-plugin.c
@@ -23,17 +23,23 @@
 #include "config.h"
 
 #include <libpeas/peas.h>
+
 #include <libide-editor.h>
 #include <libide-gui.h>
 #include <libide-tree.h>
 
+#include "gbp-vcsui-application-addin.h"
 #include "gbp-vcsui-editor-page-addin.h"
 #include "gbp-vcsui-tree-addin.h"
 #include "gbp-vcsui-workbench-addin.h"
+#include "gbp-vcsui-workspace-addin.h"
 
 _IDE_EXTERN void
 _gbp_vcsui_register_types (PeasObjectModule *module)
 {
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_APPLICATION_ADDIN,
+                                              GBP_TYPE_VCSUI_APPLICATION_ADDIN);
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_EDITOR_PAGE_ADDIN,
                                               GBP_TYPE_VCSUI_EDITOR_PAGE_ADDIN);
@@ -43,4 +49,7 @@ _gbp_vcsui_register_types (PeasObjectModule *module)
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_WORKBENCH_ADDIN,
                                               GBP_TYPE_VCSUI_WORKBENCH_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKSPACE_ADDIN,
+                                              GBP_TYPE_VCSUI_WORKSPACE_ADDIN);
 }
diff --git a/src/plugins/vcsui/vcsui.gresource.xml b/src/plugins/vcsui/vcsui.gresource.xml
index 3dd0a29b3..03f8a5be9 100644
--- a/src/plugins/vcsui/vcsui.gresource.xml
+++ b/src/plugins/vcsui/vcsui.gresource.xml
@@ -3,5 +3,7 @@
   <gresource prefix="/plugins/vcsui">
     <file>vcsui.plugin</file>
     <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+    <file preprocess="xml-stripblanks">gbp-vcsui-clone-page.ui</file>
+    <file preprocess="xml-stripblanks">gbp-vcsui-switcher-popover.ui</file>
   </gresource>
 </gresources>
diff --git a/src/plugins/vcsui/vcsui.plugin b/src/plugins/vcsui/vcsui.plugin
index 8772dac74..9dc625106 100644
--- a/src/plugins/vcsui/vcsui.plugin
+++ b/src/plugins/vcsui/vcsui.plugin
@@ -1,11 +1,12 @@
 [Plugin]
 Authors=Christian Hergert <christian hergert me>
 Builtin=true
-Copyright=Copyright © 2015-2018 Christian Hergert
-Depends=editor;project-tree;
+Copyright=Copyright © 2015-2022 Christian Hergert
+Depends=project-tree;
 Description=Provides user interface components to display VCS
 Embedded=_gbp_vcsui_register_types
 Hidden=true
 Module=vcsui
 Name=VCS interface extensions
-X-Workspace-Kind=primary;
+X-At-Startup=true
+X-Workspace-Kind=primary;greeter;


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