[gnome-builder] libide: setup git vcs to be reloadable



commit 6fef90d05d90114978151be9b15e5738a9289c99
Author: Christian Hergert <christian hergert me>
Date:   Thu Feb 26 19:47:11 2015 -0800

    libide: setup git vcs to be reloadable
    
    This is the first step to breaking out the git vcs into a series of
    mini async operations. This helps us move forward towards being able
    to reload the vcs at runtime.
    
    One of the next major hurdles to get over is how we will apply changes to
    the project tree. This simply adds the new tree to the existing tree,
    which means duplicated content.
    
    Long term, we will need to signal add/removals to the tree so that items
    like a project pane can reload their content.

 libide/git/ide-git-vcs.c |  555 ++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 461 insertions(+), 94 deletions(-)
---
diff --git a/libide/git/ide-git-vcs.c b/libide/git/ide-git-vcs.c
index 557889a..56b8df1 100644
--- a/libide/git/ide-git-vcs.c
+++ b/libide/git/ide-git-vcs.c
@@ -19,6 +19,7 @@
 #include <glib/gi18n.h>
 #include <libgit2-glib/ggit.h>
 
+#include "ide-async-helper.h"
 #include "ide-context.h"
 #include "ide-git-buffer-change-monitor.h"
 #include "ide-git-vcs.h"
@@ -26,6 +27,8 @@
 #include "ide-project-file.h"
 #include "ide-project-files.h"
 
+#define DEFAULT_CHANGED_TIMEOUT_SECS 1
+
 struct _IdeGitVcs
 {
   IdeVcs parent_instance;
@@ -33,9 +36,21 @@ struct _IdeGitVcs
   GgitRepository *repository;
   GgitRepository *change_monitor_repository;
   GFile          *working_directory;
+  GFileMonitor   *monitor;
+
+  guint           changed_timeout;
+
+  guint           reloading : 1;
 };
 
-static void g_async_initable_init_interface (GAsyncInitableIface *iface);
+static void     g_async_initable_init_interface (GAsyncInitableIface  *iface);
+static void     ide_git_vcs_reload_async        (IdeGitVcs            *self,
+                                                 GCancellable         *cancellable,
+                                                 GAsyncReadyCallback   callback,
+                                                 gpointer              user_data);
+static gboolean ide_git_vcs_reload_finish       (IdeGitVcs            *self,
+                                                 GAsyncResult         *result,
+                                                 GError              **error);
 
 G_DEFINE_TYPE_EXTENDED (IdeGitVcs, ide_git_vcs, IDE_TYPE_VCS, 0,
                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
@@ -93,61 +108,80 @@ ide_git_vcs_get_buffer_change_monitor (IdeVcs    *vcs,
 }
 
 static void
-ide_git_vcs_finalize (GObject *object)
+ide_git_vcs_load_repository_worker (GTask        *task,
+                                    gpointer      source_object,
+                                    gpointer      task_data,
+                                    GCancellable *cancellable)
 {
-  IdeGitVcs *self = (IdeGitVcs *)object;
-
-  g_clear_object (&self->change_monitor_repository);
-  g_clear_object (&self->repository);
-  g_clear_object (&self->working_directory);
+  GFile *project_file = task_data;
+  g_autoptr(GFile) location = NULL;
+  GgitRepository *repository = NULL;
+  GError *error = NULL;
 
-  G_OBJECT_CLASS (ide_git_vcs_parent_class)->finalize (object);
-}
+  g_assert (G_IS_TASK (task));
+  g_assert (G_IS_FILE (project_file));
 
-static void
-ide_git_vcs_get_property (GObject    *object,
-                          guint       prop_id,
-                          GValue     *value,
-                          GParamSpec *pspec)
-{
-  IdeGitVcs *self = IDE_GIT_VCS (object);
+  location = ggit_repository_discover (project_file, &error);
 
-  switch (prop_id)
+  if (!location)
     {
-    case PROP_REPOSITORY:
-      g_value_set_object (value, ide_git_vcs_get_repository (self));
-      break;
+      g_task_return_error (task, error);
+      return;
+    }
 
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  repository = ggit_repository_open (location, &error);
+
+  if (!repository)
+    {
+      g_task_return_error (task, error);
+      return;
     }
+
+  g_task_return_pointer (task, repository, g_object_unref);
 }
 
 static void
-ide_git_vcs_class_init (IdeGitVcsClass *klass)
+ide_git_vcs_load_repository_async (IdeGitVcs           *self,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  IdeVcsClass *vcs_class = IDE_VCS_CLASS (klass);
+  g_autoptr(GTask) task = NULL;
+  IdeContext *context;
+  GFile *project_file;
 
-  object_class->finalize = ide_git_vcs_finalize;
-  object_class->get_property = ide_git_vcs_get_property;
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  vcs_class->get_working_directory = ide_git_vcs_get_working_directory;
-  vcs_class->get_buffer_change_monitor = ide_git_vcs_get_buffer_change_monitor;
+  context = ide_object_get_context (IDE_OBJECT (self));
+  project_file = ide_context_get_project_file (context);
 
-  gParamSpecs [PROP_REPOSITORY] =
-    g_param_spec_object ("repository",
-                         _("Repository"),
-                         _("The git repository for the project."),
-                         GGIT_TYPE_REPOSITORY,
-                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-  g_object_class_install_property (object_class, PROP_REPOSITORY,
-                                   gParamSpecs [PROP_REPOSITORY]);
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_task_data (task, g_object_ref (project_file), g_object_unref);
+  g_task_run_in_thread (task, ide_git_vcs_load_repository_worker);
 }
 
-static void
-ide_git_vcs_init (IdeGitVcs *self)
+static GgitRepository *
+ide_git_vcs_load_repository_finish (IdeGitVcs     *self,
+                                    GAsyncResult  *result,
+                                    GError       **error)
 {
+  GTask *task = (GTask *)result;
+  GgitRepository *ret;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+
+  ret = g_task_propagate_pointer (task, error);
+
+  if (ret)
+    {
+      GFile *working_directory;
+
+      working_directory = ggit_repository_get_workdir (ret);
+      g_set_object (&self->working_directory, working_directory);
+    }
+
+  return ret;
 }
 
 static void
@@ -217,11 +251,15 @@ ide_git_vcs_reload_index_add_path (IdeGitVcs   *self,
   g_clear_pointer (&name, g_free);
 }
 
-static gboolean
-ide_git_vcs_reload_index (IdeGitVcs  *self,
-                          GError    **error)
+static void
+ide_git_vcs_build_tree_worker (GTask        *task,
+                               gpointer      source_object,
+                               gpointer      task_data,
+                               GCancellable *cancellable)
 {
+  IdeGitVcs *self = source_object;
   GgitIndexEntries *entries = NULL;
+  GgitRepository *repository = task_data;
   IdeProjectItem *root;
   IdeProjectItem *files = NULL;
   IdeContext *context;
@@ -229,13 +267,17 @@ ide_git_vcs_reload_index (IdeGitVcs  *self,
   GgitIndex *index = NULL;
   GHashTable *cache = NULL;
   g_autofree gchar *workdir = NULL;
-  gboolean ret = FALSE;
+  g_autoptr(GFile) workdir_file = NULL;
+  GError *error = NULL;
   guint count;
   guint i;
 
-  g_return_val_if_fail (IDE_IS_GIT_VCS (self), FALSE);
+  g_assert (G_IS_TASK (task));
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (GGIT_IS_REPOSITORY (repository));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  index = ggit_repository_get_index (self->repository, error);
+  index = ggit_repository_get_index (repository, &error);
   if (!index)
     goto cleanup;
 
@@ -260,7 +302,8 @@ ide_git_vcs_reload_index (IdeGitVcs  *self,
 
   g_hash_table_insert (cache, g_strdup ("."), g_object_ref (files));
 
-  workdir = g_file_get_path (self->working_directory);
+  workdir_file = ggit_repository_get_workdir (repository);
+  workdir = g_file_get_path (workdir_file);
 
   for (i = 0; i < count; i++)
     {
@@ -273,84 +316,412 @@ ide_git_vcs_reload_index (IdeGitVcs  *self,
       ggit_index_entry_unref (entry);
     }
 
-  ide_project_writer_lock (project);
-  ide_project_item_append (root, files);
-  ide_project_writer_unlock (project);
-
-  ret = TRUE;
-
 cleanup:
+  if (error)
+    g_task_return_error (task, error);
+  else
+    g_task_return_pointer (task, g_object_ref (files), g_object_unref);
+
   g_clear_pointer (&cache, g_hash_table_unref);
   g_clear_pointer (&entries, ggit_index_entries_unref);
   g_clear_object (&files);
   g_clear_object (&index);
+}
+
+static void
+ide_git_vcs_build_tree_async (IdeGitVcs           *self,
+                              GgitRepository      *repository,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_task_data (task, g_object_ref (repository), g_object_unref);
+  g_task_run_in_thread (task, ide_git_vcs_build_tree_worker);
+}
+
+static IdeProjectFiles *
+ide_git_vcs_build_tree_finish (IdeGitVcs     *self,
+                               GAsyncResult  *result,
+                               GError       **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_GIT_VCS (self), NULL);
+
+  return g_task_propagate_pointer (task, error);
+}
+
+static void
+ide_git_vcs__reload_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  IdeGitVcs *self = (IdeGitVcs *)object;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+
+  if (!ide_git_vcs_reload_finish (self, result, &error))
+    g_message ("%s", error->message);
+}
+
+
+static gboolean
+ide_git_vcs__changed_timeout_cb (gpointer user_data)
+{
+  IdeGitVcs *self = user_data;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+
+  self->changed_timeout = 0;
+
+  ide_git_vcs_reload_async (self, NULL, ide_git_vcs__reload_cb, NULL);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_git_vcs__monitor_changed_cb (IdeGitVcs         *self,
+                                 GFile             *file,
+                                 GFile             *other_file,
+                                 GFileMonitorEvent  event_type,
+                                 gpointer           user_data)
+{
+  g_assert (IDE_IS_GIT_VCS (self));
+
+  if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)
+    {
+      g_print ("Repository index changed.\n");
+
+      if (self->changed_timeout)
+        g_source_remove (self->changed_timeout);
+
+      self->changed_timeout = g_timeout_add_seconds (DEFAULT_CHANGED_TIMEOUT_SECS,
+                                                     ide_git_vcs__changed_timeout_cb,
+                                                     self);
+    }
+}
+
+static gboolean
+ide_git_vcs_load_monitor (IdeGitVcs  *self,
+                          GError    **error)
+{
+  gboolean ret = TRUE;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+
+  if (!self->monitor)
+    {
+      g_autoptr(GFile) location = NULL;
+      g_autoptr(GFileMonitor) monitor = NULL;
+      g_autoptr(GFile) index_file = NULL;
+
+      location = ggit_repository_get_location (self->repository);
+      index_file = g_file_get_child (location, "index");
+      monitor = g_file_monitor_file (index_file, G_FILE_MONITOR_NONE, NULL, error);
+
+      ret = !!monitor;
+
+      if (monitor)
+        {
+          g_signal_connect_object (monitor,
+                                   "changed",
+                                   G_CALLBACK (ide_git_vcs__monitor_changed_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+          self->monitor = g_object_ref (monitor);
+        }
+    }
 
   return ret;
 }
 
 static void
-ide_git_vcs_init_worker (GTask        *task,
-                         gpointer      source_object,
-                         gpointer      task_data,
-                         GCancellable *cancellable)
+ide_git_vcs_reload__load_repository_cb3 (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
 {
-  GgitRepository *repository = NULL;
-  GgitRepository *diff_repository = NULL;
-  IdeGitVcs *self = source_object;
+  IdeGitVcs *self = (IdeGitVcs *)object;
+  g_autoptr(GTask) task = user_data;
+  GgitRepository *repository;
   GError *error = NULL;
-  GFile *directory = task_data;
-  GFile *location = NULL;
 
-  g_return_if_fail (G_IS_TASK (task));
-  g_return_if_fail (IDE_IS_GIT_VCS (self));
-  g_return_if_fail (G_IS_FILE (directory));
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
 
-  location = ggit_repository_discover (directory, &error);
+  repository = ide_git_vcs_load_repository_finish (self, result, &error);
 
-  if (!location)
+  if (!repository)
     {
       g_task_return_error (task, error);
-      goto cleanup;
+      return;
     }
 
+  g_set_object (&self->change_monitor_repository, repository);
+
   /*
-   * Open the repository we control for general, primary thread use.
+   * Now finally, load the change monitor so that we can detect future changes.
    */
-  repository = ggit_repository_open (location, &error);
+  if (!ide_git_vcs_load_monitor (self, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
 
-  if (!repository)
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_git_vcs_reload__build_tree_cb (GObject      *object,
+                                   GAsyncResult *result,
+                                   gpointer      user_data)
+{
+  IdeGitVcs *self = (IdeGitVcs *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(IdeProjectFiles) files = NULL;
+  IdeContext *context;
+  IdeProject *project;
+  IdeProjectItem *root;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (G_IS_TASK (task));
+
+  files = ide_git_vcs_build_tree_finish (self, result, &error);
+
+  if (!files)
     {
       g_task_return_error (task, error);
-      goto cleanup;
+      return;
     }
 
+  context = ide_object_get_context (IDE_OBJECT (self));
+  project = ide_context_get_project (context);
+
+  ide_project_writer_lock (project);
+  root = ide_project_get_root (project);
+  /* TODO: Replace existing item!!! */
+  ide_project_item_append (root, IDE_PROJECT_ITEM (files));
+  ide_project_writer_unlock (project);
+
   /*
-   * Open the repository we control for use in threaded diff calculations.
+   * Load the repository a third time for use by the threaded change monitors generating diffs.
+   * I know it seems like a lot of loading, but it is a lot better than the N_FILES repositories
+   * we had open previously.
    */
-  diff_repository = ggit_repository_open (location, &error);
+  ide_git_vcs_load_repository_async (self,
+                                     g_task_get_cancellable (task),
+                                     ide_git_vcs_reload__load_repository_cb3,
+                                     g_object_ref (task));
+}
 
-  if (!diff_repository)
+static void
+ide_git_vcs_reload__load_repository_cb2 (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  IdeGitVcs *self = (IdeGitVcs *)object;
+  g_autoptr(GTask) task = user_data;
+  GgitRepository *repository;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  repository = ide_git_vcs_load_repository_finish (self, result, &error);
+
+  if (!repository)
     {
       g_task_return_error (task, error);
-      goto cleanup;
+      return;
     }
 
-  self->repository = g_object_ref (repository);
-  self->change_monitor_repository = g_object_ref (diff_repository);
-  self->working_directory = ggit_repository_get_workdir (self->repository);
+  /*
+   * Now go load the files for the project tree.
+   */
+  ide_git_vcs_build_tree_async (self,
+                                repository,
+                                g_task_get_cancellable (task),
+                                ide_git_vcs_reload__build_tree_cb,
+                                g_object_ref (task));
+
+  g_clear_object (&repository);
+}
+
+static void
+ide_git_vcs_reload__load_repository_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+  IdeGitVcs *self = (IdeGitVcs *)object;
+  g_autoptr(GTask) task = user_data;
+  GgitRepository *repository;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  repository = ide_git_vcs_load_repository_finish (self, result, &error);
 
-  if (!ide_git_vcs_reload_index (self, &error))
+  if (!repository)
     {
       g_task_return_error (task, error);
-      goto cleanup;
+      return;
     }
 
-  g_task_return_boolean (task, TRUE);
+  g_set_object (&self->repository, repository);
 
-cleanup:
-  g_clear_object (&location);
-  g_clear_object (&repository);
-  g_clear_object (&diff_repository);
+  /*
+   * Now load the repository again for use by the threaded index builder.
+   */
+  ide_git_vcs_load_repository_async (self,
+                                     g_task_get_cancellable (task),
+                                     ide_git_vcs_reload__load_repository_cb2,
+                                     g_object_ref (task));
+}
+
+static void
+ide_git_vcs_reload_async (IdeGitVcs           *self,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  if (self->reloading)
+    {
+      g_print ("ignoring reload request, still running.\n");
+      /*
+       * Ignore if we are already reloading. We should probably set a bit here and attept to
+       * reload again after the current process completes.
+       */
+      g_task_return_boolean (task, TRUE);
+      return;
+    }
+
+  self->reloading = TRUE;
+
+  g_print ("... now reloading...\n");
+
+  ide_git_vcs_load_repository_async (self,
+                                     NULL,
+                                     ide_git_vcs_reload__load_repository_cb,
+                                     g_object_ref (task));
+}
+
+gboolean
+ide_git_vcs_reload_finish (IdeGitVcs     *self,
+                           GAsyncResult  *result,
+                           GError       **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_GIT_VCS (self), FALSE);
+
+  self->reloading = FALSE;
+
+  return g_task_propagate_boolean (task, error);
+}
+
+static void
+ide_git_vcs_dispose (GObject *object)
+{
+  IdeGitVcs *self = (IdeGitVcs *)object;
+
+  if (self->changed_timeout)
+    {
+      g_source_remove (self->changed_timeout);
+      self->changed_timeout = 0;
+    }
+
+  if (self->monitor)
+    {
+      if (!g_file_monitor_is_cancelled (self->monitor))
+        g_file_monitor_cancel (self->monitor);
+      g_clear_object (&self->monitor);
+    }
+
+  g_clear_object (&self->change_monitor_repository);
+  g_clear_object (&self->repository);
+  g_clear_object (&self->working_directory);
+
+  G_OBJECT_CLASS (ide_git_vcs_parent_class)->dispose (object);
+}
+
+static void
+ide_git_vcs_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  IdeGitVcs *self = IDE_GIT_VCS (object);
+
+  switch (prop_id)
+    {
+    case PROP_REPOSITORY:
+      g_value_set_object (value, ide_git_vcs_get_repository (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_git_vcs_class_init (IdeGitVcsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeVcsClass *vcs_class = IDE_VCS_CLASS (klass);
+
+  object_class->dispose = ide_git_vcs_dispose;
+  object_class->get_property = ide_git_vcs_get_property;
+
+  vcs_class->get_working_directory = ide_git_vcs_get_working_directory;
+  vcs_class->get_buffer_change_monitor = ide_git_vcs_get_buffer_change_monitor;
+
+  gParamSpecs [PROP_REPOSITORY] =
+    g_param_spec_object ("repository",
+                         _("Repository"),
+                         _("The git repository for the project."),
+                         GGIT_TYPE_REPOSITORY,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_REPOSITORY,
+                                   gParamSpecs [PROP_REPOSITORY]);
+}
+
+static void
+ide_git_vcs_init (IdeGitVcs *self)
+{
+}
+
+static void
+ide_git_vcs_init_async__reload_cb (GObject      *object,
+                                   GAsyncResult *result,
+                                   gpointer      user_data)
+{
+  IdeGitVcs *self = (IdeGitVcs *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IDE_IS_GIT_VCS (self));
+
+  if (!ide_git_vcs_reload_finish (self, result, &error))
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
 }
 
 static void
@@ -360,20 +731,16 @@ ide_git_vcs_init_async (GAsyncInitable      *initable,
                         GAsyncReadyCallback  callback,
                         gpointer             user_data)
 {
-  IdeContext *context;
   IdeGitVcs *self = (IdeGitVcs *)initable;
-  GFile *project_file;
-  GTask *task;
+  g_autoptr(GTask) task = NULL;
 
   g_return_if_fail (IDE_IS_GIT_VCS (self));
 
-  context = ide_object_get_context (IDE_OBJECT (initable));
-  project_file = ide_context_get_project_file (context);
-
   task = g_task_new (self, cancellable, callback, user_data);
-  g_task_set_task_data (task, g_object_ref (project_file), g_object_unref);
-  g_task_run_in_thread (task, ide_git_vcs_init_worker);
-  g_object_unref (task);
+  ide_git_vcs_reload_async (self,
+                            cancellable,
+                            ide_git_vcs_init_async__reload_cb,
+                            g_object_ref (task));
 }
 
 static gboolean


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