[gnome-builder] libide: setup git vcs to be reloadable
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] libide: setup git vcs to be reloadable
- Date: Mon, 23 Mar 2015 23:45:36 +0000 (UTC)
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]