[gnome-builder] git: perform repository discovery and blob loading asynchronously



commit 1a5d7b5e596987a99c4d75dd2481e62d2d7437e7
Author: Christian Hergert <christian hergert me>
Date:   Tue Dec 23 18:18:09 2014 -0800

    git: perform repository discovery and blob loading asynchronously
    
    This discovers the repository (which requires a lot of access()) and
    loading of the GgitBlob for diff generation in a thread worker.

 src/editor/gb-source-change-monitor.c |  298 +++++++++++++++++++++++++++------
 1 files changed, 245 insertions(+), 53 deletions(-)
---
diff --git a/src/editor/gb-source-change-monitor.c b/src/editor/gb-source-change-monitor.c
index 7b68bcb..61c41fd 100644
--- a/src/editor/gb-source-change-monitor.c
+++ b/src/editor/gb-source-change-monitor.c
@@ -37,6 +37,8 @@ struct _GbSourceChangeMonitorPrivate
   GgitBlob       *blob;
   gchar          *relative_path;
   GHashTable     *state;
+  GCancellable   *cancellable;
+
   guint           changed_handler;
   guint           parse_timeout;
 };
@@ -333,9 +335,12 @@ on_change_cb (GbSourceChangeMonitor *monitor,
 }
 
 static void
-gb_source_change_monitor_load_blob (GbSourceChangeMonitor *monitor)
+gb_source_change_monitor_load_blob (GTask        *task,
+                                    gpointer      source_object,
+                                    gpointer      task_data,
+                                    GCancellable *cancellable)
 {
-  GbSourceChangeMonitorPrivate *priv;
+  GbSourceChangeMonitor *monitor = source_object;
   GgitOId *entry_oid = NULL;
   GgitOId *oid = NULL;
   GgitObject *blob = NULL;
@@ -343,24 +348,25 @@ gb_source_change_monitor_load_blob (GbSourceChangeMonitor *monitor)
   GgitRef *head = NULL;
   GgitTree *tree = NULL;
   GgitTreeEntry *entry = NULL;
+  GgitRepository *repo;
+  gboolean success = FALSE;
+  GFile *file;
   GFile *workdir = NULL;
   GError *error = NULL;
   gchar *relpath = NULL;
 
-  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_assert (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
 
-  priv = monitor->priv;
+  repo = ((gpointer *)task_data)[0];
+  file = ((gpointer *)task_data)[1];
 
-  /*
-   * Double check we have everything we need.
-   */
-  if (!priv->repo || !priv->file)
-    return;
+  g_assert (GGIT_IS_REPOSITORY (repo));
+  g_assert (G_IS_FILE (file));
 
   /*
    * Work our way through ggit to get to the original blob we care about.
    */
-  head = ggit_repository_get_head (priv->repo, &error);
+  head = ggit_repository_get_head (repo, &error);
   if (!head)
     GOTO (cleanup);
 
@@ -368,7 +374,7 @@ gb_source_change_monitor_load_blob (GbSourceChangeMonitor *monitor)
   if (!oid)
     GOTO (cleanup);
 
-  commit = ggit_repository_lookup (priv->repo, oid, GGIT_TYPE_COMMIT, &error);
+  commit = ggit_repository_lookup (repo, oid, GGIT_TYPE_COMMIT, &error);
   if (!commit)
     GOTO (cleanup);
 
@@ -376,11 +382,11 @@ gb_source_change_monitor_load_blob (GbSourceChangeMonitor *monitor)
   if (!tree)
     GOTO (cleanup);
 
-  workdir = ggit_repository_get_workdir (priv->repo);
+  workdir = ggit_repository_get_workdir (repo);
   if (!workdir)
     GOTO (cleanup);
 
-  relpath = g_file_get_relative_path (workdir, priv->file);
+  relpath = g_file_get_relative_path (workdir, file);
   if (!relpath)
     GOTO (cleanup);
 
@@ -392,19 +398,21 @@ gb_source_change_monitor_load_blob (GbSourceChangeMonitor *monitor)
   if (!entry_oid)
     GOTO (cleanup);
 
-  blob = ggit_repository_lookup (priv->repo, entry_oid, GGIT_TYPE_BLOB, &error);
+  blob = ggit_repository_lookup (repo, entry_oid, GGIT_TYPE_BLOB, &error);
   if (!blob)
     GOTO (cleanup);
 
-  priv->blob = g_object_ref (blob);
-  priv->relative_path = g_strdup (relpath);
+  g_task_return_pointer (task, g_object_ref (blob), g_object_unref);
+  g_object_set_data_full (G_OBJECT (task), "relpath",
+                          g_strdup (relpath), g_free);
+  success = TRUE;
 
 cleanup:
   if (error)
-    {
-      g_message ("%s", error->message);
-      g_clear_error (&error);
-    }
+    g_task_return_error (task, error);
+  else if (!success)
+    g_task_return_new_error (task, G_FILE_ERROR, G_FILE_ERROR_NOENT,
+                             _("Failed to load git blob"));
 
   g_clear_object (&blob);
   g_clear_pointer (&entry_oid, ggit_oid_free);
@@ -417,54 +425,169 @@ cleanup:
   g_clear_object (&head);
 }
 
+static GgitBlob *
+gb_source_change_monitor_load_blob_finish (GbSourceChangeMonitor  *monitor,
+                                           GAsyncResult           *result,
+                                           gchar                 **relpath,
+                                           GError                **error)
+{
+  GgitBlob *blob;
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor), NULL);
+  g_return_val_if_fail (G_IS_TASK (task), NULL);
+
+  blob = g_task_propagate_pointer (task, error);
+
+  if (blob && relpath)
+    *relpath = g_strdup (g_object_get_data (G_OBJECT (task), "relpath"));
+
+  return blob;
+}
+
 static void
-gb_source_change_monitor_discover_repository (GbSourceChangeMonitor *monitor)
+free_load_task_data (gpointer data)
 {
-  GbSourceChangeMonitorPrivate *priv;
+  gpointer *task_data = (gpointer *)data;
+
+  g_object_unref (task_data[0]);
+  g_object_unref (task_data[1]);
+  g_free (task_data);
+}
+
+static void
+gb_source_change_monitor_load_blob_async (GbSourceChangeMonitor *monitor,
+                                          GCancellable          *cancellable,
+                                          GAsyncReadyCallback    callback,
+                                          gpointer               user_data)
+{
+  gpointer *task_data;
+  GTask *task;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (!monitor->priv->repo || !monitor->priv->file)
+    {
+      g_task_report_new_error (monitor, callback, user_data,
+                               gb_source_change_monitor_load_blob_async,
+                               G_FILE_ERROR, G_FILE_ERROR_NOENT,
+                               _("No file, cannot load git blob"));
+      return;
+    }
+
+  task_data = g_new0 (gpointer, 2);
+  task_data[0] = g_object_ref (monitor->priv->repo);
+  task_data[1] = g_object_ref (monitor->priv->file);
+
+  task = g_task_new (monitor, cancellable, callback, user_data);
+  g_task_set_task_data (task, task_data, free_load_task_data);
+  g_task_run_in_thread (task, gb_source_change_monitor_load_blob);
+  g_object_unref (task);
+}
+
+static void
+gb_source_change_monitor_discover (GTask        *task,
+                                   gpointer      source_object,
+                                   gpointer      task_data,
+                                   GCancellable *cancellable)
+{
+  GbSourceChangeMonitor *monitor = source_object;
+  GgitRepository *repository = NULL;
+  GError *error = NULL;
+  GFile *file = task_data;
+  GFile *repo_dir = NULL;
 
   ENTRY;
 
   g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (G_IS_FILE (file));
 
   /*
-   * TODO: This makes a lot of assumptions.
-   *   - that we are local
-   *   - that checking disk is free
-   *   - that we don't need to cache anything.
-   *
-   *  and all of those are probably wrong.
+   * Cannot locate .git repository unless g_file_get_path() will return
+   * something.
    */
+  if (!g_file_is_native (file))
+    {
+      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                               _("Cannot load git repository from non-local filesystem."));
+      GOTO (failure);
+    }
 
-  priv = monitor->priv;
+  /* Discover the .git repository for working directory containing @file. */
+  repo_dir = ggit_repository_discover (file, &error);
 
-  g_clear_object (&priv->repo);
+  if (!repo_dir)
+    {
+      g_task_return_error (task, error);
+      GOTO (failure);
+    }
 
-  if (priv->file)
+  /* Check if we were cancelled since blocking call returned */
+  if (g_task_return_error_if_cancelled (task))
+    GOTO (failure);
+
+  /* Load the repository for the file */
+  repository = ggit_repository_open (repo_dir, &error);
+
+  if (!repository)
     {
-      GFile *repo_file;
-      GError *error = NULL;
+      g_task_return_error (task, error);
+      GOTO (failure);
+    }
 
-      repo_file = ggit_repository_discover (priv->file, &error);
+  /* Check if we were cancelled since blocking call returned */
+  if (g_task_return_error_if_cancelled (task))
+    GOTO (failure);
 
-      if (!repo_file)
-        {
-          g_message (_("Failed to locate a git repository: %s"), error->message);
-          g_clear_error (&error);
-          EXIT;
-        }
+  /* Pass the repository back to the main thread to assign to private */
+  g_task_return_pointer (task, g_object_ref (repository), g_object_unref);
 
-      priv->repo = ggit_repository_open (repo_file, &error);
+failure:
+  g_clear_object (&repo_dir);
+  g_clear_object (&repository);
 
-      if (!priv->repo)
-        {
-          g_message (_("Failed to open git repository: %s"), error->message);
-          g_clear_error (&error);
-        }
+  EXIT;
+}
 
-      g_clear_object (&repo_file);
+static void
+gb_source_change_monitor_discover_async (GbSourceChangeMonitor *monitor,
+                                         GCancellable          *cancellable,
+                                         GAsyncReadyCallback    callback,
+                                         gpointer               user_data)
+{
+  GbSourceChangeMonitorPrivate *priv;
+  GTask *task;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  priv = monitor->priv;
+
+  if (!priv->file)
+    {
+      g_task_report_new_error (monitor, callback, user_data,
+                               gb_source_change_monitor_discover_async,
+                               G_FILE_ERROR, G_FILE_ERROR_NOENT,
+                               _("No filename, cannot discover repository."));
+      return;
     }
 
-  EXIT;
+  task = g_task_new (monitor, cancellable, callback, user_data);
+  g_task_set_task_data (task, g_object_ref (priv->file), g_object_unref);
+  g_task_run_in_thread (task, gb_source_change_monitor_discover);
+  g_object_unref (task);
+}
+
+static GgitRepository *
+gb_source_change_monitor_discover_finish (GbSourceChangeMonitor  *monitor,
+                                          GAsyncResult           *result,
+                                          GError                **error)
+{
+  g_return_val_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_pointer (G_TASK (result), error);
 }
 
 GtkTextBuffer *
@@ -523,6 +646,68 @@ gb_source_change_monitor_get_file (GbSourceChangeMonitor *monitor)
   return monitor->priv->file;
 }
 
+static void
+gb_source_change_monitor_load_blob_cb (GObject      *object,
+                                       GAsyncResult *result,
+                                       gpointer      user_data)
+{
+  GbSourceChangeMonitor *monitor = (GbSourceChangeMonitor *)object;
+  GgitBlob *blob;
+  GError *error = NULL;
+  gchar *relpath = NULL;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+
+  blob = gb_source_change_monitor_load_blob_finish (monitor, result, &relpath,
+                                                    &error);
+
+  if (blob)
+    {
+      g_clear_object (&monitor->priv->blob);
+      monitor->priv->blob = blob;
+      g_clear_pointer (&monitor->priv->relative_path, g_free);
+      monitor->priv->relative_path = relpath;
+
+      gb_source_change_monitor_queue_parse (monitor);
+    }
+  else
+    {
+      g_message ("%s", error->message);
+      g_clear_error (&error);
+    }
+}
+
+static void
+gb_source_change_monitor_discover_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  GbSourceChangeMonitor *monitor = (GbSourceChangeMonitor *)object;
+  GgitRepository *repo;
+  GError *error = NULL;
+
+  g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
+
+  repo = gb_source_change_monitor_discover_finish (monitor, result, &error);
+
+  if (repo)
+    {
+      g_clear_object (&monitor->priv->repo);
+      monitor->priv->repo = repo;
+
+      if (!g_cancellable_is_cancelled (monitor->priv->cancellable))
+        gb_source_change_monitor_load_blob_async (monitor,
+                                                  monitor->priv->cancellable,
+                                                  gb_source_change_monitor_load_blob_cb,
+                                                  NULL);
+    }
+  else
+    {
+      g_message ("%s", error->message);
+      g_clear_error (&error);
+    }
+}
+
 void
 gb_source_change_monitor_reload (GbSourceChangeMonitor *monitor)
 {
@@ -531,11 +716,10 @@ gb_source_change_monitor_reload (GbSourceChangeMonitor *monitor)
   g_return_if_fail (GB_IS_SOURCE_CHANGE_MONITOR (monitor));
 
   if (monitor->priv->file)
-    {
-      gb_source_change_monitor_discover_repository (monitor);
-      gb_source_change_monitor_load_blob (monitor);
-      gb_source_change_monitor_queue_parse (monitor);
-    }
+    gb_source_change_monitor_discover_async (monitor,
+                                             monitor->priv->cancellable,
+                                             gb_source_change_monitor_discover_cb,
+                                             NULL);
 
   EXIT;
 }
@@ -581,9 +765,16 @@ gb_source_change_monitor_dispose (GObject *object)
   gb_source_change_monitor_set_buffer (monitor, NULL);
   gb_source_change_monitor_set_file (monitor, NULL);
 
+  if (monitor->priv->cancellable)
+    {
+      g_cancellable_cancel (monitor->priv->cancellable);
+      g_clear_object (&monitor->priv->cancellable);
+    }
+
   g_clear_object (&monitor->priv->repo);
   g_clear_object (&monitor->priv->blob);
 
+
   if (monitor->priv->parse_timeout)
     {
       g_source_remove (monitor->priv->parse_timeout);
@@ -704,5 +895,6 @@ gb_source_change_monitor_init (GbSourceChangeMonitor *monitor)
 {
   ENTRY;
   monitor->priv = gb_source_change_monitor_get_instance_private (monitor);
+  monitor->priv->cancellable = g_cancellable_new ();
   EXIT;
 }


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