[gnome-builder/wip/chergert/git-oop] more work on moving to git-client apis



commit 385cfa129627d04630beff2cabb70a5667ebe2c5
Author: Christian Hergert <chergert redhat com>
Date:   Fri Mar 22 16:00:27 2019 -0700

    more work on moving to git-client apis

 src/plugins/git/gbp-git-buffer-addin.c          |   4 +-
 src/plugins/git/gbp-git-buffer-change-monitor.c | 961 +++++-------------------
 src/plugins/git/gbp-git-buffer-change-monitor.h |  17 +-
 src/plugins/git/gbp-git-client.c                |   2 +-
 src/plugins/git/gbp-git-client.h                |   3 +
 src/plugins/git/gbp-git-vcs.c                   |  67 +-
 src/plugins/git/gbp-git-vcs.h                   |   2 -
 src/plugins/git/gbp-git-workbench-addin.c       |  11 +-
 src/plugins/git/gbp-git.c                       |  30 +
 9 files changed, 264 insertions(+), 833 deletions(-)
---
diff --git a/src/plugins/git/gbp-git-buffer-addin.c b/src/plugins/git/gbp-git-buffer-addin.c
index 9d21af2ba..46e17bd03 100644
--- a/src/plugins/git/gbp-git-buffer-addin.c
+++ b/src/plugins/git/gbp-git-buffer-addin.c
@@ -36,7 +36,7 @@ struct _GbpGitBufferAddin
 };
 
 static void
-gbp_git_buffer_addin_file_laoded (IdeBufferAddin *addin,
+gbp_git_buffer_addin_file_loaded (IdeBufferAddin *addin,
                                   IdeBuffer      *buffer,
                                   GFile          *file)
 {
@@ -153,7 +153,7 @@ gbp_git_buffer_addin_settle_finish (IdeBufferAddin  *addin,
 static void
 buffer_addin_iface_init (IdeBufferAddinInterface *iface)
 {
-  iface->file_loaded = gbp_git_buffer_addin_file_laoded;
+  iface->file_loaded = gbp_git_buffer_addin_file_loaded;
   iface->file_saved = gbp_git_buffer_addin_file_saved;
   iface->unload = gbp_git_buffer_addin_unload;
   iface->settle_async = gbp_git_buffer_addin_settle_async;
diff --git a/src/plugins/git/gbp-git-buffer-change-monitor.c b/src/plugins/git/gbp-git-buffer-change-monitor.c
index 72f3ce13f..e80e6d4a9 100644
--- a/src/plugins/git/gbp-git-buffer-change-monitor.c
+++ b/src/plugins/git/gbp-git-buffer-change-monitor.c
@@ -1,6 +1,6 @@
 /* gbp-git-buffer-change-monitor.c
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,437 +18,183 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-/* Some code from this file is loosely based around the git-diff
- * plugin from Atom. Namely, API usage for iterating through hunks
- * containing changes. It's license is provided below.
- */
-
-/*
- * Copyright (c) 2014 GitHub Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
-
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
-
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
 #define G_LOG_DOMAIN "gbp-git-buffer-change-monitor"
 
+#include "config.h"
+
 #include <dazzle.h>
-#include <glib/gi18n.h>
-#include <libgit2-glib/ggit.h>
-#include <stdlib.h>
 
+#include "gbp-git-client.h"
 #include "gbp-git-buffer-change-monitor.h"
-#include "gbp-git-vcs.h"
-
 #include "line-cache.h"
 
 #define DELAY_CHANGED_SEC 1
 
-/**
- * SECTION:gbp-git-buffer-change-monitor
- *
- * This module provides line change monitoring when used in conjunction with an
- * GbpGitVcs.  The changes are generated by comparing the buffer contents to
- * the version found inside of the git repository.
- *
- * To enable us to avoid blocking the main loop, the actual diff is performed
- * in a background thread. To avoid threading issues with the rest of LibGBP,
- * this module creates a copy of the loaded repository. A single thread will be
- * dispatched for the context and all reload tasks will be performed from that
- * thread.
- *
- * Upon completion of the diff, the results will be passed back to the primary
- * thread and the state updated for use by line change renderer in the source
- * view.
- *
- * Since: 3.32
- */
-
 struct _GbpGitBufferChangeMonitor
 {
   IdeBufferChangeMonitor  parent_instance;
-
-  DzlSignalGroup         *signal_group;
-
-  GgitRepository         *repository;
-  GArray                 *lines;
-
-  GgitBlob               *cached_blob;
+  DzlSignalGroup         *signals;
   LineCache              *cache;
-
-  GQueue                  wait_tasks;
-
-  guint                   changed_timeout;
-
-  guint                   state_dirty : 1;
-  guint                   in_calculation : 1;
   guint                   delete_range_requires_recalculation : 1;
-  guint                   is_child_of_workdir : 1;
-  guint                   in_failed_state : 1;
 };
 
-typedef struct
-{
-  GgitRepository *repository;
-  LineCache      *cache;
-  GFile          *file;
-  GBytes         *content;
-  GgitBlob       *blob;
-  IdeObject      *lock_object;
-  guint           is_child_of_workdir : 1;
-} DiffTask;
-
-typedef struct
-{
-  gint old_start;
-  gint old_lines;
-  gint new_start;
-  gint new_lines;
-} Range;
-
 G_DEFINE_TYPE (GbpGitBufferChangeMonitor, gbp_git_buffer_change_monitor, IDE_TYPE_BUFFER_CHANGE_MONITOR)
 
-DZL_DEFINE_COUNTER (instances, "GbpGitBufferChangeMonitor", "Instances", "The number of git buffer change 
monitor instances.");
-
-enum {
-  PROP_0,
-  PROP_REPOSITORY,
-  LAST_PROP
-};
-
-static GParamSpec  *properties [LAST_PROP];
-static GAsyncQueue *work_queue;
-static GThread     *work_thread;
-
 static void
-diff_task_free (gpointer data)
+gbp_git_buffer_change_monitor_recalculate (GbpGitBufferChangeMonitor *self)
 {
-  DiffTask *diff = data;
+  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
 
-  if (diff)
-    {
-      g_clear_object (&diff->file);
-      g_clear_object (&diff->blob);
-      g_clear_object (&diff->repository);
-      g_clear_object (&diff->lock_object);
-      g_clear_pointer (&diff->cache, line_cache_free);
-      g_clear_pointer (&diff->content, g_bytes_unref);
-      g_slice_free (DiffTask, diff);
-    }
+  gbp_git_buffer_change_monitor_wait_async (self, NULL, NULL, NULL);
 }
 
 static void
-complete_wait_tasks (GbpGitBufferChangeMonitor *self,
-                     GParamSpec                *pspec,
-                     IdeTask                   *calculate_task)
+gbp_git_buffer_change_monitor_load (IdeBufferChangeMonitor *monitor,
+                                    IdeBuffer              *buffer)
 {
-  gpointer taskptr;
+  GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)monitor;
 
-  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_assert (IDE_IS_TASK (calculate_task));
-
-  while ((taskptr = g_queue_pop_head (&self->wait_tasks)))
-    {
-      g_autoptr(IdeTask) task = taskptr;
+  g_assert (DZL_IS_SIGNAL_GROUP (self->signals));
+  g_assert (IDE_IS_BUFFER (buffer));
 
-      ide_task_return_boolean (task, TRUE);
-    }
+  dzl_signal_group_set_target (self->signals, buffer);
 }
 
-static LineCache *
-gbp_git_buffer_change_monitor_calculate_finish (GbpGitBufferChangeMonitor  *self,
-                                                GAsyncResult               *result,
-                                                GError                    **error)
+static void
+gbp_git_buffer_change_monitor_reload (IdeBufferChangeMonitor *monitor)
 {
-  IdeTask *task = (IdeTask *)result;
-  DiffTask *diff;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_assert (IDE_IS_TASK (result));
-
-  if (ide_object_set_error_if_destroyed (IDE_OBJECT (self), error))
-    return NULL;
-
-  diff = ide_task_get_task_data (task);
-
-  if (diff != NULL)
-    {
-      g_assert (GGIT_IS_REPOSITORY (diff->repository));
-      g_assert (G_IS_FILE (diff->file));
-      g_assert (diff->content != NULL);
-      g_assert (GBP_IS_GIT_VCS (diff->lock_object));
-
-      /* Keep the blob around for future use */
-      if (diff->blob != self->cached_blob)
-        g_set_object (&self->cached_blob, diff->blob);
+  GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)monitor;
 
-      /* If the file is a child of the working directory, we need to know */
-      self->is_child_of_workdir = diff->is_child_of_workdir;
-    }
+  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (monitor));
 
-  return ide_task_propagate_pointer (task, error);
+  g_clear_pointer (&self->cache, line_cache_free);
 }
 
-static void
-gbp_git_buffer_change_monitor_calculate_async (GbpGitBufferChangeMonitor *self,
-                                               GCancellable              *cancellable,
-                                               GAsyncReadyCallback        callback,
-                                               gpointer                   user_data)
+static IdeBufferLineChange
+translate_mark (LineMark mark)
 {
-  g_autoptr(IdeTask) task = NULL;
-  g_autoptr(IdeContext) context = NULL;
-  GbpGitVcs *vcs;
-  IdeBuffer *buffer;
-  DiffTask *diff;
-  GFile *file;
-
-  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
-  g_assert (self->repository != NULL);
-
-  self->state_dirty = FALSE;
-
-  task = ide_task_new (self, cancellable, callback, user_data);
-  ide_task_set_source_tag (task, gbp_git_buffer_change_monitor_calculate_async);
-
-  g_signal_connect_object (task,
-                           "notify::completed",
-                           G_CALLBACK (complete_wait_tasks),
-                           self,
-                           G_CONNECT_SWAPPED);
+  IdeBufferLineChange change = 0;
 
-  buffer = ide_buffer_change_monitor_get_buffer (IDE_BUFFER_CHANGE_MONITOR (self));
-  g_assert (IDE_IS_BUFFER (buffer));
+  if (mark & LINE_MARK_ADDED)
+    change |= IDE_BUFFER_LINE_CHANGE_ADDED;
 
-  file = ide_buffer_get_file (buffer);
-  g_assert (G_IS_FILE (file));
+  if (mark & LINE_MARK_REMOVED)
+    change |= IDE_BUFFER_LINE_CHANGE_DELETED;
 
-  context = ide_object_ref_context (IDE_OBJECT (self));
-  vcs = ide_context_peek_child_typed (context, GBP_TYPE_GIT_VCS);
+  if (mark & LINE_MARK_PREVIOUS_REMOVED)
+    change |= IDE_BUFFER_LINE_CHANGE_PREVIOUS_DELETED;
 
-  if (!GBP_IS_GIT_VCS (vcs))
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_CANCELLED,
-                                 "Cannot provide changes, not connected to GbpGitVcs.");
-      return;
-    }
+  if (mark & LINE_MARK_CHANGED)
+    change |= IDE_BUFFER_LINE_CHANGE_CHANGED;
 
-  diff = g_slice_new0 (DiffTask);
-  diff->file = g_object_ref (file);
-  diff->repository = g_object_ref (self->repository);
-  diff->cache = line_cache_new ();
-  diff->content = ide_buffer_dup_content (buffer);
-  diff->blob = self->cached_blob ? g_object_ref (self->cached_blob) : NULL;
-  diff->lock_object = g_object_ref (IDE_OBJECT (vcs));
+  return change;
+}
 
-  ide_task_set_task_data (task, diff, diff_task_free);
+static IdeBufferLineChange
+gbp_git_buffer_change_monitor_get_change (IdeBufferChangeMonitor *monitor,
+                                          guint                   line)
+{
+  GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)monitor;
 
-  self->in_calculation = TRUE;
+  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
 
-  g_async_queue_push (work_queue, g_steal_pointer (&task));
+  return self->cache ? translate_mark (line_cache_get_mark (self->cache, line)) : 0;
 }
 
 static void
-foreach_cb (gpointer data,
-            gpointer user_data)
+gbp_git_buffer_change_monitor_foreach_change_cb (gpointer data,
+                                                 gpointer user_data)
 {
   const LineEntry *entry = data;
-  struct {
+  const struct {
     IdeBufferChangeMonitorForeachFunc func;
-    gpointer user_data;
+    gpointer data;
   } *state = user_data;
-  IdeBufferLineChange change = 0;
-
-  if (entry->mark & LINE_MARK_ADDED)
-    change |= IDE_BUFFER_LINE_CHANGE_ADDED;
-
-  if (entry->mark & LINE_MARK_REMOVED)
-    change |= IDE_BUFFER_LINE_CHANGE_DELETED;
 
-  if (entry->mark & LINE_MARK_PREVIOUS_REMOVED)
-    change |= IDE_BUFFER_LINE_CHANGE_PREVIOUS_DELETED;
-
-  if (entry->mark & LINE_MARK_CHANGED)
-    change |= IDE_BUFFER_LINE_CHANGE_CHANGED;
+  g_assert (entry != NULL);
+  g_assert (state != NULL);
+  g_assert (state->func != NULL);
 
-  state->func (entry->line, change, state->user_data);
+  state->func (entry->line, translate_mark (entry->mark), state->data);
 }
 
 static void
 gbp_git_buffer_change_monitor_foreach_change (IdeBufferChangeMonitor            *monitor,
-                                              guint                              begin_line,
-                                              guint                              end_line,
+                                              guint                              line_begin,
+                                              guint                              line_end,
                                               IdeBufferChangeMonitorForeachFunc  callback,
                                               gpointer                           user_data)
 {
   GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)monitor;
-  struct {
-    IdeBufferChangeMonitorForeachFunc func;
-    gpointer user_data;
-  } state = { callback, user_data };
 
   g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
   g_assert (callback != NULL);
 
-  if (end_line == G_MAXUINT)
-    end_line--;
-
-  if (self->cache == NULL)
-    {
-      /* If within working directory, synthesize line addition. */
-      if (self->is_child_of_workdir)
-        {
-          for (guint i = begin_line; i < end_line; i++)
-            callback (i, IDE_BUFFER_LINE_CHANGE_ADDED, user_data);
-        }
-
-      return;
-    }
-
-  line_cache_foreach_in_range (self->cache, begin_line, end_line, foreach_cb, &state);
-}
-
-static IdeBufferLineChange
-gbp_git_buffer_change_monitor_get_change (IdeBufferChangeMonitor *monitor,
-                                          guint                   line)
-{
-  GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)monitor;
-  guint mark;
-
-  /* Don't imply changes we don't know are real, in the case that
-   * we failed to communicate with git properly about the blob diff.
-   */
-  if (self->in_failed_state)
-    return IDE_BUFFER_LINE_CHANGE_NONE;
-
-  if (self->cache == NULL)
-    {
-      /* If within working directory, synthesize line addition. */
-      if (self->is_child_of_workdir)
-        return IDE_BUFFER_LINE_CHANGE_ADDED;
-      return IDE_BUFFER_LINE_CHANGE_NONE;
-    }
-
-  mark = line_cache_get_mark (self->cache, line + 1);
-
-  if (mark & LINE_MARK_ADDED)
-    return IDE_BUFFER_LINE_CHANGE_ADDED;
-  else if (mark & LINE_MARK_REMOVED)
-    return IDE_BUFFER_LINE_CHANGE_DELETED;
-  else if (mark & LINE_MARK_CHANGED)
-    return IDE_BUFFER_LINE_CHANGE_CHANGED;
-  else
-    return IDE_BUFFER_LINE_CHANGE_NONE;
-}
-
-void
-gbp_git_buffer_change_monitor_set_repository (GbpGitBufferChangeMonitor *self,
-                                              GgitRepository            *repository)
-{
-  gboolean do_reload;
-
-  g_return_if_fail (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_return_if_fail (GGIT_IS_REPOSITORY (repository));
-
-  do_reload = self->repository != NULL && repository != NULL;
-
-  if (g_set_object (&self->repository, repository))
+  if (self->cache != NULL)
     {
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_REPOSITORY]);
-
-      if (do_reload)
-        ide_buffer_change_monitor_reload (IDE_BUFFER_CHANGE_MONITOR (self));
+      struct {
+        IdeBufferChangeMonitorForeachFunc func;
+        gpointer data;
+      } state = { callback, user_data };
+
+      line_cache_foreach_in_range (self->cache,
+                                   line_begin,
+                                   line_end,
+                                   gbp_git_buffer_change_monitor_foreach_change_cb,
+                                   &state);
     }
 }
 
 static void
-gbp_git_buffer_change_monitor__calculate_cb (GObject      *object,
-                                             GAsyncResult *result,
-                                             gpointer      user_data_unused)
+gbp_git_buffer_change_monitor_insert_text_after_cb (GbpGitBufferChangeMonitor *self,
+                                                    const GtkTextIter         *location,
+                                                    gchar                     *text,
+                                                    gint                       len,
+                                                    IdeBuffer                 *buffer)
 {
-  GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)object;
-  LineCache *cache;
-  g_autoptr(GError) error = NULL;
+  IdeBufferLineChange change;
+  guint line;
 
   g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_assert (user_data_unused == NULL);
-
-  self->in_calculation = FALSE;
-
-  cache = gbp_git_buffer_change_monitor_calculate_finish (self, result, &error);
-
-  if (cache == NULL)
-    {
-      if (!self->in_failed_state && !g_error_matches (error, GGIT_ERROR, GGIT_ERROR_NOTFOUND))
-        {
-          ide_object_warning (self,
-                              /* translators: %s is replaced with the error string from git */
-                              _("There was a failure while calculating line changes from git. The exact 
error was: %s"),
-                              error->message);
-          self->in_failed_state = TRUE;
-        }
-    }
-  else
-    {
-      g_clear_pointer (&self->cache, line_cache_free);
-      self->cache = g_steal_pointer (&cache);
-      self->in_failed_state = FALSE;
-    }
+  g_assert (location != NULL);
+  g_assert (text != NULL);
+  g_assert (IDE_IS_BUFFER (buffer));
 
-  ide_buffer_change_monitor_emit_changed (IDE_BUFFER_CHANGE_MONITOR (self));
+  /*
+   * We need to recalculate the diff when text is inserted if:
+   *
+   * 1) A newline is included in the text.
+   * 2) The line currently has flags of NONE.
+   *
+   * Technically we need to do it on every change to be more correct, but that wastes a lot of
+   * power. So instead, we'll be a bit lazy about it here and pick up the other changes on a much
+   * more conservative timeout, generated by gbp_git_buffer_change_monitor__buffer_changed_cb().
+   */
 
-  /* Recalculate if the buffer has changed since last request. */
-  if (self->state_dirty)
-    gbp_git_buffer_change_monitor_calculate_async (self,
-                                                   NULL,
-                                                   gbp_git_buffer_change_monitor__calculate_cb,
-                                                   NULL);
-}
+  if (NULL != memmem (text, len, "\n", 1))
+    goto recalculate;
 
-static void
-gbp_git_buffer_change_monitor_recalculate (GbpGitBufferChangeMonitor *self)
-{
-  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
+  line = gtk_text_iter_get_line (location);
+  change = gbp_git_buffer_change_monitor_get_change (IDE_BUFFER_CHANGE_MONITOR (self), line);
+  if (change == IDE_BUFFER_LINE_CHANGE_NONE)
+    goto recalculate;
 
-  self->state_dirty = TRUE;
+  return;
 
-  if (!self->in_calculation)
-    gbp_git_buffer_change_monitor_calculate_async (self,
-                                                   NULL,
-                                                   gbp_git_buffer_change_monitor__calculate_cb,
-                                                   NULL);
+recalculate:
+  gbp_git_buffer_change_monitor_recalculate (self);
 }
 
 static void
-gbp_git_buffer_change_monitor__buffer_delete_range_after_cb (GbpGitBufferChangeMonitor *self,
-                                                             GtkTextIter               *begin,
-                                                             GtkTextIter               *end,
-                                                             IdeBuffer                 *buffer)
+gbp_git_buffer_change_monitor_delete_range_after_cb (GbpGitBufferChangeMonitor *self,
+                                                     GtkTextIter               *begin,
+                                                     GtkTextIter               *end,
+                                                     IdeBuffer                 *buffer)
 {
-  IDE_ENTRY;
-
   g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_assert (begin);
-  g_assert (end);
+  g_assert (begin != NULL);
+  g_assert (end != NULL);
   g_assert (IDE_IS_BUFFER (buffer));
 
   if (self->delete_range_requires_recalculation)
@@ -456,19 +202,16 @@ gbp_git_buffer_change_monitor__buffer_delete_range_after_cb (GbpGitBufferChangeM
       self->delete_range_requires_recalculation = FALSE;
       gbp_git_buffer_change_monitor_recalculate (self);
     }
-
-  IDE_EXIT;
 }
 
 static void
-gbp_git_buffer_change_monitor__buffer_delete_range_cb (GbpGitBufferChangeMonitor *self,
-                                                       GtkTextIter               *begin,
-                                                       GtkTextIter               *end,
-                                                       IdeBuffer                 *buffer)
+gbp_git_buffer_change_monitor_delete_range_cb (GbpGitBufferChangeMonitor *self,
+                                               GtkTextIter               *begin,
+                                               GtkTextIter               *end,
+                                               IdeBuffer                 *buffer)
 {
   IdeBufferLineChange change;
-
-  IDE_ENTRY;
+  guint line;
 
   g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
   g_assert (begin != NULL);
@@ -481,20 +224,21 @@ gbp_git_buffer_change_monitor__buffer_delete_range_cb (GbpGitBufferChangeMonitor
    * 1) The range includes a newline.
    * 2) The current line change is set to NONE.
    *
-   * Technically we need to do it on every change to be more correct, but that wastes a lot of
-   * power. So instead, we'll be a bit lazy about it here and pick up the other changes on a much
-   * more conservative timeout, generated by gbp_git_buffer_change_monitor__buffer_changed_cb().
+   * Technically we need to do it on every change to be more correct, but
+   * that wastes a lot of power. So instead, we'll be a bit lazy about it
+   * here and pick up the other changes on a much more conservative timeout,
+   * generated by gbp_git_buffer_change_monitor__buffer_changed_cb().
    */
 
   if (gtk_text_iter_get_line (begin) != gtk_text_iter_get_line (end))
-    IDE_GOTO (recalculate);
+    goto recalculate;
 
-  change = gbp_git_buffer_change_monitor_get_change (IDE_BUFFER_CHANGE_MONITOR (self),
-                                                     gtk_text_iter_get_line (begin));
+  line = gtk_text_iter_get_line (begin);
+  change = gbp_git_buffer_change_monitor_get_change (IDE_BUFFER_CHANGE_MONITOR (self), line);
   if (change == IDE_BUFFER_LINE_CHANGE_NONE)
-    IDE_GOTO (recalculate);
+    goto recalculate;
 
-  IDE_EXIT;
+  return;
 
 recalculate:
   /*
@@ -502,340 +246,16 @@ recalculate:
    * gbp_git_buffer_change_monitor__buffer_delete_range_after_cb perform the operation.
    */
   self->delete_range_requires_recalculation = TRUE;
-
-  IDE_EXIT;
-}
-
-static void
-gbp_git_buffer_change_monitor__buffer_insert_text_after_cb (GbpGitBufferChangeMonitor *self,
-                                                            GtkTextIter               *location,
-                                                            gchar                     *text,
-                                                            gint                       len,
-                                                            IdeBuffer                 *buffer)
-{
-  IdeBufferLineChange change;
-
-  IDE_ENTRY;
-
-  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_assert (location);
-  g_assert (text);
-  g_assert (IDE_IS_BUFFER (buffer));
-
-  /*
-   * We need to recalculate the diff when text is inserted if:
-   *
-   * 1) A newline is included in the text.
-   * 2) The line currently has flags of NONE.
-   *
-   * Technically we need to do it on every change to be more correct, but that wastes a lot of
-   * power. So instead, we'll be a bit lazy about it here and pick up the other changes on a much
-   * more conservative timeout, generated by gbp_git_buffer_change_monitor__buffer_changed_cb().
-   */
-
-  if (NULL != memmem (text, len, "\n", 1))
-    IDE_GOTO (recalculate);
-
-  change = gbp_git_buffer_change_monitor_get_change (IDE_BUFFER_CHANGE_MONITOR (self),
-                                                     gtk_text_iter_get_line (location));
-  if (change == IDE_BUFFER_LINE_CHANGE_NONE)
-    IDE_GOTO (recalculate);
-
-  IDE_EXIT;
-
-recalculate:
-  gbp_git_buffer_change_monitor_recalculate (self);
-
-  IDE_EXIT;
-}
-
-static gboolean
-gbp_git_buffer_change_monitor__changed_timeout_cb (gpointer user_data)
-{
-  GbpGitBufferChangeMonitor *self = user_data;
-
-  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-
-  self->changed_timeout = 0;
-  gbp_git_buffer_change_monitor_recalculate (self);
-
-  return G_SOURCE_REMOVE;
 }
 
 static void
-gbp_git_buffer_change_monitor__buffer_changed_after_cb (GbpGitBufferChangeMonitor *self,
-                                                        IdeBuffer                 *buffer)
+gbp_git_buffer_change_monitor_change_settled_cb (GbpGitBufferChangeMonitor *self,
+                                                 IdeBuffer                 *buffer)
 {
   g_assert (IDE_IS_BUFFER_CHANGE_MONITOR (self));
   g_assert (IDE_IS_BUFFER (buffer));
 
-  self->state_dirty = TRUE;
-
-  if (self->in_calculation)
-    return;
-
-  dzl_clear_source (&self->changed_timeout);
-  self->changed_timeout = g_timeout_add_seconds (DELAY_CHANGED_SEC,
-                                                 gbp_git_buffer_change_monitor__changed_timeout_cb,
-                                                 self);
-}
-
-static void
-gbp_git_buffer_change_monitor_reload (IdeBufferChangeMonitor *monitor)
-{
-  GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)monitor;
-
-  IDE_ENTRY;
-
-  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-
-  g_clear_object (&self->cached_blob);
   gbp_git_buffer_change_monitor_recalculate (self);
-
-  IDE_EXIT;
-}
-
-static void
-gbp_git_buffer_change_monitor_load (IdeBufferChangeMonitor *monitor,
-                                    IdeBuffer              *buffer)
-{
-  GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)monitor;
-
-  IDE_ENTRY;
-
-  g_return_if_fail (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_return_if_fail (IDE_IS_BUFFER (buffer));
-
-  dzl_signal_group_set_target (self->signal_group, buffer);
-
-  IDE_EXIT;
-}
-
-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 gboolean
-gbp_git_buffer_change_monitor_calculate_threaded (GbpGitBufferChangeMonitor  *self,
-                                                  DiffTask                   *diff,
-                                                  GError                    **error)
-{
-  g_autofree gchar *relative_path = NULL;
-  g_autoptr(GgitDiffOptions) options = NULL;
-  g_autoptr(GFile) workdir = NULL;
-  g_autoptr(GArray) ranges = NULL;
-  LineCache *cache;
-  const guint8 *data;
-  gsize data_len = 0;
-
-  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-  g_assert (diff != NULL);
-  g_assert (G_IS_FILE (diff->file));
-  g_assert (diff->cache != NULL);
-  g_assert (GGIT_IS_REPOSITORY (diff->repository));
-  g_assert (diff->content != NULL);
-  g_assert (!diff->blob || GGIT_IS_BLOB (diff->blob));
-  g_assert (error != NULL);
-  g_assert (*error == NULL);
-
-  workdir = ggit_repository_get_workdir (diff->repository);
-
-  if (!workdir)
-    {
-      g_set_error (error,
-                   G_IO_ERROR,
-                   G_IO_ERROR_INVALID_FILENAME,
-                   _("Repository does not have a working directory."));
-      return FALSE;
-    }
-
-  relative_path = g_file_get_relative_path (workdir, diff->file);
-
-  if (!relative_path)
-    {
-      g_set_error (error,
-                   G_IO_ERROR,
-                   G_IO_ERROR_INVALID_FILENAME,
-                   _("File is not under control of git working directory."));
-      return FALSE;
-    }
-
-  diff->is_child_of_workdir = TRUE;
-
-  /*
-   * Find the blob if necessary. This will be cached by the main thread for
-   * us on the way out of the async operation.
-   */
-  if (diff->blob == NULL)
-    {
-      GgitOId *entry_oid = NULL;
-      GgitOId *oid = NULL;
-      GgitObject *blob = NULL;
-      GgitObject *commit = NULL;
-      GgitRef *head = NULL;
-      GgitTree *tree = NULL;
-      GgitTreeEntry *entry = NULL;
-
-      head = ggit_repository_get_head (diff->repository, error);
-      if (!head)
-        goto cleanup;
-
-      oid = ggit_ref_get_target (head);
-      if (!oid)
-        goto cleanup;
-
-      commit = ggit_repository_lookup (diff->repository, oid, GGIT_TYPE_COMMIT, error);
-      if (!commit)
-        goto cleanup;
-
-      tree = ggit_commit_get_tree (GGIT_COMMIT (commit));
-      if (!tree)
-        goto cleanup;
-
-      entry = ggit_tree_get_by_path (tree, relative_path, error);
-      if (!entry)
-        goto cleanup;
-
-      entry_oid = ggit_tree_entry_get_id (entry);
-      if (!entry_oid)
-        goto cleanup;
-
-      blob = ggit_repository_lookup (diff->repository, entry_oid, GGIT_TYPE_BLOB, error);
-      if (!blob)
-        goto cleanup;
-
-      diff->blob = g_object_ref (GGIT_BLOB (blob));
-
-    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);
-    }
-
-  if (diff->blob == NULL)
-    {
-      if (*error == NULL)
-        g_set_error (error,
-                     G_IO_ERROR,
-                     G_IO_ERROR_NOT_FOUND,
-                     _("The requested file does not exist within the git index."));
-      return FALSE;
-    }
-
-  data = g_bytes_get_data (diff->content, &data_len);
-
-  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 (diff->blob,
-                            relative_path,
-                            data,
-                            data_len,
-                            relative_path,
-                            options,
-                            NULL,         /* File Callback */
-                            NULL,         /* Binary Callback */
-                            diff_hunk_cb, /* Hunk Callback */
-                            NULL,
-                            ranges,
-                            error);
-
-  cache = diff->cache;
-
-  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);
-        }
-    }
-
-  return *error == NULL;
-}
-
-static gpointer
-gbp_git_buffer_change_monitor_worker (gpointer data)
-{
-  GAsyncQueue *queue = data;
-  gpointer taskptr;
-
-  g_assert (queue != NULL);
-
-  /*
-   * This is a single thread worker that dispatches the particular
-   * change to the given change monitor. We require a single thread
-   * so that we can mantain the invariant that only a single thread
-   * can access a GgitRepository at a time (and change monitors all
-   * share the same GgitRepository amongst themselves).
-   */
-
-  while ((taskptr = g_async_queue_pop (queue)))
-    {
-      GbpGitBufferChangeMonitor *self;
-      g_autoptr(GError) error = NULL;
-      g_autoptr(IdeTask) task = taskptr;
-      DiffTask *diff;
-      gboolean ret;
-
-      self = ide_task_get_source_object (task);
-      g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
-
-      diff = ide_task_get_task_data (task);
-      g_assert (diff != NULL);
-
-      /* Acquire the lock for the parent to ensure we have access to repository */
-      ide_object_lock (diff->lock_object);
-      ret = gbp_git_buffer_change_monitor_calculate_threaded (self, diff, &error);
-      ide_object_unlock (diff->lock_object);
-
-      if (!ret)
-        ide_task_return_error (task, g_steal_pointer (&error));
-      else
-        ide_task_return_pointer (task,
-                                 g_steal_pointer (&diff->cache),
-                                 line_cache_free);
-    }
-
-  return NULL;
 }
 
 static void
@@ -843,138 +263,136 @@ gbp_git_buffer_change_monitor_destroy (IdeObject *object)
 {
   GbpGitBufferChangeMonitor *self = (GbpGitBufferChangeMonitor *)object;
 
-  dzl_clear_source (&self->changed_timeout);
+  g_clear_pointer (&self->cache, line_cache_free);
 
-  if (self->signal_group)
+  if (self->signals != NULL)
     {
-      dzl_signal_group_set_target (self->signal_group, NULL);
-      g_clear_object (&self->signal_group);
+      dzl_signal_group_set_target (self->signals, NULL);
+      g_clear_object (&self->signals);
     }
 
-  g_clear_object (&self->cached_blob);
-  g_clear_object (&self->repository);
-  g_clear_pointer (&self->cache, line_cache_free);
-
   IDE_OBJECT_CLASS (gbp_git_buffer_change_monitor_parent_class)->destroy (object);
 }
 
-static void
-gbp_git_buffer_change_monitor_finalize (GObject *object)
-{
-  G_OBJECT_CLASS (gbp_git_buffer_change_monitor_parent_class)->finalize (object);
-
-  DZL_COUNTER_DEC (instances);
-}
-
-static void
-gbp_git_buffer_change_monitor_set_property (GObject      *object,
-                                            guint         prop_id,
-                                            const GValue *value,
-                                            GParamSpec   *pspec)
-{
-  GbpGitBufferChangeMonitor *self = GBP_GIT_BUFFER_CHANGE_MONITOR (object);
-
-  switch (prop_id)
-    {
-    case PROP_REPOSITORY:
-      gbp_git_buffer_change_monitor_set_repository (self, g_value_get_object (value));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
 static void
 gbp_git_buffer_change_monitor_class_init (GbpGitBufferChangeMonitorClass *klass)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeBufferChangeMonitorClass *monitor_class = IDE_BUFFER_CHANGE_MONITOR_CLASS (klass);
   IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
-  IdeBufferChangeMonitorClass *parent_class = IDE_BUFFER_CHANGE_MONITOR_CLASS (klass);
-
-  object_class->finalize = gbp_git_buffer_change_monitor_finalize;
-  object_class->set_property = gbp_git_buffer_change_monitor_set_property;
 
   i_object_class->destroy = gbp_git_buffer_change_monitor_destroy;
 
-  parent_class->load = gbp_git_buffer_change_monitor_load;
-  parent_class->get_change = gbp_git_buffer_change_monitor_get_change;
-  parent_class->reload = gbp_git_buffer_change_monitor_reload;
-  parent_class->foreach_change = gbp_git_buffer_change_monitor_foreach_change;
-
-  properties [PROP_REPOSITORY] =
-    g_param_spec_object ("repository",
-                         "Repository",
-                         "The repository to use for calculating diffs.",
-                         GGIT_TYPE_REPOSITORY,
-                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_properties (object_class, LAST_PROP, properties);
-
-  /* Note: We use a single worker thread so that we can maintain the
-   *       invariant that only a single thread is touching the GgitRepository
-   *       at a time. (Also, you can only type in one editor at a time, so
-   *       on worker thread for interactive blob changes is fine.
-   */
-  work_queue = g_async_queue_new ();
-  work_thread = g_thread_new ("GbpGitBufferChangeMonitorWorker",
-                              gbp_git_buffer_change_monitor_worker,
-                              work_queue);
+  monitor_class->load = gbp_git_buffer_change_monitor_load;
+  monitor_class->reload = gbp_git_buffer_change_monitor_reload;
+  monitor_class->get_change = gbp_git_buffer_change_monitor_get_change;
+  monitor_class->foreach_change = gbp_git_buffer_change_monitor_foreach_change;
 }
 
 static void
 gbp_git_buffer_change_monitor_init (GbpGitBufferChangeMonitor *self)
 {
-  DZL_COUNTER_INC (instances);
+  self->signals = dzl_signal_group_new (IDE_TYPE_BUFFER);
 
-  self->signal_group = dzl_signal_group_new (IDE_TYPE_BUFFER);
-  dzl_signal_group_connect_object (self->signal_group,
+  dzl_signal_group_connect_object (self->signals,
                                    "insert-text",
-                                   G_CALLBACK (gbp_git_buffer_change_monitor__buffer_insert_text_after_cb),
+                                   G_CALLBACK (gbp_git_buffer_change_monitor_insert_text_after_cb),
                                    self,
                                    G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-  dzl_signal_group_connect_object (self->signal_group,
+  dzl_signal_group_connect_object (self->signals,
                                    "delete-range",
-                                   G_CALLBACK (gbp_git_buffer_change_monitor__buffer_delete_range_cb),
+                                   G_CALLBACK (gbp_git_buffer_change_monitor_delete_range_cb),
                                    self,
                                    G_CONNECT_SWAPPED);
-  dzl_signal_group_connect_object (self->signal_group,
+  dzl_signal_group_connect_object (self->signals,
                                    "delete-range",
-                                   G_CALLBACK (gbp_git_buffer_change_monitor__buffer_delete_range_after_cb),
+                                   G_CALLBACK (gbp_git_buffer_change_monitor_delete_range_after_cb),
                                    self,
                                    G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-  dzl_signal_group_connect_object (self->signal_group,
-                                   "changed",
-                                   G_CALLBACK (gbp_git_buffer_change_monitor__buffer_changed_after_cb),
+  dzl_signal_group_connect_object (self->signals,
+                                   "change-settled",
+                                   G_CALLBACK (gbp_git_buffer_change_monitor_change_settled_cb),
                                    self,
                                    G_CONNECT_SWAPPED | G_CONNECT_AFTER);
 }
 
+static void
+gbp_git_buffer_change_monitor_wait_cb (GObject      *object,
+                                       GAsyncResult *result,
+                                       gpointer      user_data)
+{
+  GbpGitClient *client = (GbpGitClient *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  GbpGitBufferChangeMonitor *self;
+  LineCache *cache;
+
+  g_assert (GBP_IS_GIT_CLIENT (client));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!(cache = gbp_git_client_get_changes_finish (client, result, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  self = ide_task_get_source_object (task);
+  g_assert (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
+  g_clear_pointer (&self->cache, line_cache_free);
+  self->cache = g_steal_pointer (&cache);
+
+  ide_task_return_boolean (task, TRUE);
+
+  ide_buffer_change_monitor_emit_changed (IDE_BUFFER_CHANGE_MONITOR (self));
+}
+
 void
 gbp_git_buffer_change_monitor_wait_async (GbpGitBufferChangeMonitor *self,
                                           GCancellable              *cancellable,
                                           GAsyncReadyCallback        callback,
                                           gpointer                   user_data)
 {
+  g_autofree gchar *path = NULL;
   g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GBytes) bytes = NULL;
+  g_autoptr(GFile) workdir = NULL;
+  GbpGitClient *client;
+  IdeContext *context;
+  IdeBuffer *buffer;
+  GFile *file;
 
-  g_return_if_fail (IDE_IS_MAIN_THREAD ());
   g_return_if_fail (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
+  self->delete_range_requires_recalculation = FALSE;
+
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, gbp_git_buffer_change_monitor_wait_async);
 
-  if (!self->state_dirty && !self->in_calculation)
+  buffer = dzl_signal_group_get_target (self->signals);
+  context = ide_object_get_context (IDE_OBJECT (self));
+  workdir = ide_context_ref_workdir (context);
+  file = ide_buffer_get_file (buffer);
+
+  if (!g_file_has_prefix (file, workdir))
     {
-      ide_task_return_boolean (task, TRUE);
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_SUPPORTED,
+                                 "File is not within project directory");
       return;
     }
 
-  g_queue_push_tail (&self->wait_tasks, g_steal_pointer (&task));
+  path = g_file_get_relative_path (workdir, file);
+  bytes = ide_buffer_dup_content (buffer);
+  client = gbp_git_client_from_context (context);
 
-  if (!self->in_calculation)
-    gbp_git_buffer_change_monitor_recalculate (self);
+  gbp_git_client_get_changes_async (client,
+                                    path,
+                                    (const gchar *)g_bytes_get_data (bytes, NULL),
+                                    cancellable,
+                                    gbp_git_buffer_change_monitor_wait_cb,
+                                    g_steal_pointer (&task));
 }
 
 gboolean
@@ -982,7 +400,6 @@ gbp_git_buffer_change_monitor_wait_finish (GbpGitBufferChangeMonitor  *self,
                                            GAsyncResult               *result,
                                            GError                    **error)
 {
-  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
   g_return_val_if_fail (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (self), FALSE);
   g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
 
diff --git a/src/plugins/git/gbp-git-buffer-change-monitor.h b/src/plugins/git/gbp-git-buffer-change-monitor.h
index c5e620d7d..c35e2869f 100644
--- a/src/plugins/git/gbp-git-buffer-change-monitor.h
+++ b/src/plugins/git/gbp-git-buffer-change-monitor.h
@@ -20,7 +20,6 @@
 
 #pragma once
 
-#include <libgit2-glib/ggit.h>
 #include <libide-code.h>
 
 G_BEGIN_DECLS
@@ -29,14 +28,12 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (GbpGitBufferChangeMonitor, gbp_git_buffer_change_monitor, GBP, 
GIT_BUFFER_CHANGE_MONITOR, IdeBufferChangeMonitor)
 
-void     gbp_git_buffer_change_monitor_set_repository (GbpGitBufferChangeMonitor  *self,
-                                                       GgitRepository             *repository);
-void     gbp_git_buffer_change_monitor_wait_async     (GbpGitBufferChangeMonitor  *self,
-                                                       GCancellable               *cancellable,
-                                                       GAsyncReadyCallback         callback,
-                                                       gpointer                    user_data);
-gboolean gbp_git_buffer_change_monitor_wait_finish    (GbpGitBufferChangeMonitor  *self,
-                                                       GAsyncResult               *result,
-                                                       GError                    **error);
+void     gbp_git_buffer_change_monitor_wait_async  (GbpGitBufferChangeMonitor  *self,
+                                                    GCancellable               *cancellable,
+                                                    GAsyncReadyCallback         callback,
+                                                    gpointer                    user_data);
+gboolean gbp_git_buffer_change_monitor_wait_finish (GbpGitBufferChangeMonitor  *self,
+                                                    GAsyncResult               *result,
+                                                    GError                    **error);
 
 G_END_DECLS
diff --git a/src/plugins/git/gbp-git-client.c b/src/plugins/git/gbp-git-client.c
index 991319495..fda635cad 100644
--- a/src/plugins/git/gbp-git-client.c
+++ b/src/plugins/git/gbp-git-client.c
@@ -90,7 +90,7 @@ gbp_git_client_notification_cb (GbpGitClient  *self,
   IdeNotification *notif;
 
   g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (GBP_IS_GIT_CLIENT (client));
+  g_assert (GBP_IS_GIT_CLIENT (self));
   g_assert (command != NULL);
   g_assert (JSONRPC_IS_CLIENT (client));
 
diff --git a/src/plugins/git/gbp-git-client.h b/src/plugins/git/gbp-git-client.h
index efc2d369f..2390d8e94 100644
--- a/src/plugins/git/gbp-git-client.h
+++ b/src/plugins/git/gbp-git-client.h
@@ -48,6 +48,9 @@ gboolean      gbp_git_client_call_finish              (GbpGitClient         *sel
                                                        GAsyncResult         *result,
                                                        GVariant            **reply,
                                                        GError              **error);
+gboolean      gbp_git_client_is_ignored               (GbpGitClient         *self,
+                                                       const gchar          *path,
+                                                       GError              **error);
 void          gbp_git_client_is_ignored_async         (GbpGitClient         *self,
                                                        const gchar          *path,
                                                        GCancellable         *cancellable,
diff --git a/src/plugins/git/gbp-git-vcs.c b/src/plugins/git/gbp-git-vcs.c
index b70e0f683..ee9adf1ca 100644
--- a/src/plugins/git/gbp-git-vcs.c
+++ b/src/plugins/git/gbp-git-vcs.c
@@ -25,6 +25,7 @@
 #include <stdlib.h>
 
 #include "gbp-git-branch.h"
+#include "gbp-git-client.h"
 #include "gbp-git-tag.h"
 #include "gbp-git-vcs.h"
 #include "gbp-git-vcs-config.h"
@@ -32,7 +33,7 @@
 struct _GbpGitVcs
 {
   IdeObject       parent_instance;
-  GgitRepository *repository;
+  GbpGitClient   *client;
   GFile          *location;
   GFile          *workdir;
   gchar          *branch;
@@ -54,12 +55,29 @@ G_DEFINE_TYPE_WITH_CODE (GbpGitVcs, gbp_git_vcs, IDE_TYPE_OBJECT,
 
 static GParamSpec *properties [N_PROPS];
 
+static void
+gbp_git_vcs_parent_set (IdeObject *object,
+                        IdeObject *parent)
+{
+  GbpGitVcs *self = (GbpGitVcs *)object;
+  g_autoptr(IdeContext) context = NULL;
+
+  g_assert (GBP_IS_GIT_VCS (self));
+  g_assert (!object || IDE_IS_OBJECT (parent));
+
+  if (object == NULL)
+    return;
+
+  context = ide_object_ref_context (IDE_OBJECT (self));
+  self->client = g_object_ref (gbp_git_client_from_context (context));
+}
+
 static void
 gbp_git_vcs_finalize (GObject *object)
 {
   GbpGitVcs *self = (GbpGitVcs *)object;
 
-  g_clear_object (&self->repository);
+  g_clear_object (&self->client);
   g_clear_object (&self->location);
   g_clear_object (&self->workdir);
   g_clear_pointer (&self->branch, g_free);
@@ -85,10 +103,6 @@ gbp_git_vcs_get_property (GObject    *object,
       g_value_set_object (value, self->location);
       break;
 
-    case PROP_REPOSITORY:
-      g_value_set_object (value, self->repository);
-      break;
-
     case PROP_WORKDIR:
       g_value_set_object (value, self->workdir);
       break;
@@ -116,10 +130,6 @@ gbp_git_vcs_set_property (GObject      *object,
       self->location = g_value_dup_object (value);
       break;
 
-    case PROP_REPOSITORY:
-      self->repository = g_value_dup_object (value);
-      break;
-
     case PROP_WORKDIR:
       self->workdir = g_value_dup_object (value);
       break;
@@ -133,11 +143,14 @@ static void
 gbp_git_vcs_class_init (GbpGitVcsClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
 
   object_class->finalize = gbp_git_vcs_finalize;
   object_class->get_property = gbp_git_vcs_get_property;
   object_class->set_property = gbp_git_vcs_set_property;
 
+  i_object_class->parent_set = gbp_git_vcs_parent_set;
+
   properties [PROP_BRANCH_NAME] =
     g_param_spec_string ("branch-name",
                          "Branch Name",
@@ -152,13 +165,6 @@ gbp_git_vcs_class_init (GbpGitVcsClass *klass)
                          G_TYPE_FILE,
                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 
-  properties [PROP_REPOSITORY] =
-    g_param_spec_object ("repository",
-                         "Repository",
-                         "The underlying repository object",
-                         GGIT_TYPE_REPOSITORY,
-                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-
   properties [PROP_WORKDIR] =
     g_param_spec_object ("workdir",
                          "Workdir",
@@ -181,13 +187,6 @@ gbp_git_vcs_get_location (GbpGitVcs *self)
   return self->location;
 }
 
-GgitRepository *
-gbp_git_vcs_get_repository (GbpGitVcs *self)
-{
-  g_return_val_if_fail (GBP_IS_GIT_VCS (self), NULL);
-  return self->repository;
-}
-
 static GFile *
 gbp_git_vcs_get_workdir (IdeVcs *vcs)
 {
@@ -233,23 +232,15 @@ gbp_git_vcs_is_ignored (IdeVcs  *vcs,
   /* self->workdir is not changed after creation, so safe
    * to access it from a thread.
    */
-  name = g_file_get_relative_path (self->workdir, file);
-  if (g_strcmp0 (name, ".git") == 0)
-    return TRUE;
-
-  /*
-   * If we have a valid name to work with, we want to query the
-   * repository. But this could be called from a thread, so ensure
-   * we are the only thread accessing self->repository right now.
-   */
-  if (name != NULL)
+  if ((name = g_file_get_relative_path (self->workdir, file)))
     {
-      ide_object_lock (IDE_OBJECT (self));
-      ret = ggit_repository_path_is_ignored (self->repository, name, error);
-      ide_object_unlock (IDE_OBJECT (self));
+      if (g_strcmp0 (name, ".git") == 0 ||
+          g_str_has_prefix (name, ".git/") ||
+          gbp_git_client_is_ignored (self->client, file, error))
+        return TRUE;
     }
 
-  return ret;
+  return FALSE;
 }
 
 typedef struct
diff --git a/src/plugins/git/gbp-git-vcs.h b/src/plugins/git/gbp-git-vcs.h
index cb337921a..843dae7ad 100644
--- a/src/plugins/git/gbp-git-vcs.h
+++ b/src/plugins/git/gbp-git-vcs.h
@@ -20,7 +20,6 @@
 
 #pragma once
 
-#include <libgit2-glib/ggit.h>
 #include <libide-vcs.h>
 
 G_BEGIN_DECLS
@@ -30,7 +29,6 @@ G_BEGIN_DECLS
 G_DECLARE_FINAL_TYPE (GbpGitVcs, gbp_git_vcs, GBP, GIT_VCS, IdeObject)
 
 GFile          *gbp_git_vcs_get_location   (GbpGitVcs            *self);
-GgitRepository *gbp_git_vcs_get_repository (GbpGitVcs            *self);
 void            gbp_git_vcs_reload_async   (GbpGitVcs            *self,
                                             GCancellable         *cancellable,
                                             GAsyncReadyCallback   callback,
diff --git a/src/plugins/git/gbp-git-workbench-addin.c b/src/plugins/git/gbp-git-workbench-addin.c
index 58a14a6cc..7826a6097 100644
--- a/src/plugins/git/gbp-git-workbench-addin.c
+++ b/src/plugins/git/gbp-git-workbench-addin.c
@@ -46,7 +46,7 @@ gbp_git_workbench_addin_discover_cb (GObject      *object,
                                      gpointer      user_data)
 {
   GbpGitClient *client = (GbpGitClient *)object;
-  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(IdeTask) task = user_data;
   g_autoptr(GError) error = NULL;
   g_autoptr(IdeVcs) vcs = NULL;
   g_autoptr(GFile) workdir = NULL;
@@ -123,18 +123,15 @@ static void
 gbp_git_workbench_addin_foreach_buffer_cb (IdeBuffer *buffer,
                                            gpointer   user_data)
 {
-  GgitRepository *repository = user_data;
   IdeBufferChangeMonitor *monitor;
 
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_BUFFER (buffer));
-  g_assert (GGIT_IS_REPOSITORY (repository));
 
   monitor = ide_buffer_get_change_monitor (buffer);
 
   if (GBP_IS_GIT_BUFFER_CHANGE_MONITOR (monitor))
-    gbp_git_buffer_change_monitor_set_repository (GBP_GIT_BUFFER_CHANGE_MONITOR (monitor),
-                                                  repository);
+    ide_buffer_change_monitor_reload (monitor);
 }
 
 static void
@@ -146,7 +143,6 @@ gbp_git_workbench_addin_reload_cb (GObject      *object,
   g_autoptr(GbpGitWorkbenchAddin) self = user_data;
   g_autoptr(GError) error = NULL;
   IdeBufferManager *buffer_manager;
-  GgitRepository *repository;
   IdeContext *context;
 
   g_assert (IDE_IS_MAIN_THREAD ());
@@ -160,13 +156,12 @@ gbp_git_workbench_addin_reload_cb (GObject      *object,
   if (self->workbench == NULL)
     return;
 
-  repository = gbp_git_vcs_get_repository (vcs);
   context = ide_workbench_get_context (self->workbench);
   buffer_manager = ide_buffer_manager_from_context (context);
 
   ide_buffer_manager_foreach (buffer_manager,
                               gbp_git_workbench_addin_foreach_buffer_cb,
-                              repository);
+                              NULL);
 }
 
 static void
diff --git a/src/plugins/git/gbp-git.c b/src/plugins/git/gbp-git.c
index 679ef112d..e677b4217 100644
--- a/src/plugins/git/gbp-git.c
+++ b/src/plugins/git/gbp-git.c
@@ -18,6 +18,31 @@
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
 
+/* Some code within this file is based upon libgit2 usage from GitHub. Their
+ * license is retained below, however the combined work is subject to the
+ * GPLv2+ as noted above.
+ *
+ * Copyright (c) 2014 GitHub Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
 #define G_LOG_DOMAIN "gbp-git"
 
 #include "config.h"
@@ -111,6 +136,9 @@ gbp_git_monitor_changed_cb (GbpGit             *self,
   g_assert (GBP_IS_GIT (self));
   g_assert (GBP_IS_GIT_INDEX_MONITOR (monitor));
 
+  g_clear_object (&self->last_blob);
+  g_clear_pointer (&self->last_blob_path, g_free);
+
   g_signal_emit (self, signals [CHANGED], 0);
 }
 
@@ -129,6 +157,8 @@ gbp_git_set_workdir (GbpGit *self,
     {
       g_clear_object (&self->repository);
       g_clear_object (&self->monitor);
+      g_clear_object (&self->last_blob);
+      g_clear_pointer (&self->last_blob_path, g_free);
 
       if (workdir != NULL)
         {


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