[gnome-builder/wip/chergert/git-oop: 28/34] add daemon side get-changes implementation



commit 6af364f72a82ef53c69fc6d7e126fcca452d9b90
Author: Christian Hergert <chergert redhat com>
Date:   Thu Mar 21 18:14:19 2019 -0700

    add daemon side get-changes implementation

 src/plugins/git/gbp-git.c           | 213 ++++++++++++++++++++++++++++++++++++
 src/plugins/git/gbp-git.h           |   9 ++
 src/plugins/git/gnome-builder-git.c |  73 ++++++++++++
 src/plugins/git/meson.build         |   1 +
 4 files changed, 296 insertions(+)
---
diff --git a/src/plugins/git/gbp-git.c b/src/plugins/git/gbp-git.c
index 68fae4c8e..195714a68 100644
--- a/src/plugins/git/gbp-git.c
+++ b/src/plugins/git/gbp-git.c
@@ -25,10 +25,12 @@
 #include <libgit2-glib/ggit.h>
 
 #include "gbp-git.h"
+#include "line-cache.h"
 
 struct _GbpGit
 {
   GObject         parent_instance;
+  GMutex          mutex;
   GFile          *workdir;
   GgitRepository *repository;
 };
@@ -42,6 +44,7 @@ gbp_git_finalize (GObject *object)
 
   g_clear_object (&self->workdir);
   g_clear_object (&self->repository);
+  g_mutex_clear (&self->mutex);
 
   G_OBJECT_CLASS (gbp_git_parent_class)->finalize (object);
 }
@@ -57,6 +60,7 @@ gbp_git_class_init (GbpGitClass *klass)
 static void
 gbp_git_init (GbpGit *self)
 {
+  g_mutex_init (&self->mutex);
 }
 
 GbpGit *
@@ -951,3 +955,212 @@ gbp_git_discover_finish (GbpGit        *self,
 
   return FALSE;
 }
+
+typedef struct
+{
+  gchar  *path;
+  GBytes *bytes;
+} GetChanges;
+
+typedef struct
+{
+  gint old_start;
+  gint old_lines;
+  gint new_start;
+  gint new_lines;
+} Range;
+
+static void
+get_changes_free (GetChanges *state)
+{
+  g_clear_pointer (&state->path, g_free);
+  g_clear_pointer (&state->bytes, g_bytes_unref);
+  g_slice_free (GetChanges, state);
+}
+
+static gint
+diff_hunk_cb (GgitDiffDelta *delta,
+              GgitDiffHunk  *hunk,
+              gpointer       user_data)
+{
+  GArray *ranges = user_data;
+  Range range;
+
+  g_assert (delta != NULL);
+  g_assert (hunk != NULL);
+  g_assert (ranges != NULL);
+
+  range.old_start = ggit_diff_hunk_get_old_start (hunk);
+  range.old_lines = ggit_diff_hunk_get_old_lines (hunk);
+  range.new_start = ggit_diff_hunk_get_new_start (hunk);
+  range.new_lines = ggit_diff_hunk_get_new_lines (hunk);
+
+  g_array_append_val (ranges, range);
+
+  return 0;
+}
+
+static void
+gbp_git_get_changes_worker (GTask        *task,
+                            gpointer      source_object,
+                            gpointer      task_data,
+                            GCancellable *cancellable)
+{
+  GbpGit *self = source_object;
+  GetChanges *state = task_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GMutexLocker) locker = NULL;
+  g_autoptr(GgitDiffOptions) options = NULL;
+  g_autoptr(GVariant) ret = NULL;
+  g_autoptr(GArray) ranges = NULL;
+  GgitTreeEntry *entry = NULL;
+  const gchar *contents;
+  GgitObject *commit = NULL;
+  GgitObject *blob = NULL;
+  LineCache *cache = NULL;
+  GgitTree *tree = NULL;
+  GgitOId *entry_oid = NULL;
+  GgitOId *oid = NULL;
+  GgitRef *head = NULL;
+  gsize len;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GBP_IS_GIT (self));
+  g_assert (state != NULL);
+  g_assert (state->path != NULL);
+  g_assert (state->bytes != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  locker = g_mutex_locker_new (&self->mutex);
+
+  if (self->repository == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_NOT_INITIALIZED,
+                               "No repository has been loaded");
+      return;
+    }
+
+  if (!(head = ggit_repository_get_head (self->repository, &error)) ||
+      !(oid = ggit_ref_get_target (head)) ||
+      !(commit = ggit_repository_lookup (self->repository, oid, GGIT_TYPE_COMMIT, &error)) ||
+      !(tree = ggit_commit_get_tree (GGIT_COMMIT (commit))) ||
+      !(entry = ggit_tree_get_by_path (tree, state->path, &error)) ||
+      !(entry_oid = ggit_tree_entry_get_id (entry)) ||
+      !(blob = ggit_repository_lookup (self->repository, entry_oid, GGIT_TYPE_BLOB, &error)))
+    goto cleanup;
+
+  contents = g_bytes_get_data (state->bytes, &len);
+
+  g_assert (GGIT_IS_BLOB (blob));
+  g_assert (contents != NULL);
+
+  ranges = g_array_new (FALSE, FALSE, sizeof (Range));
+  options = ggit_diff_options_new ();
+  ggit_diff_options_set_n_context_lines (options, 0);
+  ggit_diff_blob_to_buffer (GGIT_BLOB (blob),
+                            state->path,
+                            (const guint8 *)contents,
+                            len,
+                            state->path,
+                            options,
+                            NULL,         /* File Callback */
+                            NULL,         /* Binary Callback */
+                            diff_hunk_cb, /* Hunk Callback */
+                            NULL,
+                            ranges,
+                            &error);
+
+  if (error != NULL)
+    goto cleanup;
+
+  cache = line_cache_new ();
+
+  for (guint i = 0; i < ranges->len; i++)
+    {
+      const Range *range = &g_array_index (ranges, Range, i);
+      gint start_line = range->new_start - 1;
+      gint end_line = range->new_start + range->new_lines - 1;
+
+      if (range->old_lines == 0 && range->new_lines > 0)
+        line_cache_mark_range (cache, start_line, end_line, LINE_MARK_ADDED);
+      else if (range->new_lines == 0 && range->old_lines > 0)
+        {
+          if (start_line < 0)
+            line_cache_mark_range (cache, 0, 0, LINE_MARK_PREVIOUS_REMOVED);
+          else
+            line_cache_mark_range (cache, start_line + 1, start_line + 1, LINE_MARK_REMOVED);
+        }
+      else
+        line_cache_mark_range (cache, start_line, end_line, LINE_MARK_CHANGED);
+    }
+
+  ret = line_cache_to_variant (cache);
+
+cleanup:
+  g_clear_object (&blob);
+  g_clear_pointer (&entry_oid, ggit_oid_free);
+  g_clear_pointer (&entry, ggit_tree_entry_unref);
+  g_clear_object (&tree);
+  g_clear_object (&commit);
+  g_clear_pointer (&oid, ggit_oid_free);
+  g_clear_object (&head);
+  g_clear_pointer (&cache, line_cache_free);
+
+  g_assert (ret != NULL || error != NULL);
+
+  if (error != NULL)
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_pointer (task,
+                           g_steal_pointer (&ret),
+                           (GDestroyNotify)g_variant_unref);
+}
+
+void
+gbp_git_get_changes_async (GbpGit              *self,
+                           const gchar         *path,
+                           GBytes              *bytes,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  GetChanges *state;
+
+  g_assert (GBP_IS_GIT (self));
+  g_assert (path != NULL);
+  g_assert (bytes != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  state = g_slice_new0 (GetChanges);
+  state->path = g_strdup (path);
+  state->bytes = g_bytes_ref (bytes);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, gbp_git_get_changes_async);
+  g_task_set_task_data (task, state, (GDestroyNotify)get_changes_free);
+
+  if (self->repository == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_NOT_INITIALIZED,
+                               "No repository loaded");
+      return;
+    }
+
+  g_task_run_in_thread (task, gbp_git_get_changes_worker);
+}
+
+GVariant *
+gbp_git_get_changes_finish (GbpGit        *self,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  g_assert (GBP_IS_GIT (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/src/plugins/git/gbp-git.h b/src/plugins/git/gbp-git.h
index 7023e60f5..3a735c3b7 100644
--- a/src/plugins/git/gbp-git.h
+++ b/src/plugins/git/gbp-git.h
@@ -146,5 +146,14 @@ gboolean   gbp_git_discover_finish          (GbpGit                      *self,
                                              gchar                      **branch,
                                              gboolean                    *is_worktree,
                                              GError                     **error);
+void       gbp_git_get_changes_async        (GbpGit                      *self,
+                                             const gchar                 *path,
+                                             GBytes                      *bytes,
+                                             GCancellable                *cancellable,
+                                             GAsyncReadyCallback          callback,
+                                             gpointer                     user_data);
+GVariant  *gbp_git_get_changes_finish       (GbpGit                      *self,
+                                             GAsyncResult                *result,
+                                             GError                     **error);
 
 G_END_DECLS
diff --git a/src/plugins/git/gnome-builder-git.c b/src/plugins/git/gnome-builder-git.c
index 6a7cbd3a7..dfc9d14ba 100644
--- a/src/plugins/git/gnome-builder-git.c
+++ b/src/plugins/git/gnome-builder-git.c
@@ -982,6 +982,78 @@ handle_discover (JsonrpcServer *server,
                           client_op_ref (op));
 }
 
+/* Handle Get Changes to Buffer {{{1 */
+
+static void
+handle_get_changes_cb (GObject      *object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  GbpGit *git = (GbpGit *)object;
+  g_autoptr(ClientOp) op = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GVariant) lines = NULL;
+
+  g_assert (GBP_IS_GIT (git));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (op != NULL);
+
+  if (!(lines = gbp_git_get_changes_finish (git, result, &error)))
+    client_op_error (op, error);
+  else
+    client_op_reply (op, lines);
+}
+
+static void
+handle_get_changes (JsonrpcServer *server,
+                    JsonrpcClient *client,
+                    const gchar   *method,
+                    GVariant      *id,
+                    GVariant      *params,
+                    GbpGit        *git)
+{
+  g_autoptr(ClientOp) op = NULL;
+  g_autoptr(GBytes) bytes = NULL;
+  const gchar *path = NULL;
+  const gchar *contents = NULL;
+  gboolean r;
+
+  g_assert (JSONRPC_IS_SERVER (server));
+  g_assert (JSONRPC_IS_CLIENT (client));
+  g_assert (g_str_equal (method, "git/getChanges"));
+  g_assert (id != NULL);
+  g_assert (GBP_IS_GIT (git));
+
+  op = client_op_new (client, id);
+
+  r = JSONRPC_MESSAGE_PARSE (params,
+    "path", JSONRPC_MESSAGE_GET_STRING (&path),
+    "contents", JSONRPC_MESSAGE_GET_STRING (&contents)
+  );
+
+  if (!r)
+    {
+      client_op_bad_params (op);
+      return;
+    }
+
+  /* Create a byte buffer that will reference the original variant so that
+   * we can avoid copying the buffer contents when diff'ing from the
+   * worker-thread.
+   */
+  bytes = g_bytes_new_with_free_func (contents,
+                                      strlen (contents ?: ""),
+                                      (GDestroyNotify)g_variant_unref,
+                                      g_variant_ref (params));
+
+  gbp_git_get_changes_async (git,
+                             path,
+                             bytes,
+                             op->cancellable,
+                             (GAsyncReadyCallback)handle_get_changes_cb,
+                             client_op_ref (op));
+}
+
 /* Main Loop and Setup {{{1 */
 
 gint
@@ -1026,6 +1098,7 @@ main (gint argc,
   ADD_HANDLER ("git/cloneUrl", handle_clone_url);
   ADD_HANDLER ("git/createRepo", handle_create_repo);
   ADD_HANDLER ("git/discover", handle_discover);
+  ADD_HANDLER ("git/getChanges", handle_get_changes);
   ADD_HANDLER ("git/isIgnored", handle_is_ignored);
   ADD_HANDLER ("git/listRefsByKind", handle_list_refs_by_kind);
   ADD_HANDLER ("git/readConfig", handle_read_config);
diff --git a/src/plugins/git/meson.build b/src/plugins/git/meson.build
index f40c5bc12..ec56fcd74 100644
--- a/src/plugins/git/meson.build
+++ b/src/plugins/git/meson.build
@@ -24,6 +24,7 @@ gnome_builder_git_sources = [
   'gbp-git.c',
   'gbp-git-remote-callbacks.c',
   'gnome-builder-git.c',
+  'line-cache.c',
 ]
 
 plugin_git_resources = gnome.compile_resources(


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