[gnome-builder/wip/project-selector: 42/56] project-selector: add basic clone support with libgit2-glib



commit 97c3537af7fb0b7c6a35980265ea8709c8dec4de
Author: Christian Hergert <christian hergert me>
Date:   Fri Apr 3 00:31:10 2015 -0700

    project-selector: add basic clone support with libgit2-glib
    
    We still need a bunch of work on the callbacks, but as a proof of concept
    this works. You need to select an empty directory, which I'm not happy
    about. I'd like to take the repository name in the path and add that to
    the suffix of the directory path.
    
    We could also do something like:
    
      [ URL ]
      [ Name ]
      [ Directory ]
    
    and then modify Directory filechooserbutton when Name changes. This would
    allow for specific overrides.

 data/ui/gb-new-project-dialog.ui    |  105 +++++++++++--------
 src/dialogs/gb-new-project-dialog.c |  193 ++++++++++++++++++++++++++++++++++-
 2 files changed, 252 insertions(+), 46 deletions(-)
---
diff --git a/data/ui/gb-new-project-dialog.ui b/data/ui/gb-new-project-dialog.ui
index d558462..fc3925a 100644
--- a/data/ui/gb-new-project-dialog.ui
+++ b/data/ui/gb-new-project-dialog.ui
@@ -281,64 +281,79 @@
           </object>
         </child>
         <child>
-          <object class="GtkBox" id="page_clone_remote">
-            <property name="orientation">horizontal</property>
-            <property name="expand">true</property>
-            <property name="spacing">12</property>
+          <object class="GtkOverlay" id="page_clone_remote">
             <property name="visible">true</property>
-            <child type="center">
-              <object class="GtkBox">
-                <property name="orientation">vertical</property>
-                <property name="spacing">12</property>
-                <property name="halign">center</property>
-                <property name="hexpand">false</property>
-                <property name="valign">center</property>
-                <property name="vexpand">false</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>
-                <child>
-                  <object class="GtkEntry" id="clone_uri_entry">
-                    <property name="hexpand">true</property>
-                    <property name="width-chars">35</property>
-                    <property name="placeholder-text" translatable="yes">ssh://user 
host:repository.git</property>
-                    <property name="visible">true</property>
-                  </object>
-                </child>
-                <child>
-                  <object class="GtkFileChooserButton" id="clone_location_button">
-                    <property name="action">select-folder</property>
-                    <property name="hexpand">true</property>
-                    <property name="visible">true</property>
-                  </object>
-                </child>
+                <style>
+                  <class name="osd"/>
+                </style>
               </object>
             </child>
             <child>
               <object class="GtkBox">
-                <property name="orientation">vertical</property>
+                <property name="orientation">horizontal</property>
+                <property name="expand">true</property>
                 <property name="spacing">12</property>
-                <property name="hexpand">true</property>
-                <property name="vexpand">true</property>
-                <property name="valign">center</property>
-                <property name="halign">end</property>
                 <property name="visible">true</property>
-                <child>
-                  <object class="GtkLabel" id="clone_uri_label">
-                    <property name="label" translatable="yes">Repository URL</property>
+                <child type="center">
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">12</property>
+                    <property name="halign">center</property>
+                    <property name="hexpand">false</property>
+                    <property name="valign">center</property>
+                    <property name="vexpand">false</property>
                     <property name="visible">true</property>
-                    <property name="xalign">1.0</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
+                    <child>
+                      <object class="GtkEntry" id="clone_uri_entry">
+                        <property name="hexpand">true</property>
+                        <property name="width-chars">35</property>
+                        <property name="placeholder-text" translatable="yes">ssh://user 
host:repository.git</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkFileChooserButton" id="clone_location_button">
+                        <property name="action">select-folder</property>
+                        <property name="hexpand">true</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
                   </object>
                 </child>
                 <child>
-                  <object class="GtkLabel" id="clone_location_label">
-                    <property name="label" translatable="yes">Location</property>
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">12</property>
+                    <property name="hexpand">true</property>
+                    <property name="vexpand">true</property>
+                    <property name="valign">center</property>
+                    <property name="halign">end</property>
                     <property name="visible">true</property>
-                    <property name="xalign">1.0</property>
-                    <style>
-                      <class name="dim-label"/>
-                    </style>
+                    <child>
+                      <object class="GtkLabel" id="clone_uri_label">
+                        <property name="label" translatable="yes">Repository URL</property>
+                        <property name="visible">true</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="clone_location_label">
+                        <property name="label" translatable="yes">Location</property>
+                        <property name="visible">true</property>
+                        <property name="xalign">1.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
diff --git a/src/dialogs/gb-new-project-dialog.c b/src/dialogs/gb-new-project-dialog.c
index 946e0f4..16c57f6 100644
--- a/src/dialogs/gb-new-project-dialog.c
+++ b/src/dialogs/gb-new-project-dialog.c
@@ -17,17 +17,25 @@
  */
 
 #include <glib/gi18n.h>
+#include <libgit2-glib/ggit.h>
 
 #include "gb-new-project-dialog.h"
+#include "gb-string.h"
 #include "gb-widget.h"
 
+#define ANIMATION_DURATION_MSEC 250
+
 struct _GbNewProjectDialog
 {
   GtkDialog             parent_instance;
 
+  gdouble               progress_fraction;
+
   GtkButton            *back_button;
   GtkButton            *cancel_button;
   GtkFileChooserWidget *clone_location_button;
+  GtkProgressBar       *clone_progress;
+  GtkEntry             *clone_uri_entry;
   GtkButton            *create_button;
   GtkFileChooserWidget *file_chooser;
   GtkHeaderBar         *header_bar;
@@ -39,6 +47,12 @@ struct _GbNewProjectDialog
   GtkStack             *stack;
 };
 
+typedef struct
+{
+  gchar *uri;
+  GFile *location;
+} CloneRequest;
+
 G_DEFINE_TYPE (GbNewProjectDialog, gb_new_project_dialog, GTK_TYPE_WINDOW)
 
 enum {
@@ -57,6 +71,35 @@ static GParamSpec *gParamSpecs [LAST_PROP];
 static guint gSignals [LAST_SIGNAL];
 
 static void
+clone_request_free (gpointer data)
+{
+  CloneRequest *req = data;
+
+  if (req)
+    {
+      g_free (req->uri);
+      g_clear_object (&req->location);
+      g_free (req);
+    }
+}
+
+static CloneRequest *
+clone_request_new (const gchar *uri,
+                   GFile       *location)
+{
+  CloneRequest *req;
+
+  g_assert (uri);
+  g_assert (location);
+
+  req = g_new0 (CloneRequest, 1);
+  req->uri = g_strdup (uri);
+  req->location = g_object_ref (location);
+
+  return req;
+}
+
+static void
 gb_new_project_dialog_back (GbNewProjectDialog *self)
 {
   GtkWidget *child;
@@ -72,6 +115,127 @@ gb_new_project_dialog_back (GbNewProjectDialog *self)
 }
 
 static void
+gb_new_project_dialog__clone_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  GbNewProjectDialog *self = (GbNewProjectDialog *)object;
+  g_autoptr(GFile) file = NULL;
+  GTask *task = (GTask *)result;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  file = g_task_propagate_pointer (task, &error);
+
+  if (file == NULL)
+    g_warning ("%s", error->message);
+  else
+    g_signal_emit (self, gSignals [OPEN_PROJECT], 0, file);
+}
+
+static gboolean
+update_progress_cb (gpointer data)
+{
+  g_autoptr(GbNewProjectDialog) self = data;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  ide_object_animate (self->clone_progress,
+                      IDE_ANIMATION_EASE_IN_OUT_QUAD,
+                      ANIMATION_DURATION_MSEC,
+                      NULL,
+                      "fraction", self->progress_fraction,
+                      NULL);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+transfer_progress_cb (GgitRemoteCallbacks  *callbacks,
+                      GgitTransferProgress *stats,
+                      gpointer              user_data)
+{
+  GbNewProjectDialog *self = user_data;
+  guint total;
+  guint received;
+
+  g_assert (GGIT_IS_REMOTE_CALLBACKS (callbacks));
+  g_assert (stats != NULL);
+
+  total = ggit_transfer_progress_get_total_objects (stats);
+  received = ggit_transfer_progress_get_received_objects (stats);
+  if (total == 0)
+    return;
+
+  self->progress_fraction = (gdouble)received / (gdouble)total;
+
+  g_timeout_add (0, update_progress_cb, g_object_ref (self));
+}
+
+static void
+gb_new_project_dialog__clone_worker (GTask        *task,
+                                     gpointer      source_object,
+                                     gpointer      task_data,
+                                     GCancellable *cancellable)
+{
+  GbNewProjectDialog *self = source_object;
+  GgitRepository *repository;
+  g_autoptr(GFile) workdir = NULL;
+  CloneRequest *req = task_data;
+  GgitCloneOptions *clone_options;
+  GgitRemoteCallbacks *callbacks;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (source_object));
+  g_assert (req != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  clone_options = ggit_clone_options_new ();
+
+  ggit_clone_options_set_is_bare (clone_options, FALSE);
+  ggit_clone_options_set_checkout_branch (clone_options, "master");
+
+  callbacks = g_object_new (GGIT_TYPE_REMOTE_CALLBACKS, NULL);
+  g_signal_connect (callbacks, "transfer-progress", G_CALLBACK (transfer_progress_cb), self);
+  ggit_clone_options_set_remote_callbacks (clone_options, callbacks);
+
+  repository = ggit_repository_clone (req->uri, req->location, clone_options, &error);
+
+  g_object_unref (callbacks);
+
+  if (repository == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  workdir = ggit_repository_get_workdir (repository);
+  g_task_return_pointer (task, g_object_ref (workdir), g_object_unref);
+
+  g_object_unref (repository);
+}
+
+static void
+gb_new_project_dialog_begin_clone (GbNewProjectDialog *self)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GFile) location = NULL;
+  CloneRequest *req;
+  const gchar *uri;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+
+  uri = gtk_entry_get_text (self->clone_uri_entry);
+  location = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (self->clone_location_button));
+  req = clone_request_new (uri, location);
+  task = g_task_new (self, NULL, gb_new_project_dialog__clone_cb, self);
+  g_task_set_task_data (task, req, clone_request_free);
+  g_task_run_in_thread (task, gb_new_project_dialog__clone_worker);
+}
+
+static void
 gb_new_project_dialog__back_button_clicked (GbNewProjectDialog *self,
                                             GtkButton          *back_button)
 {
@@ -110,6 +274,10 @@ gb_new_project_dialog__create_button_clicked (GbNewProjectDialog *self,
       if (file != NULL)
         g_signal_emit (self, gSignals [OPEN_PROJECT], 0, file);
     }
+  else if (visible_child == GTK_WIDGET (self->page_clone_remote))
+    {
+      gb_new_project_dialog_begin_clone (self);
+    }
 }
 
 static void
@@ -246,6 +414,22 @@ gb_new_project_dialog__open_list_box_header_func (GtkListBoxRow *row,
 }
 
 static void
+gb_new_project_dialog__clone_uri_entry_changed (GbNewProjectDialog *self,
+                                                GtkEntry           *entry)
+{
+  const gchar *text;
+  gboolean sensitive;
+
+  g_assert (GB_IS_NEW_PROJECT_DIALOG (self));
+  g_assert (GTK_IS_ENTRY (entry));
+
+  text = gtk_entry_get_text (entry);
+  sensitive = !gb_str_empty0 (text);
+
+  gtk_widget_set_sensitive (GTK_WIDGET (self->create_button), sensitive);
+}
+
+static void
 gb_new_project_dialog_finalize (GObject *object)
 {
   GbNewProjectDialog *self = (GbNewProjectDialog *)object;
@@ -337,6 +521,8 @@ gb_new_project_dialog_class_init (GbNewProjectDialogClass *klass)
   GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, back_button);
   GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, cancel_button);
   GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_location_button);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_progress);
+  GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, clone_uri_entry);
   GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, create_button);
   GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, file_chooser);
   GB_WIDGET_CLASS_BIND (klass, GbNewProjectDialog, header_bar);
@@ -352,7 +538,6 @@ static void
 gb_new_project_dialog_init (GbNewProjectDialog *self)
 {
   g_autofree gchar *path = NULL;
-  GFile *location;
   GList *iter;
   GList *filters;
 
@@ -381,6 +566,12 @@ gb_new_project_dialog_init (GbNewProjectDialog *self)
                            self,
                            G_CONNECT_SWAPPED);
 
+  g_signal_connect_object (self->clone_uri_entry,
+                           "changed",
+                           G_CALLBACK (gb_new_project_dialog__clone_uri_entry_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
   g_signal_connect_object (self->create_button,
                            "clicked",
                            G_CALLBACK (gb_new_project_dialog__create_button_clicked),


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