[gnome-builder/wip/cosimoc/flatpak-greeter: 2/10] Loading a project and set it up from a given flatpak manifest



commit cda7f780919f931c4d4fa21febe31facb54d7906
Author: Simon Schampijer <simon schampijer endlessm com>
Date:   Fri Dec 9 12:02:38 2016 +0000

    Loading a project and set it up from a given flatpak manifest
    
    This adds a command line option (-m) to pass a flatpak manifest
    when starting GNOME Builder. A greeter with progress feedback
    is displayed while the workbench is created and flatpak-builder
    downloads the sources in the background.
    
    This works currently for git sources only. Support for archives
    and file sources (patches) will be handled in a follow up
    commit.

 libide/application/ide-application-actions.c       |   38 ++-
 libide/application/ide-application-command-line.c  |   17 +
 libide/greeter/ide-greeter-perspective.c           |   31 +-
 libide/greeter/ide-greeter-perspective.h           |    3 +-
 plugins/flatpak/Makefile.am                        |   18 +-
 plugins/flatpak/configure.ac                       |   46 ++-
 plugins/flatpak/flatpak.plugin                     |    1 +
 plugins/flatpak/gbp-flatpak-clone-widget.c         |  508 ++++++++++++++++++++
 plugins/flatpak/gbp-flatpak-clone-widget.h         |   40 ++
 plugins/flatpak/gbp-flatpak-clone-widget.ui        |   51 ++
 plugins/flatpak/gbp-flatpak-genesis-addin.c        |  204 ++++++++
 plugins/flatpak/gbp-flatpak-genesis-addin.h        |   32 ++
 plugins/flatpak/gbp-flatpak-plugin.c               |    4 +
 .../flatpak/gbp-flatpak-resources.gresource.xml    |    6 +
 plugins/git/ide-git-plugin.c                       |    6 +
 plugins/git/ide-git-remote-callbacks.c             |   11 +
 16 files changed, 999 insertions(+), 17 deletions(-)
---
diff --git a/libide/application/ide-application-actions.c b/libide/application/ide-application-actions.c
index fb8b872..243ae47 100644
--- a/libide/application/ide-application-actions.c
+++ b/libide/application/ide-application-actions.c
@@ -175,17 +175,14 @@ ide_application_actions_open_project (GSimpleAction *action,
 
 
 static void
-ide_application_actions_new_project (GSimpleAction *action,
-                                     GVariant      *variant,
-                                     gpointer       user_data)
+ide_application_actions_load_workbench_view (IdeApplication *self,
+                                             const char     *genesis_view,
+                                             const char     *manifest)
 {
-  IdeApplication *self = user_data;
   IdeWorkbench *workbench = NULL;
   IdePerspective *greeter;
   const GList *list;
 
-  g_assert (IDE_IS_APPLICATION (self));
-
   list = gtk_application_get_windows (GTK_APPLICATION (self));
 
   for (; list != NULL; list = list->next)
@@ -214,13 +211,25 @@ ide_application_actions_new_project (GSimpleAction *action,
   if (greeter)
     {
       ide_greeter_perspective_show_genesis_view (IDE_GREETER_PERSPECTIVE (greeter),
-                                                 "GbpCreateProjectGenesisAddin");
+                                                 genesis_view, manifest);
     }
 
   gtk_window_present (GTK_WINDOW (workbench));
 }
 
 static void
+ide_application_actions_new_project (GSimpleAction *action,
+                                     GVariant      *variant,
+                                     gpointer       user_data)
+{
+  IdeApplication *self = user_data;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  ide_application_actions_load_workbench_view (self, "GbpCreateProjectGenesisAddin", NULL);
+}
+
+static void
 ide_application_actions_shortcuts (GSimpleAction *action,
                                    GVariant      *variant,
                                    gpointer       user_data)
@@ -307,6 +316,20 @@ ide_application_actions_load_project (GSimpleAction *action,
     }
 }
 
+static void
+ide_application_actions_load_flatpak (GSimpleAction *action,
+                                      GVariant      *args,
+                                      gpointer       user_data)
+{
+  IdeApplication *self = user_data;
+  const gchar *manifest = NULL;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  manifest = g_variant_get_string (args, NULL);
+  ide_application_actions_load_workbench_view (self, "GbpFlatpakGenesisAddin", manifest);
+}
+
 static const GActionEntry IdeApplicationActions[] = {
   { "about",        ide_application_actions_about },
   { "dayhack",      ide_application_actions_dayhack },
@@ -314,6 +337,7 @@ static const GActionEntry IdeApplicationActions[] = {
   { "open-project", ide_application_actions_open_project },
   { "new-project",  ide_application_actions_new_project },
   { "load-project", ide_application_actions_load_project, "s"},
+  { "load-flatpak", ide_application_actions_load_flatpak, "s"},
   { "preferences",  ide_application_actions_preferences },
   { "quit",         ide_application_actions_quit },
   { "shortcuts",    ide_application_actions_shortcuts },
diff --git a/libide/application/ide-application-command-line.c 
b/libide/application/ide-application-command-line.c
index f673385..c722e32 100644
--- a/libide/application/ide-application-command-line.c
+++ b/libide/application/ide-application-command-line.c
@@ -164,6 +164,7 @@ ide_application_local_command_line (GApplication   *application,
   IdeApplication *self = (IdeApplication *)application;
   g_autofree gchar *path_copy = NULL;
   g_autofree gchar *filename = NULL;
+  g_autofree gchar *manifest = NULL;
   GOptionContext *context = NULL;
   GOptionGroup *group;
   const gchar *shortdesc = NULL;
@@ -233,6 +234,14 @@ ide_application_local_command_line (GApplication   *application,
       N_("Opens the project specified by PATH"),
       N_("PATH") },
 
+    { "manifest",
+      'm',
+      G_OPTION_FLAG_IN_MAIN,
+      G_OPTION_ARG_FILENAME,
+      &manifest,
+      N_("Clones the project specified by MANIFEST"),
+      N_("MANIFEST") },
+
     { NULL }
   };
 
@@ -458,6 +467,14 @@ ide_application_local_command_line (GApplication   *application,
       goto cleanup;
     }
 
+  if (manifest != NULL)
+    {
+      GVariant *file;
+      file = g_variant_new ("s", manifest);
+      g_action_group_activate_action ((GActionGroup *) application, "load-flatpak", file);
+      goto cleanup;
+    }
+
   g_application_activate (application);
 
 cleanup:
diff --git a/libide/greeter/ide-greeter-perspective.c b/libide/greeter/ide-greeter-perspective.c
index 636f6b4..418c3b9 100644
--- a/libide/greeter/ide-greeter-perspective.c
+++ b/libide/greeter/ide-greeter-perspective.c
@@ -74,6 +74,7 @@ struct _IdeGreeterPerspective
 };
 
 static void ide_perspective_iface_init (IdePerspectiveInterface *iface);
+static void ide_greeter_perspective_genesis_continue (IdeGreeterPerspective *self);
 
 G_DEFINE_TYPE_EXTENDED (IdeGreeterPerspective, ide_greeter_perspective, GTK_TYPE_BIN, 0,
                         G_IMPLEMENT_INTERFACE (IDE_TYPE_PERSPECTIVE,
@@ -749,12 +750,24 @@ ide_greeter_perspective_open_clicked (IdeGreeterPerspective *self,
 
 void
 ide_greeter_perspective_show_genesis_view (IdeGreeterPerspective *self,
-                                           const gchar           *genesis_addin_name)
+                                           const gchar           *genesis_addin_name,
+                                           const gchar           *manifest)
 {
+  GtkWidget *addin;
+
   g_assert (IDE_IS_GREETER_PERSPECTIVE (self));
 
-  gtk_stack_set_visible_child_name (self->genesis_stack, genesis_addin_name);
+  addin = gtk_stack_get_child_by_name (self->genesis_stack, genesis_addin_name);
+  gtk_stack_set_visible_child (self->genesis_stack, addin);
   egg_state_machine_set_state (self->state_machine, "genesis");
+
+  if (manifest != NULL)
+    {
+      g_object_set (addin, "manifest", manifest, NULL);
+
+      gtk_widget_hide (GTK_WIDGET (self->genesis_continue_button));
+      ide_greeter_perspective_genesis_continue (self);
+    }
 }
 
 static void
@@ -767,7 +780,7 @@ genesis_button_clicked (IdeGreeterPerspective *self,
   g_assert (GTK_IS_BUTTON (button));
 
   name = gtk_widget_get_name (GTK_WIDGET (button));
-  ide_greeter_perspective_show_genesis_view (self, name);
+  ide_greeter_perspective_show_genesis_view (self, name, NULL);
 }
 
 static void
@@ -922,8 +935,7 @@ run_genesis_addin (PeasExtensionSet *set,
 }
 
 static void
-ide_greeter_perspective_genesis_continue_clicked (IdeGreeterPerspective *self,
-                                                  GtkButton             *button)
+ide_greeter_perspective_genesis_continue (IdeGreeterPerspective *self)
 {
   struct {
     IdeGreeterPerspective *self;
@@ -931,7 +943,6 @@ ide_greeter_perspective_genesis_continue_clicked (IdeGreeterPerspective *self,
   } state = { 0 };
 
   g_assert (IDE_IS_GREETER_PERSPECTIVE (self));
-  g_assert (GTK_IS_BUTTON (button));
 
   state.self = self;
   state.name = gtk_stack_get_visible_child_name (self->genesis_stack);
@@ -940,6 +951,14 @@ ide_greeter_perspective_genesis_continue_clicked (IdeGreeterPerspective *self,
 }
 
 static void
+ide_greeter_perspective_genesis_continue_clicked (IdeGreeterPerspective *self,
+                                                  GtkButton             *button)
+{
+  g_assert (GTK_IS_BUTTON (button));
+  ide_greeter_perspective_genesis_continue (self);
+}
+
+static void
 update_title_for_matching_addin (PeasExtensionSet *set,
                                  PeasPluginInfo   *plugin_info,
                                  PeasExtension    *exten,
diff --git a/libide/greeter/ide-greeter-perspective.h b/libide/greeter/ide-greeter-perspective.h
index 122bbd7..d0f86ea 100644
--- a/libide/greeter/ide-greeter-perspective.h
+++ b/libide/greeter/ide-greeter-perspective.h
@@ -30,7 +30,8 @@ G_BEGIN_DECLS
 G_DECLARE_FINAL_TYPE (IdeGreeterPerspective, ide_greeter_perspective, IDE, GREETER_PERSPECTIVE, GtkBin)
 
 void ide_greeter_perspective_show_genesis_view (IdeGreeterPerspective *self,
-                                                const gchar *genesis_addin_name);
+                                                const gchar *genesis_addin_name,
+                                                const gchar *manifest);
 
 G_END_DECLS
 
diff --git a/plugins/flatpak/Makefile.am b/plugins/flatpak/Makefile.am
index 3f31772..935f59f 100644
--- a/plugins/flatpak/Makefile.am
+++ b/plugins/flatpak/Makefile.am
@@ -21,12 +21,26 @@ libflatpak_plugin_la_SOURCES = \
        gbp-flatpak-runner.h \
        gbp-flatpak-application-addin.c \
        gbp-flatpak-application-addin.h \
+       gbp-flatpak-clone-widget.c \
+       gbp-flatpak-clone-widget.h \
+       gbp-flatpak-genesis-addin.c \
+       gbp-flatpak-genesis-addin.h \
        $(NULL)
 
-libflatpak_plugin_la_CFLAGS = $(PLUGIN_CFLAGS) $(FLATPAK_CFLAGS)
-libflatpak_plugin_la_LIBADD = $(FLATPAK_LIBS)
+nodist_libflatpak_plugin_la_SOURCES = \
+       gbp-flatpak-resources.c \
+       gbp-flatpak-resources.h
+
+libflatpak_plugin_la_CFLAGS = $(PLUGIN_CFLAGS) $(FLATPAK_CFLAGS) $(GIT_CFLAGS)
+libflatpak_plugin_la_LIBADD = $(FLATPAK_LIBS) $(GIT_LIBS)
 libflatpak_plugin_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 
+glib_resources_c = gbp-flatpak-resources.c
+glib_resources_h = gbp-flatpak-resources.h
+glib_resources_xml = gbp-flatpak-resources.gresource.xml
+glib_resources_namespace = gbp_flatpak
+include $(top_srcdir)/build/autotools/Makefile.am.gresources
+
 include $(top_srcdir)/plugins/Makefile.plugin
 
 endif
diff --git a/plugins/flatpak/configure.ac b/plugins/flatpak/configure.ac
index d820b50..d21a4d1 100644
--- a/plugins/flatpak/configure.ac
+++ b/plugins/flatpak/configure.ac
@@ -1,4 +1,5 @@
 m4_define([flatpak_required_version], [0.6.9])
+m4_define([ggit_required_version], [0.24.0])
 
 # --enable-flatpak-plugin=yes/no/auto
 AC_ARG_ENABLE([flatpak-plugin],
@@ -18,7 +19,50 @@ AS_IF([test "$enable_flatpak_plugin" != no],[
               AC_MSG_ERROR([--enable-flatpak-plugin requires flatpak >= flatpak_required_version])
        ])
 
-       enable_flatpak_plugin=$have_flatpak
+       PKG_CHECK_MODULES(GIT,
+                         [libgit2-glib-1.0 >= ggit_required_version],
+                         [have_git=yes],
+                         [have_git=no])
+
+       AS_IF([test "$enable_flatpak_plugin" = "yes" && test "$have_git" = "no"],[
+               AC_MSG_ERROR([--enable-flatpak-plugin requires libgit2-glib-1.0 >= ggit_required_version])
+       ])
+
+       AS_IF([test "$have_flatpak" = "yes" && test "$have_git" = "yes"],[
+               [enable_flatpak_plugin=yes]
+       ])
+
+       dnl ***********************************************************************
+       dnl Be extra careful about libgit2-glib requirements
+       dnl ***********************************************************************
+       cflags_save="${CFLAGS}"
+       libs_save="${LIBS}"
+       CFLAGS="${CFLAGS} ${GIT_CFLAGS}"
+       LIBS="${LIBS} ${GIT_LIBS}"
+       # Thread-Safe
+       AC_MSG_CHECKING([for thread-safe support in libgit2])
+       AC_TRY_RUN([
+               #include <libgit2-glib/ggit.h>
+               int
+               main(int argc, const char *argv[])
+               {
+                       ggit_init ();
+                       return ((ggit_get_features() & GGIT_FEATURE_THREADS) != 0) ? 0 : 1;
+               }
+       ],[AC_MSG_RESULT([yes])],[AC_MSG_ERROR([no, please recompile a threadsafe version of libgit2 
(-DTHREADSAFE:BOOL=ON)])])
+       # SSH
+       AC_MSG_CHECKING([for SSH support in libgit2])
+       AC_TRY_RUN([
+               #include <libgit2-glib/ggit.h>
+               int
+               main(int argc, const char *argv[])
+               {
+                       ggit_init ();
+                       return ((ggit_get_features() & GGIT_FEATURE_SSH) != 0) ? 0 : 1;
+               }
+       ],[AC_MSG_RESULT([yes])],[AC_MSG_ERROR([no, please recompile a libgit2 with ssh support])])
+       CFLAGS="${cflags_save}"
+       LIBS="${libs_save}"
 ])
 
 AM_CONDITIONAL(ENABLE_FLATPAK_PLUGIN, [test "x$enable_flatpak_plugin" = "xyes"])
diff --git a/plugins/flatpak/flatpak.plugin b/plugins/flatpak/flatpak.plugin
index 03e199a..4f11f19 100644
--- a/plugins/flatpak/flatpak.plugin
+++ b/plugins/flatpak/flatpak.plugin
@@ -5,3 +5,4 @@ Description=Provides support for building with Flatpak
 Authors=Christian Hergert <christian hergert me>
 Copyright=Copyright © 2016 Christian Hergert
 Builtin=true
+Depends=git-plugin
diff --git a/plugins/flatpak/gbp-flatpak-clone-widget.c b/plugins/flatpak/gbp-flatpak-clone-widget.c
new file mode 100644
index 0000000..8e8decf
--- /dev/null
+++ b/plugins/flatpak/gbp-flatpak-clone-widget.c
@@ -0,0 +1,508 @@
+/* gbp-flatpak-clone-widget.c
+ *
+ * Copyright (C) 2016 Endless Mobile, Inc.
+ *
+ * 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/>.
+ */
+
+#include <glib/gi18n.h>
+#include <libgit2-glib/ggit.h>
+#include <ide.h>
+
+#include "egg-animation.h"
+
+#include "gbp-flatpak-clone-widget.h"
+
+#define ANIMATION_DURATION_MSEC 250
+
+struct _GbpFlatpakCloneWidget
+{
+  GtkBin          parent_instance;
+
+  GtkProgressBar *clone_progress;
+
+  guint           is_ready : 1;
+
+  gchar          *child_name;
+  gchar          *id;
+  gchar          *manifest;
+};
+
+typedef enum {
+  TYPE_GIT,
+  TYPE_ARCHIVE
+} SourceType;
+
+typedef struct
+{
+  SourceType type;
+  IdeVcsUri *uri;
+  gchar     *branch;
+  gchar     *sha;
+} ModuleSource;
+
+typedef struct
+{
+  ModuleSource *src;
+  GFile        *destination;
+  GFile        *project_file;
+} DownloadRequest;
+
+enum {
+  PROP_0,
+  PROP_IS_READY,
+  PROP_MANIFEST,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE (GbpFlatpakCloneWidget, gbp_flatpak_clone_widget, GTK_TYPE_BIN)
+
+static void
+module_source_free (void *data)
+{
+  ModuleSource *src = data;
+
+  g_clear_pointer (&src->uri, ide_vcs_uri_unref);
+  g_free (src->branch);
+  g_free (src->sha);
+  g_slice_free (ModuleSource, src);
+}
+
+static void
+download_request_free (gpointer data)
+{
+  DownloadRequest *req = data;
+
+  module_source_free (req->src);
+  g_clear_object (&req->destination);
+  g_clear_object (&req->project_file);
+  g_slice_free (DownloadRequest, req);
+}
+
+static DownloadRequest *
+download_request_new (ModuleSource *src,
+                      GFile        *destination)
+{
+  DownloadRequest *req;
+
+  g_assert (src);
+  g_assert (destination);
+
+  req = g_slice_new0 (DownloadRequest);
+  req->src = src;
+  req->destination = g_object_ref (destination);
+
+  return req;
+}
+
+static void
+gbp_flatpak_clone_widget_finalize (GObject *object)
+{
+  GbpFlatpakCloneWidget *self = (GbpFlatpakCloneWidget *)object;
+
+  g_clear_pointer (&self->child_name, g_free);
+  g_clear_pointer (&self->id, g_free);
+  g_clear_pointer (&self->manifest, g_free);
+
+  G_OBJECT_CLASS (gbp_flatpak_clone_widget_parent_class)->finalize (object);
+}
+
+static void
+gbp_flatpak_clone_widget_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  GbpFlatpakCloneWidget *self = GBP_FLATPAK_CLONE_WIDGET(object);
+  switch (prop_id)
+    {
+    case PROP_MANIFEST:
+      g_free (self->manifest);
+      self->manifest = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_flatpak_clone_widget_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  GbpFlatpakCloneWidget *self = GBP_FLATPAK_CLONE_WIDGET(object);
+
+  switch (prop_id)
+    {
+    case PROP_IS_READY:
+      g_value_set_boolean (value, self->is_ready);
+      break;
+
+    case PROP_MANIFEST:
+      g_value_set_string (value, self->manifest);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_flatpak_clone_widget_class_init (GbpFlatpakCloneWidgetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gbp_flatpak_clone_widget_finalize;
+  object_class->get_property = gbp_flatpak_clone_widget_get_property;
+  object_class->set_property = gbp_flatpak_clone_widget_set_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_IS_READY,
+                                   g_param_spec_boolean ("is-ready",
+                                                         "Is Ready",
+                                                         "If the widget is ready to continue.",
+                                                         FALSE,
+                                                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
+
+  g_object_class_install_property (object_class,
+                                   PROP_MANIFEST,
+                                   g_param_spec_string ("manifest",
+                                                        "Manifest",
+                                                        "Name of the flatpak manifest to load.",
+                                                        NULL,
+                                                        (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+
+  gtk_widget_class_set_css_name (widget_class, "flatpakclonewidget");
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/flatpak-plugin/gbp-flatpak-clone-widget.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpFlatpakCloneWidget, clone_progress);
+}
+
+static void
+gbp_flatpak_clone_widget_init (GbpFlatpakCloneWidget *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+ide_workbench_open_project_async_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  GbpFlatpakCloneWidget *self = user_data;
+  IdeWorkbench *workbench = IDE_WORKBENCH (object);
+  IdeConfigurationManager *configmgr;
+  IdeConfiguration *config;
+  IdeContext *context;
+  IdeContext *config_context;
+  IdeRuntimeManager *runtime_manager;
+
+  g_assert (IDE_IS_WORKBENCH (workbench));
+  g_assert (GBP_IS_FLATPAK_CLONE_WIDGET (self));
+
+  context = ide_workbench_get_context (IDE_WORKBENCH (workbench));
+  configmgr = ide_context_get_configuration_manager (context);
+  config = ide_configuration_manager_get_current (configmgr);
+
+  config_context = ide_object_get_context (IDE_OBJECT (config));
+  runtime_manager = ide_context_get_runtime_manager (config_context);
+
+  if (g_list_model_get_n_items (G_LIST_MODEL (runtime_manager)) > 0)
+    {
+      g_autoptr(IdeRuntime) last =
+        g_list_model_get_item (G_LIST_MODEL (runtime_manager),
+                               g_list_model_get_n_items (G_LIST_MODEL (runtime_manager)) - 1);
+      ide_configuration_set_runtime_id (config, ide_runtime_get_id (last));
+    }
+}
+
+static gboolean
+open_after_timeout (gpointer user_data)
+{
+  g_autoptr(GTask) task = user_data;
+  DownloadRequest *req;
+  GbpFlatpakCloneWidget *self;
+  IdeWorkbench *workbench;
+
+  IDE_ENTRY;
+
+  req = g_task_get_task_data (task);
+  self = g_task_get_source_object (task);
+  g_assert (GBP_IS_FLATPAK_CLONE_WIDGET (self));
+
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  ide_workbench_open_project_async (workbench, req->project_file, NULL,
+                                    ide_workbench_open_project_async_cb, g_object_ref (self));
+
+  IDE_RETURN (G_SOURCE_REMOVE);
+}
+
+static void
+gbp_flatpak_clone_widget_worker_completed (GTask      *task,
+                                           GParamSpec *pspec,
+                                           gpointer    user_data)
+{
+  GbpFlatpakCloneWidget *self = user_data;
+
+  if (!g_task_get_completed (task))
+    return;
+
+  egg_object_animate_full (self->clone_progress,
+                           EGG_ANIMATION_EASE_IN_OUT_QUAD,
+                           ANIMATION_DURATION_MSEC,
+                           NULL,
+                           (GDestroyNotify)ide_widget_hide_with_fade,
+                           self->clone_progress,
+                           "fraction", 1.0,
+                           NULL);
+
+  /* Wait for a second so animations can complete before opening
+   * the project. Otherwise, it's pretty jarring to the user.
+   */
+  g_timeout_add (ANIMATION_DURATION_MSEC, open_after_timeout, g_object_ref (task));
+}
+
+static void
+gbp_flatpak_clone_widget_worker (GTask        *task,
+                                 gpointer      source_object,
+                                 gpointer      task_data,
+                                 GCancellable *cancellable)
+{
+  GbpFlatpakCloneWidget *self = source_object;
+  DownloadRequest *req = task_data;
+  g_autofree gchar *uristr = NULL;
+  GgitFetchOptions *fetch_options;
+  g_autoptr(GgitCloneOptions) clone_options = NULL;
+  g_autoptr(GgitRemoteCallbacks) callbacks = NULL;
+  g_autoptr(GgitRepository) repository = NULL;
+  g_autoptr(IdeProgress) progress = NULL;
+  g_autoptr(GFile) src = NULL;
+  g_autoptr(GFile) dst = NULL;
+  GError *error = NULL;
+  GType git_callbacks_type;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GBP_IS_FLATPAK_CLONE_WIDGET (self));
+  g_assert (req != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  /* First, try to open an existing repository at this path */
+  repository = ggit_repository_open (req->destination, &error);
+
+  /* Ignore errors when the repository is not found, but fail the
+   * task otherwise.
+   */
+  if (repository == NULL &&
+      !g_error_matches (error, GGIT_ERROR, GGIT_ERROR_NOTFOUND))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_clear_error (&error);
+
+  if (repository == NULL)
+    {
+      /* HACK: we don't want libide to depend on libgit2 just yet, so for
+       * now, we just lookup the GType of the object we need from the git
+       * plugin by name.
+       */
+      git_callbacks_type = g_type_from_name ("IdeGitRemoteCallbacks");
+      g_assert (git_callbacks_type != 0);
+
+      callbacks = g_object_new (git_callbacks_type, NULL);
+      g_object_get (callbacks, "progress", &progress, NULL);
+      g_object_bind_property (progress, "fraction", self->clone_progress, "fraction", 0);
+
+      fetch_options = ggit_fetch_options_new ();
+      ggit_fetch_options_set_remote_callbacks (fetch_options, callbacks);
+
+      clone_options = ggit_clone_options_new ();
+      ggit_clone_options_set_is_bare (clone_options, FALSE);
+      ggit_clone_options_set_checkout_branch (clone_options, req->src->branch);
+      ggit_clone_options_set_fetch_options (clone_options, fetch_options);
+      g_clear_pointer (&fetch_options, ggit_fetch_options_free);
+
+      uristr = ide_vcs_uri_to_string (req->src->uri);
+      ggit_repository_clone (uristr, req->destination, clone_options, &error);
+      if (repository == NULL)
+        {
+          g_task_return_error (task, error);
+          return;
+        }
+    }
+
+  req->project_file = ggit_repository_get_workdir (repository);
+
+  /* copy manifest into the source directory */
+  src = g_file_new_for_path (self->manifest);
+  dst = g_file_get_child (req->project_file,
+                          g_strjoin (".", self->id, "json", NULL));
+  g_clear_pointer (&self->id, g_free);
+  if (!g_file_copy (src, dst, G_FILE_COPY_OVERWRITE, NULL,
+                    NULL, NULL, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static ModuleSource *
+get_source (GbpFlatpakCloneWidget  *self,
+            GError                **error)
+{
+  g_autoptr(JsonParser) parser = NULL;
+  JsonNode *root_node;
+  JsonObject *root_object;
+  JsonObject *app_object;
+  JsonArray *modules = NULL;
+  JsonArray *sources = NULL;
+  guint num_modules;
+  ModuleSource *src;
+  g_autoptr(IdeVcsUri) uri = NULL;
+
+  parser = json_parser_new ();
+  if (!json_parser_load_from_file (parser, self->manifest, error))
+    return NULL;
+
+  root_node = json_parser_get_root (parser);
+  root_object = json_node_get_object (root_node);
+
+  self->id = g_strdup (json_object_get_string_member (root_object, "app-id"));
+
+  modules = json_object_get_array_member (root_object, "modules");
+  num_modules = json_array_get_length (modules);
+
+  /* guess that the primary module is always the last one */
+  app_object = json_array_get_object_element (modules, num_modules - 1);
+  sources = json_object_get_array_member (app_object, "sources");
+
+  for (guint i = 0; i < json_array_get_length (sources); i++)
+    {
+      JsonNode *source;
+      JsonObject *source_object;
+      const gchar *url;
+
+      source = json_array_get_element (sources, i);
+      source_object = json_node_get_object (source);
+      src = g_slice_new0 (ModuleSource);
+
+      if (g_strcmp0 (json_object_get_string_member (source_object, "type"), "git") == 0)
+        {
+          src->type = TYPE_GIT;
+          if (json_object_has_member (source_object, "branch"))
+            src->branch = g_strdup (json_object_get_string_member (source_object, "branch"));
+        }
+
+      url = json_object_get_string_member (source_object, "url");
+      src->uri = ide_vcs_uri_new (url);
+    }
+
+  return src;
+}
+
+void
+gbp_flatpak_clone_widget_clone_async (GbpFlatpakCloneWidget   *self,
+                                      GCancellable            *cancellable,
+                                      GAsyncReadyCallback      callback,
+                                      gpointer                 user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GSettings) settings = NULL;
+  g_autoptr(GFile) destination = NULL;
+  g_autofree gchar *path = NULL;
+  g_autofree gchar *projects_dir = NULL;
+  DownloadRequest *req;
+  ModuleSource *src;
+  GError *error = NULL;
+
+  g_return_if_fail (GBP_IS_FLATPAK_CLONE_WIDGET (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  src = get_source (self, &error);
+  if (src == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  if (src->uri != NULL)
+    {
+      const gchar *uri_path;
+      gchar *name = NULL;
+
+      uri_path = ide_vcs_uri_get_path (src->uri);
+      if (uri_path != NULL)
+        {
+          name = g_path_get_basename (uri_path);
+
+          if (g_str_has_suffix (name, ".git"))
+            *(strrchr (name, '.')) = '\0';
+
+          if (!g_str_equal (name, "/"))
+            {
+              g_free (self->child_name);
+              self->child_name = g_steal_pointer (&name);
+            }
+
+          g_free (name);
+        }
+    }
+
+  settings = g_settings_new ("org.gnome.builder");
+  path = g_settings_get_string (settings, "projects-directory");
+
+  if (ide_str_empty0 (path))
+    path = g_build_filename (g_get_home_dir (), "Projects", NULL);
+
+  if (!g_path_is_absolute (path))
+    projects_dir = g_build_filename (g_get_home_dir (), path, NULL);
+  else
+    projects_dir = g_steal_pointer (&path);
+
+  destination = g_file_new_for_path (projects_dir);
+
+  if (self->child_name)
+    {
+      g_autoptr(GFile) child = g_file_get_child (destination, self->child_name);
+      g_set_object (&destination, child);
+    }
+
+  req = download_request_new (src, destination);
+
+  g_task_set_task_data (task, req, download_request_free);
+  g_task_run_in_thread (task, gbp_flatpak_clone_widget_worker);
+
+  g_signal_connect (task, "notify::completed",
+                    G_CALLBACK (gbp_flatpak_clone_widget_worker_completed), self);
+}
+
+gboolean
+gbp_flatpak_clone_widget_clone_finish (GbpFlatpakCloneWidget *self,
+                                       GAsyncResult          *result,
+                                       GError               **error)
+{
+  g_return_val_if_fail (GBP_IS_FLATPAK_CLONE_WIDGET (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/plugins/flatpak/gbp-flatpak-clone-widget.h b/plugins/flatpak/gbp-flatpak-clone-widget.h
new file mode 100644
index 0000000..3b29155
--- /dev/null
+++ b/plugins/flatpak/gbp-flatpak-clone-widget.h
@@ -0,0 +1,40 @@
+/* gbp-flatpak-clone-widget.h
+ *
+ * Copyright (C) 2016 Endless Mobile, Inc.
+ *
+ * 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/>.
+ */
+
+#ifndef GBP_FLATPAK_CLONE_WIDGET_H
+#define GBP_FLATPAK_CLONE_WIDGET_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_FLATPAK_CLONE_WIDGET (gbp_flatpak_clone_widget_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpFlatpakCloneWidget, gbp_flatpak_clone_widget, GBP, FLATPAK_CLONE_WIDGET, GtkBin)
+
+void     gbp_flatpak_clone_widget_clone_async  (GbpFlatpakCloneWidget *self,
+                                                GCancellable          *cancellable,
+                                                GAsyncReadyCallback    callback,
+                                                gpointer               user_data);
+gboolean gbp_flatpak_clone_widget_clone_finish (GbpFlatpakCloneWidget *self,
+                                                GAsyncResult          *result,
+                                                GError               **error);
+
+G_END_DECLS
+
+#endif /* GBP_FLATPAK_CLONE_WIDGET_H */
diff --git a/plugins/flatpak/gbp-flatpak-clone-widget.ui b/plugins/flatpak/gbp-flatpak-clone-widget.ui
new file mode 100644
index 0000000..6d7baa9
--- /dev/null
+++ b/plugins/flatpak/gbp-flatpak-clone-widget.ui
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="GbpFlatpakCloneWidget" parent="GtkBin">
+    <child>
+      <object class="GtkOverlay" id="page_clone_remote">
+        <property name="visible">true</property>
+        <child type="overlay">
+          <object class="GtkProgressBar" id="clone_progress">
+            <property name="valign">start</property>
+            <property name="fraction">0.0</property>
+            <property name="visible">true</property>
+            <style>
+              <class name="osd"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <property name="valign">center</property>
+            <property name="vexpand">true</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkImage">
+                <property name="icon-name">document-save-symbolic</property>
+                <property name="pixel-size">128</property>
+                <property name="visible">true</property>
+                <property name="margin">12</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <property name="label" translatable="yes">Downloading application sources...</property>
+                <property name="margin-bottom">24</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/plugins/flatpak/gbp-flatpak-genesis-addin.c b/plugins/flatpak/gbp-flatpak-genesis-addin.c
new file mode 100644
index 0000000..4656a49
--- /dev/null
+++ b/plugins/flatpak/gbp-flatpak-genesis-addin.c
@@ -0,0 +1,204 @@
+/* gbp-flatpak-genesis-addin.c
+ *
+ * Copyright (C) 2016 Endless
+ *
+ * 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/>.
+ */
+
+#include <glib/gi18n.h>
+#include <ide.h>
+
+#include "gbp-flatpak-clone-widget.h"
+#include "gbp-flatpak-genesis-addin.h"
+
+struct _GbpFlatpakGenesisAddin
+{
+  GObject                parent_instance;
+  GbpFlatpakCloneWidget *clone_widget;
+};
+
+static void genesis_addin_iface_init (IdeGenesisAddinInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GbpFlatpakGenesisAddin, gbp_flatpak_genesis_addin, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_GENESIS_ADDIN, genesis_addin_iface_init))
+
+enum {
+  PROP_0,
+  PROP_IS_READY
+};
+
+static void
+gbp_flatpak_genesis_addin_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+  GbpFlatpakGenesisAddin *self = GBP_FLATPAK_GENESIS_ADDIN(object);
+
+  switch (prop_id)
+    {
+    case PROP_IS_READY:
+      if (self->clone_widget != NULL)
+        g_object_get_property (G_OBJECT (self->clone_widget), "is-ready", value);
+      else
+        g_value_set_boolean (value, FALSE);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_flatpak_genesis_addin_class_init (GbpFlatpakGenesisAddinClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = gbp_flatpak_genesis_addin_get_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_IS_READY,
+                                   g_param_spec_boolean ("is-ready",
+                                                         "Is Ready",
+                                                         "If the widget is ready to continue.",
+                                                         FALSE,
+                                                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
+}
+
+static void
+gbp_flatpak_genesis_addin_init (GbpFlatpakGenesisAddin *self)
+{
+}
+
+static gchar *
+gbp_flatpak_genesis_addin_get_icon_name (IdeGenesisAddin *addin)
+{
+  return g_strdup ("gitg-symbolic");
+}
+
+static gchar *
+gbp_flatpak_genesis_addin_get_title (IdeGenesisAddin *addin)
+{
+  return g_strdup (_("Clone App"));
+}
+
+static void
+widget_is_ready (GtkWidget              *widget,
+                 GParamSpec             *pspec,
+                 GbpFlatpakGenesisAddin *self)
+{
+  g_assert (GBP_IS_FLATPAK_GENESIS_ADDIN (self));
+
+  g_object_notify (G_OBJECT (self), "is-ready");
+}
+
+static GtkWidget *
+gbp_flatpak_genesis_addin_get_widget (IdeGenesisAddin *addin)
+{
+  GbpFlatpakGenesisAddin *self = (GbpFlatpakGenesisAddin *)addin;
+
+  g_assert (GBP_IS_FLATPAK_GENESIS_ADDIN (self));
+
+  if (self->clone_widget == NULL)
+    {
+      self->clone_widget = g_object_new (GBP_TYPE_FLATPAK_CLONE_WIDGET,
+                                         "visible", TRUE,
+                                         NULL);
+      g_signal_connect (self->clone_widget,
+                        "notify::is-ready",
+                        G_CALLBACK (widget_is_ready),
+                        self);
+    }
+
+  return GTK_WIDGET (self->clone_widget);
+}
+
+static void
+gbp_flatpak_genesis_addin_run_cb (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  GbpFlatpakCloneWidget *widget = (GbpFlatpakCloneWidget *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GBP_IS_FLATPAK_CLONE_WIDGET (widget));
+
+  if (!gbp_flatpak_clone_widget_clone_finish (widget, result, &error))
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_flatpak_genesis_addin_run_async (IdeGenesisAddin     *addin,
+                                     GCancellable        *cancellable,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             user_data)
+{
+  GbpFlatpakGenesisAddin *self = (GbpFlatpakGenesisAddin *)addin;
+  GTask *task;
+
+  g_return_if_fail (GBP_IS_FLATPAK_GENESIS_ADDIN (addin));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  gbp_flatpak_clone_widget_clone_async (self->clone_widget,
+                                        cancellable,
+                                        gbp_flatpak_genesis_addin_run_cb,
+                                        task);
+}
+
+static gboolean
+gbp_flatpak_genesis_addin_run_finish (IdeGenesisAddin  *addin,
+                                      GAsyncResult     *result,
+                                      GError          **error)
+{
+  g_return_val_if_fail (GBP_IS_FLATPAK_GENESIS_ADDIN (addin), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gint
+gbp_flatpak_genesis_addin_get_priority (IdeGenesisAddin *addin)
+{
+  return 100;
+}
+
+static gchar *
+gbp_flatpak_genesis_addin_get_label (IdeGenesisAddin *addin)
+{
+  return NULL;
+}
+
+static gchar *
+gbp_flatpak_genesis_addin_get_next_label (IdeGenesisAddin *addin)
+{
+  return g_strdup (_("Clone"));
+}
+
+static void
+genesis_addin_iface_init (IdeGenesisAddinInterface *iface)
+{
+  iface->get_title = gbp_flatpak_genesis_addin_get_title;
+  iface->get_icon_name = gbp_flatpak_genesis_addin_get_icon_name;
+  iface->get_widget = gbp_flatpak_genesis_addin_get_widget;
+  iface->run_async = gbp_flatpak_genesis_addin_run_async;
+  iface->run_finish = gbp_flatpak_genesis_addin_run_finish;
+  iface->get_priority = gbp_flatpak_genesis_addin_get_priority;
+  iface->get_label = gbp_flatpak_genesis_addin_get_label;
+  iface->get_next_label = gbp_flatpak_genesis_addin_get_next_label;
+}
diff --git a/plugins/flatpak/gbp-flatpak-genesis-addin.h b/plugins/flatpak/gbp-flatpak-genesis-addin.h
new file mode 100644
index 0000000..c812e46
--- /dev/null
+++ b/plugins/flatpak/gbp-flatpak-genesis-addin.h
@@ -0,0 +1,32 @@
+/* gbp-flatpak-genesis-addin.h
+ *
+ * Copyright (C) 2016 Endless
+ *
+ * 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/>.
+ */
+
+#ifndef GBP_FLATPAK_GENESIS_ADDIN_H
+#define GBP_FLATPAK_GENESIS_ADDIN_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_FLATPAK_GENESIS_ADDIN (gbp_flatpak_genesis_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpFlatpakGenesisAddin, gbp_flatpak_genesis_addin, GBP, FLATPAK_GENESIS_ADDIN, GObject)
+
+G_END_DECLS
+
+#endif /* GBP_FLATPAK_GENESIS_ADDIN_H */
diff --git a/plugins/flatpak/gbp-flatpak-plugin.c b/plugins/flatpak/gbp-flatpak-plugin.c
index 2ea41ab..fb9bb72 100644
--- a/plugins/flatpak/gbp-flatpak-plugin.c
+++ b/plugins/flatpak/gbp-flatpak-plugin.c
@@ -21,6 +21,7 @@
 
 #include "gbp-flatpak-runtime-provider.h"
 #include "gbp-flatpak-application-addin.h"
+#include "gbp-flatpak-genesis-addin.h"
 
 void
 peas_register_types (PeasObjectModule *module)
@@ -31,4 +32,7 @@ peas_register_types (PeasObjectModule *module)
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_APPLICATION_ADDIN,
                                               GBP_TYPE_FLATPAK_APPLICATION_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_GENESIS_ADDIN,
+                                              GBP_TYPE_FLATPAK_GENESIS_ADDIN);
 }
diff --git a/plugins/flatpak/gbp-flatpak-resources.gresource.xml 
b/plugins/flatpak/gbp-flatpak-resources.gresource.xml
new file mode 100644
index 0000000..52c931d
--- /dev/null
+++ b/plugins/flatpak/gbp-flatpak-resources.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/builder/plugins/flatpak-plugin">
+    <file>gbp-flatpak-clone-widget.ui</file>
+  </gresource>
+</gresources>
diff --git a/plugins/git/ide-git-plugin.c b/plugins/git/ide-git-plugin.c
index e8b5c6a..a33b0fb 100644
--- a/plugins/git/ide-git-plugin.c
+++ b/plugins/git/ide-git-plugin.c
@@ -20,6 +20,7 @@
 #include <ide.h>
 
 #include "ide-git-genesis-addin.h"
+#include "ide-git-remote-callbacks.h"
 #include "ide-git-vcs.h"
 #include "ide-git-vcs-config.h"
 #include "ide-git-vcs-initializer.h"
@@ -54,6 +55,11 @@ peas_register_types (PeasObjectModule *module)
 {
   if (register_ggit ())
     {
+      /* HACK: we load this type by name from the flatpak plugin, so make
+       * sure it exists.
+       */
+      g_type_ensure (IDE_TYPE_GIT_REMOTE_CALLBACKS);
+
       peas_object_module_register_extension_type (module,
                                                   IDE_TYPE_VCS,
                                                   IDE_TYPE_GIT_VCS);
diff --git a/plugins/git/ide-git-remote-callbacks.c b/plugins/git/ide-git-remote-callbacks.c
index 6df1dc7..e611178 100644
--- a/plugins/git/ide-git-remote-callbacks.c
+++ b/plugins/git/ide-git-remote-callbacks.c
@@ -41,6 +41,7 @@ G_DEFINE_TYPE (IdeGitRemoteCallbacks, ide_git_remote_callbacks, GGIT_TYPE_REMOTE
 enum {
   PROP_0,
   PROP_FRACTION,
+  PROP_PROGRESS,
   LAST_PROP
 };
 
@@ -202,6 +203,10 @@ ide_git_remote_callbacks_get_property (GObject    *object,
       g_value_set_double (value, ide_git_remote_callbacks_get_fraction (self));
       break;
 
+    case PROP_PROGRESS:
+      g_value_set_object (value, ide_git_remote_callbacks_get_progress (self));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -227,6 +232,12 @@ ide_git_remote_callbacks_class_init (IdeGitRemoteCallbacksClass *klass)
                          1.0,
                          0.0,
                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  properties [PROP_PROGRESS] =
+    g_param_spec_object ("progress",
+                         "Progress",
+                         "An IdeProgress instance containing the operation progress.",
+                         IDE_TYPE_PROGRESS,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, LAST_PROP, properties);
 }



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