[gnome-builder: 46/139] libide-vcs: add libide-vcs static library



commit 8d41d9a502896e1f7d8cd984d74c738286ff261a
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jan 9 16:36:20 2019 -0800

    libide-vcs: add libide-vcs static library
    
    This breaks out the vcs components into a new static library. It also
    moves some editor components into a plugin vcsui to bridge between the
    libide-vcs layer and the libide-editor layer.
    
    Some simplification of the IdeVcs occurred, and will likely gain more
    changes as we add support for more VCS features.

 src/libide/vcs/ide-directory-vcs.c              | 180 +++++++++++
 src/libide/vcs/ide-directory-vcs.h              |  36 +++
 src/libide/vcs/ide-vcs-cloner.c                 | 148 +++++++++
 src/libide/vcs/ide-vcs-cloner.h                 |  73 +++++
 src/libide/vcs/ide-vcs-config.c                 |   3 +-
 src/libide/vcs/ide-vcs-config.h                 |   6 +-
 src/libide/vcs/ide-vcs-file-info.c              |   7 +-
 src/libide/vcs/ide-vcs-file-info.h              |   6 +-
 src/libide/vcs/ide-vcs-initializer.c            |   2 +-
 src/libide/vcs/ide-vcs-initializer.h            |   6 +-
 src/libide/vcs/ide-vcs-monitor.c                | 392 +++++++++++++++++-------
 src/libide/vcs/ide-vcs-monitor.h                |  22 +-
 src/libide/vcs/ide-vcs-uri.c                    |  59 +++-
 src/libide/vcs/ide-vcs-uri.h                    |  50 +--
 src/libide/vcs/ide-vcs.c                        | 260 +++++-----------
 src/libide/vcs/ide-vcs.h                        |  29 +-
 src/libide/vcs/libide-vcs.h                     |  38 +++
 src/libide/vcs/meson.build                      |  83 ++++-
 src/plugins/vcsui/gbp-vcsui-editor-page-addin.c | 137 +++++++++
 src/plugins/vcsui/gbp-vcsui-editor-page-addin.h |  31 ++
 src/plugins/vcsui/gbp-vcsui-tree-addin.c        | 209 +++++++++++++
 src/plugins/vcsui/gbp-vcsui-tree-addin.h        |  31 ++
 src/plugins/vcsui/gtk/menus.ui                  |  20 ++
 src/plugins/vcsui/meson.build                   |  13 +
 src/plugins/vcsui/vcsui-plugin.c                |  42 +++
 src/plugins/vcsui/vcsui.gresource.xml           |   7 +
 src/plugins/vcsui/vcsui.plugin                  |  11 +
 27 files changed, 1537 insertions(+), 364 deletions(-)
---
diff --git a/src/libide/vcs/ide-directory-vcs.c b/src/libide/vcs/ide-directory-vcs.c
new file mode 100644
index 000000000..6311168d8
--- /dev/null
+++ b/src/libide/vcs/ide-directory-vcs.c
@@ -0,0 +1,180 @@
+/* ide-directory-vcs.c
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-directory-vcs"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-threading.h>
+
+#include "ide-context.h"
+
+#include "ide-directory-vcs.h"
+
+struct _IdeDirectoryVcs
+{
+  IdeObject  parent_instances;
+  GFile     *workdir;
+};
+
+#define LOAD_MAX_FILES 5000
+
+static void vcs_iface_init (IdeVcsInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeDirectoryVcs, ide_directory_vcs, IDE_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_VCS, vcs_iface_init))
+
+enum {
+  PROP_0,
+  N_PROPS,
+
+  /* Override Properties */
+  PROP_BRANCH_NAME,
+  PROP_WORKDIR,
+};
+
+static gchar *
+ide_directory_vcs_get_branch_name (IdeVcs *vcs)
+{
+  return g_strdup (_("unversioned"));
+}
+
+static GFile *
+ide_directory_vcs_get_workdir (IdeVcs *vcs)
+{
+  IdeDirectoryVcs *self = (IdeDirectoryVcs *)vcs;
+
+  g_return_val_if_fail (IDE_IS_DIRECTORY_VCS (vcs), NULL);
+
+  /* Note: This function is expected to be thread-safe for
+   *       those holding a reference to @vcs. So
+   *       @workdir cannot be changed after creation
+   *       and must be valid for the lifetime of @vcs.
+   */
+
+  return self->workdir;
+}
+
+static gboolean
+ide_directory_vcs_is_ignored (IdeVcs  *vcs,
+                              GFile   *file,
+                              GError **error)
+{
+  g_autofree gchar *reversed = NULL;
+
+  g_assert (IDE_IS_VCS (vcs));
+  g_assert (G_IS_FILE (file));
+
+  reversed = g_strreverse (g_file_get_basename (file));
+
+  /* check suffixes, in reverse */
+  if ((reversed [0] == '~') ||
+      (strncmp (reversed, "al.", 3) == 0) ||        /* .la */
+      (strncmp (reversed, "ol.", 3) == 0) ||        /* .lo */
+      (strncmp (reversed, "o.", 2) == 0) ||         /* .o */
+      (strncmp (reversed, "pws.", 4) == 0) ||       /* .swp */
+      (strncmp (reversed, "sped.", 5) == 0) ||      /* .deps */
+      (strncmp (reversed, "sbil.", 5) == 0) ||      /* .libs */
+      (strncmp (reversed, "cyp.", 4) == 0) ||       /* .pyc */
+      (strncmp (reversed, "oyp.", 4) == 0) ||       /* .pyo */
+      (strncmp (reversed, "omg.", 4) == 0) ||       /* .gmo */
+      (strncmp (reversed, "tig.", 4) == 0) ||       /* .git */
+      (strncmp (reversed, "rzb.", 4) == 0) ||       /* .bzr */
+      (strncmp (reversed, "nvs.", 4) == 0) ||       /* .svn */
+      (strncmp (reversed, "pmatsrid.", 9) == 0) ||  /* .dirstamp */
+      (strncmp (reversed, "hcg.", 4) == 0))         /* .gch */
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+ide_directory_vcs_dispose (GObject *object)
+{
+  IdeDirectoryVcs *self = (IdeDirectoryVcs *)object;
+
+  g_clear_object (&self->workdir);
+
+  G_OBJECT_CLASS (ide_directory_vcs_parent_class)->dispose (object);
+}
+
+static void
+ide_directory_vcs_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeDirectoryVcs *self = IDE_DIRECTORY_VCS (object);
+
+  switch (prop_id)
+    {
+    case PROP_BRANCH_NAME:
+      g_value_take_string (value, ide_directory_vcs_get_branch_name (IDE_VCS (self)));
+      break;
+
+    case PROP_WORKDIR:
+      g_value_set_object (value, ide_directory_vcs_get_workdir (IDE_VCS (self)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_directory_vcs_class_init (IdeDirectoryVcsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_directory_vcs_dispose;
+  object_class->get_property = ide_directory_vcs_get_property;
+
+  g_object_class_override_property (object_class, PROP_BRANCH_NAME, "branch-name");
+  g_object_class_override_property (object_class, PROP_WORKDIR, "workdir");
+}
+
+static void
+ide_directory_vcs_init (IdeDirectoryVcs *self)
+{
+}
+
+static gint
+ide_directory_vcs_get_priority (IdeVcs *vcs)
+{
+  return G_MAXINT;
+}
+
+static void
+vcs_iface_init (IdeVcsInterface *iface)
+{
+  iface->get_workdir = ide_directory_vcs_get_workdir;
+  iface->is_ignored = ide_directory_vcs_is_ignored;
+  iface->get_priority = ide_directory_vcs_get_priority;
+  iface->get_branch_name = ide_directory_vcs_get_branch_name;
+}
+
+IdeDirectoryVcs *
+ide_directory_vcs_new (GFile *workdir)
+{
+  IdeDirectoryVcs *self = g_object_new (IDE_TYPE_DIRECTORY_VCS, NULL);
+  self->workdir = g_file_dup (workdir);
+  return self;
+}
diff --git a/src/libide/vcs/ide-directory-vcs.h b/src/libide/vcs/ide-directory-vcs.h
new file mode 100644
index 000000000..3f1b9f575
--- /dev/null
+++ b/src/libide/vcs/ide-directory-vcs.h
@@ -0,0 +1,36 @@
+/* ide-directory-vcs.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+#include "ide-vcs.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DIRECTORY_VCS (ide_directory_vcs_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_FINAL_TYPE (IdeDirectoryVcs, ide_directory_vcs, IDE, DIRECTORY_VCS, IdeObject)
+
+IdeDirectoryVcs *ide_directory_vcs_new (GFile *workdir);
+
+G_END_DECLS
diff --git a/src/libide/vcs/ide-vcs-cloner.c b/src/libide/vcs/ide-vcs-cloner.c
new file mode 100644
index 000000000..b1c798e89
--- /dev/null
+++ b/src/libide/vcs/ide-vcs-cloner.c
@@ -0,0 +1,148 @@
+/* ide-vcs-cloner.c
+ *
+ * Copyright 2018-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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-vcs-cloner"
+
+#include "config.h"
+
+#include <libide-threading.h>
+
+#include "ide-vcs-cloner.h"
+
+G_DEFINE_INTERFACE (IdeVcsCloner, ide_vcs_cloner, G_TYPE_OBJECT)
+
+static void
+ide_vcs_cloner_default_init (IdeVcsClonerInterface *iface)
+{
+}
+
+/**
+ * ide_vcs_cloner_validate_uri:
+ * @self: a #IdeVcsCloner
+ * @uri: a string containing the URI to validate
+ * @errmsg: (out) (optional): a location for an error message
+ *
+ * Checks to see if @uri is valid, and if not, sets @errmsg to a string
+ * describing how the URI is invalid.
+ *
+ * Returns: %TRUE if @uri is valid, otherwise %FALSE and @error is set.
+ *
+ * Since: 3.32
+ */
+gboolean
+ide_vcs_cloner_validate_uri (IdeVcsCloner  *self,
+                             const gchar   *uri,
+                             gchar        **errmsg)
+{
+  g_return_val_if_fail (IDE_IS_VCS_CLONER (self), FALSE);
+
+  if (errmsg != NULL)
+    *errmsg = NULL;
+
+  if (IDE_VCS_CLONER_GET_IFACE (self)->validate_uri)
+    return IDE_VCS_CLONER_GET_IFACE (self)->validate_uri (self, uri, errmsg);
+
+  return FALSE;
+}
+
+/**
+ * ide_vcs_cloner_clone_async:
+ * @self: an #IdeVcsCloner
+ * @uri: a string containing the URI
+ * @destination: a string containing the destination path
+ * @options: a #GVariantDict containing any user supplied options
+ * @cancellable: (nullable): a #GCancellable
+ * @progress: (out) (optional): a location for an #IdeNotification, or %NULL
+ * @callback: a #GAsyncReadyCallback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * Since: 3.32
+ */
+void
+ide_vcs_cloner_clone_async (IdeVcsCloner         *self,
+                            const gchar          *uri,
+                            const gchar          *destination,
+                            GVariantDict         *options,
+                            GCancellable         *cancellable,
+                            IdeNotification     **progress,
+                            GAsyncReadyCallback   callback,
+                            gpointer              user_data)
+{
+  g_return_if_fail (IDE_IS_VCS_CLONER (self));
+  g_return_if_fail (uri != NULL);
+  g_return_if_fail (destination != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (progress != NULL)
+    *progress = NULL;
+
+  IDE_VCS_CLONER_GET_IFACE (self)->clone_async (self,
+                                                uri,
+                                                destination,
+                                                options,
+                                                cancellable,
+                                                progress,
+                                                callback,
+                                                user_data);
+}
+
+/**
+ * ide_vcs_cloner_clone_finish:
+ * @self: an #IdeVcsCloner
+ * @result: a #GAsyncResult provided to callback
+ * @error: a location for a #GError, or %NULL
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ *
+ * Since: 3.32
+ */
+gboolean
+ide_vcs_cloner_clone_finish (IdeVcsCloner  *self,
+                             GAsyncResult  *result,
+                             GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_VCS_CLONER (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_VCS_CLONER_GET_IFACE (self)->clone_finish (self, result, error);
+}
+
+/**
+ * ide_vcs_cloner_get_title:
+ * @self: a #IdeVcsCloner
+ *
+ * Gets the for the cloner, such as "Git". This may be used to present
+ * a selector to the user based on the backend clone engine. Other suitable
+ * titles might be "Subversion" or "CVS".
+ *
+ * Returns: (transfer full): a string containing the title
+ *
+ * Since: 3.32
+ */
+gchar *
+ide_vcs_cloner_get_title (IdeVcsCloner *self)
+{
+  g_return_val_if_fail (IDE_IS_VCS_CLONER (self), NULL);
+
+  if (IDE_VCS_CLONER_GET_IFACE (self)->get_title)
+    return IDE_VCS_CLONER_GET_IFACE (self)->get_title (self);
+
+  return NULL;
+}
diff --git a/src/libide/vcs/ide-vcs-cloner.h b/src/libide/vcs/ide-vcs-cloner.h
new file mode 100644
index 000000000..69e464c9f
--- /dev/null
+++ b/src/libide/vcs/ide-vcs-cloner.h
@@ -0,0 +1,73 @@
+/* ide-vcs-cloner.h
+ *
+ * Copyright 2018-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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_VCS_CLONER (ide_vcs_cloner_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_INTERFACE (IdeVcsCloner, ide_vcs_cloner, IDE, VCS_CLONER, GObject)
+
+struct _IdeVcsClonerInterface
+{
+  GTypeInterface parent_iface;
+
+  gchar    *(*get_title)    (IdeVcsCloner         *self);
+  gboolean  (*validate_uri) (IdeVcsCloner         *self,
+                             const gchar          *uri,
+                             gchar               **errmsg);
+  void      (*clone_async)  (IdeVcsCloner         *self,
+                             const gchar          *uri,
+                             const gchar          *destination,
+                             GVariantDict         *options,
+                             GCancellable         *cancellable,
+                             IdeNotification     **progress,
+                             GAsyncReadyCallback   callback,
+                             gpointer              user_data);
+  gboolean  (*clone_finish) (IdeVcsCloner         *self,
+                             GAsyncResult         *result,
+                             GError              **error);
+};
+
+IDE_AVAILABLE_IN_3_32
+gchar   *ide_vcs_cloner_get_title    (IdeVcsCloner         *self);
+IDE_AVAILABLE_IN_3_32
+void     ide_vcs_cloner_clone_async  (IdeVcsCloner         *self,
+                                      const gchar          *uri,
+                                      const gchar          *destination,
+                                      GVariantDict         *options,
+                                      GCancellable         *cancellable,
+                                      IdeNotification     **progress,
+                                      GAsyncReadyCallback   callback,
+                                      gpointer              user_data);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_vcs_cloner_clone_finish (IdeVcsCloner         *self,
+                                      GAsyncResult         *result,
+                                      GError              **error);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_vcs_cloner_validate_uri (IdeVcsCloner         *self,
+                                      const gchar          *uri,
+                                      gchar               **errmsg);
+
+G_END_DECLS
diff --git a/src/libide/vcs/ide-vcs-config.c b/src/libide/vcs/ide-vcs-config.c
index b03817e65..8b6c4780a 100644
--- a/src/libide/vcs/ide-vcs-config.c
+++ b/src/libide/vcs/ide-vcs-config.c
@@ -22,7 +22,8 @@
 
 #include "config.h"
 
-#include "vcs/ide-vcs-config.h"
+#include "ide-vcs-config.h"
+#include "ide-vcs-enums.h"
 
 G_DEFINE_INTERFACE (IdeVcsConfig, ide_vcs_config, G_TYPE_OBJECT)
 
diff --git a/src/libide/vcs/ide-vcs-config.h b/src/libide/vcs/ide-vcs-config.h
index 387aa331b..457639791 100644
--- a/src/libide/vcs/ide-vcs-config.h
+++ b/src/libide/vcs/ide-vcs-config.h
@@ -20,9 +20,11 @@
 
 #pragma once
 
-#include <glib-object.h>
+#if !defined (IDE_VCS_INSIDE) && !defined (IDE_VCS_COMPILATION)
+# error "Only <libide-vcs.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/vcs/ide-vcs-file-info.c b/src/libide/vcs/ide-vcs-file-info.c
index 3ed967992..7a2164eeb 100644
--- a/src/libide/vcs/ide-vcs-file-info.c
+++ b/src/libide/vcs/ide-vcs-file-info.c
@@ -22,9 +22,8 @@
 
 #include "config.h"
 
-#include "ide-enums.h"
-
-#include "vcs/ide-vcs-file-info.h"
+#include "ide-vcs-enums.h"
+#include "ide-vcs-file-info.h"
 
 typedef struct
 {
@@ -180,7 +179,7 @@ ide_vcs_file_info_class_init (IdeVcsFileInfoClass *klass)
                        IDE_TYPE_VCS_FILE_STATUS,
                        IDE_VCS_FILE_STATUS_UNCHANGED,
                        (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
-  
+
   g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
diff --git a/src/libide/vcs/ide-vcs-file-info.h b/src/libide/vcs/ide-vcs-file-info.h
index 2ba874dfb..0e0600324 100644
--- a/src/libide/vcs/ide-vcs-file-info.h
+++ b/src/libide/vcs/ide-vcs-file-info.h
@@ -20,9 +20,11 @@
 
 #pragma once
 
-#include <gio/gio.h>
+#if !defined (IDE_VCS_INSIDE) && !defined (IDE_VCS_COMPILATION)
+# error "Only <libide-vcs.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/vcs/ide-vcs-initializer.c b/src/libide/vcs/ide-vcs-initializer.c
index 84bd2f815..9742b845f 100644
--- a/src/libide/vcs/ide-vcs-initializer.c
+++ b/src/libide/vcs/ide-vcs-initializer.c
@@ -22,7 +22,7 @@
 
 #include "config.h"
 
-#include "vcs/ide-vcs-initializer.h"
+#include "ide-vcs-initializer.h"
 
 G_DEFINE_INTERFACE (IdeVcsInitializer, ide_vcs_initializer, G_TYPE_OBJECT)
 
diff --git a/src/libide/vcs/ide-vcs-initializer.h b/src/libide/vcs/ide-vcs-initializer.h
index 0649383bb..44193b97e 100644
--- a/src/libide/vcs/ide-vcs-initializer.h
+++ b/src/libide/vcs/ide-vcs-initializer.h
@@ -20,9 +20,11 @@
 
 #pragma once
 
-#include <gio/gio.h>
+#if !defined (IDE_VCS_INSIDE) && !defined (IDE_VCS_COMPILATION)
+# error "Only <libide-vcs.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/vcs/ide-vcs-monitor.c b/src/libide/vcs/ide-vcs-monitor.c
index d4fb9245c..5bb44902b 100644
--- a/src/libide/vcs/ide-vcs-monitor.c
+++ b/src/libide/vcs/ide-vcs-monitor.c
@@ -23,20 +23,21 @@
 #include "config.h"
 
 #include <dazzle.h>
+#include <libide-core.h>
 
-#include "ide-context.h"
-#include "ide-debug.h"
-
-#include "vcs/ide-vcs.h"
-#include "vcs/ide-vcs-file-info.h"
-#include "vcs/ide-vcs-monitor.h"
+#include "ide-vcs.h"
+#include "ide-vcs-file-info.h"
+#include "ide-vcs-monitor.h"
 
 struct _IdeVcsMonitor
 {
   IdeObject                parent_instance;
 
   GFile                   *root;
+  IdeVcs                  *vcs;
+  DzlSignalGroup          *vcs_signals;
   DzlRecursiveFileMonitor *monitor;
+  DzlSignalGroup          *monitor_signals;
   GHashTable              *status_by_file;
 
   guint                    cache_source;
@@ -55,6 +56,7 @@ enum {
 enum {
   PROP_0,
   PROP_ROOT,
+  PROP_VCS,
   N_PROPS
 };
 
@@ -62,13 +64,14 @@ static GParamSpec *properties [N_PROPS];
 static guint signals [N_SIGNALS];
 
 static void
-ide_vcs_monitor_add_parents (GHashTable       *hash,
-                             GFile            *file,
-                             GFile            *toplevel,
-                             IdeVcsFileStatus  status)
+ide_vcs_monitor_add_parents_locked (GHashTable       *hash,
+                                    GFile            *file,
+                                    GFile            *toplevel,
+                                    IdeVcsFileStatus  status)
 {
   GFile *parent;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (hash != NULL);
   g_assert (G_IS_FILE (file));
   g_assert (G_IS_FILE (toplevel));
@@ -110,74 +113,77 @@ ide_vcs_monitor_list_status_cb (GObject      *object,
   IdeVcs *vcs = (IdeVcs *)object;
   g_autoptr(IdeVcsMonitor) self = user_data;
   g_autoptr(GListModel) model = NULL;
-  g_autoptr(GHashTable) status_by_file = NULL;
-  GFile *workdir;
-  guint n_items;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_VCS (vcs));
   g_assert (IDE_IS_VCS_MONITOR (self));
 
+  ide_object_lock (IDE_OBJECT (self));
+
   self->busy = FALSE;
 
-  model = ide_vcs_list_status_finish (vcs, result, NULL);
-  if (model == NULL)
-    return;
+  if ((model = ide_vcs_list_status_finish (vcs, result, NULL)))
+    {
+      g_autoptr(GHashTable) status_by_file = NULL;
+      guint n_items;
 
-  n_items = g_list_model_get_n_items (model);
-  workdir = ide_vcs_get_working_directory (vcs);
-  status_by_file = g_hash_table_new_full (g_file_hash,
-                                          (GEqualFunc) g_file_equal,
-                                          g_object_unref,
-                                          g_object_unref);
+      n_items = g_list_model_get_n_items (model);
+      status_by_file = g_hash_table_new_full (g_file_hash,
+                                              (GEqualFunc)g_file_equal,
+                                              g_object_unref,
+                                              g_object_unref);
 
-  for (guint i = 0; i < n_items; i++)
-    {
-      g_autoptr(IdeVcsFileInfo) info = NULL;
-      IdeVcsFileStatus status;
-      GFile *file;
+      for (guint i = 0; i < n_items; i++)
+        {
+          g_autoptr(IdeVcsFileInfo) info = NULL;
+          IdeVcsFileStatus status;
+          GFile *file;
 
-      info = g_list_model_get_item (model, i);
-      file = ide_vcs_file_info_get_file (info);
-      status = ide_vcs_file_info_get_status (info);
+          info = g_list_model_get_item (model, i);
+          file = ide_vcs_file_info_get_file (info);
+          status = ide_vcs_file_info_get_status (info);
 
-      g_hash_table_insert (status_by_file,
-                           g_file_dup (file),
-                           g_steal_pointer (&info));
+          g_hash_table_insert (status_by_file,
+                               g_file_dup (file),
+                               g_steal_pointer (&info));
 
-      ide_vcs_monitor_add_parents (status_by_file, file, workdir, status);
-    }
+          ide_vcs_monitor_add_parents_locked (status_by_file, file, self->root, status);
+        }
 
-  g_clear_pointer (&self->status_by_file, g_hash_table_unref);
-  self->status_by_file = g_steal_pointer (&status_by_file);
+      g_clear_pointer (&self->status_by_file, g_hash_table_unref);
+      self->status_by_file = g_steal_pointer (&status_by_file);
 
-  g_signal_emit (self, signals[RELOADED], 0);
+      g_signal_emit (self, signals [RELOADED], 0);
+    }
+
+  ide_object_unlock (IDE_OBJECT (self));
 }
 
 static gboolean
 ide_vcs_monitor_cache_cb (gpointer data)
 {
   IdeVcsMonitor *self = data;
-  IdeContext *context;
-  IdeVcs *vcs;
-  GFile *workdir;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_VCS_MONITOR (self));
 
-  self->cache_source = 0;
+  ide_object_lock (IDE_OBJECT (self));
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-  vcs = ide_context_get_vcs (context);
-  workdir = ide_vcs_get_working_directory (vcs);
+  self->cache_source = 0;
 
-  self->busy = TRUE;
+  if (self->vcs != NULL)
+    {
+      self->busy = TRUE;
+      ide_vcs_list_status_async (self->vcs,
+                                 self->root,
+                                 TRUE,
+                                 G_PRIORITY_LOW,
+                                 NULL,
+                                 ide_vcs_monitor_list_status_cb,
+                                 g_object_ref (self));
+    }
 
-  ide_vcs_list_status_async (vcs,
-                             workdir,
-                             TRUE,
-                             G_PRIORITY_LOW,
-                             NULL,
-                             ide_vcs_monitor_list_status_cb,
-                             g_object_ref (self));
+  ide_object_unlock (IDE_OBJECT (self));
 
   return G_SOURCE_REMOVE;
 }
@@ -185,13 +191,16 @@ ide_vcs_monitor_cache_cb (gpointer data)
 static void
 ide_vcs_monitor_queue_reload (IdeVcsMonitor *self)
 {
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_VCS_MONITOR (self));
 
+  ide_object_lock (IDE_OBJECT (self));
   if (self->cache_source == 0 && !self->busy)
     self->cache_source = g_idle_add_full (G_PRIORITY_LOW,
                                           ide_vcs_monitor_cache_cb,
                                           g_object_ref (self),
                                           g_object_unref);
+  ide_object_unlock (IDE_OBJECT (self));
 }
 
 static void
@@ -203,6 +212,7 @@ ide_vcs_monitor_changed_cb (IdeVcsMonitor           *self,
 {
   IDE_ENTRY;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_VCS_MONITOR (self));
   g_assert (G_IS_FILE (file));
   g_assert (!other_file || G_IS_FILE (other_file));
@@ -221,12 +231,15 @@ ide_vcs_monitor_vcs_changed_cb (IdeVcsMonitor *self,
 {
   IDE_ENTRY;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_VCS_MONITOR (self));
   g_assert (IDE_IS_VCS (vcs));
 
   /* Everything is invalidated by new VCS index, reload now */
+  ide_object_lock (IDE_OBJECT (self));
   g_clear_pointer (&self->status_by_file, g_hash_table_unref);
   ide_vcs_monitor_queue_reload (self);
+  ide_object_unlock (IDE_OBJECT (self));
 
   IDE_EXIT;
 }
@@ -236,15 +249,15 @@ ide_vcs_monitor_ignore_func (GFile    *file,
                              gpointer  data)
 {
   IdeVcsMonitor *self = data;
-  IdeContext *context;
-  IdeVcs *vcs;
+  gboolean ret;
 
   g_assert (IDE_IS_VCS_MONITOR (self));
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-  vcs = ide_context_get_vcs (context);
+  ide_object_lock (IDE_OBJECT (self));
+  ret = ide_vcs_is_ignored (self->vcs, file, NULL);
+  ide_object_unlock (IDE_OBJECT (self));
 
-  return ide_vcs_is_ignored (vcs, file, NULL);
+  return ret;
 }
 
 static void
@@ -256,6 +269,7 @@ ide_vcs_monitor_start_cb (GObject      *object,
   g_autoptr(IdeVcsMonitor) self = user_data;
   g_autoptr(GError) error = NULL;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (DZL_IS_RECURSIVE_FILE_MONITOR (monitor));
   g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (IDE_IS_VCS_MONITOR (self));
@@ -267,47 +281,41 @@ ide_vcs_monitor_start_cb (GObject      *object,
 }
 
 static void
-ide_vcs_monitor_constructed (GObject *object)
+ide_vcs_monitor_maybe_reload_locked (IdeVcsMonitor *self)
 {
-  IdeVcsMonitor *self = (IdeVcsMonitor *)object;
-  IdeContext *context;
-  IdeVcs *vcs;
-
-  G_OBJECT_CLASS (ide_vcs_monitor_parent_class)->constructed (object);
-
-  context = ide_object_get_context (IDE_OBJECT (self));
-  vcs = ide_context_get_vcs (context);
-
-  g_signal_connect_object (vcs,
-                           "changed",
-                           G_CALLBACK (ide_vcs_monitor_vcs_changed_cb),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  self->monitor = dzl_recursive_file_monitor_new (self->root);
+  g_assert (IDE_IS_VCS_MONITOR (self));
 
-  dzl_recursive_file_monitor_set_ignore_func (self->monitor,
-                                              ide_vcs_monitor_ignore_func,
-                                              self, NULL);
+  g_clear_pointer (&self->status_by_file, g_hash_table_unref);
+  g_clear_handle_id (&self->cache_source, g_source_remove);
 
-  g_signal_connect_object (self->monitor,
-                           "changed",
-                           G_CALLBACK (ide_vcs_monitor_changed_cb),
-                           self,
-                           G_CONNECT_SWAPPED);
+  if (self->monitor)
+    {
+      dzl_signal_group_set_target (self->monitor_signals, NULL);
+      dzl_recursive_file_monitor_set_ignore_func (self->monitor, NULL, NULL, NULL);
+      dzl_recursive_file_monitor_cancel (self->monitor);
+      g_clear_object (&self->monitor);
+    }
 
-  dzl_recursive_file_monitor_start_async (self->monitor,
-                                          NULL,
-                                          ide_vcs_monitor_start_cb,
-                                          g_object_ref (self));
+  if (G_IS_FILE (self->root) && IDE_IS_VCS (self->vcs))
+    {
+      self->monitor = dzl_recursive_file_monitor_new (self->root);
+      dzl_recursive_file_monitor_set_ignore_func (self->monitor,
+                                                  ide_vcs_monitor_ignore_func,
+                                                  self, NULL);
+      dzl_signal_group_set_target (self->monitor_signals, self->monitor);
+      dzl_recursive_file_monitor_start_async (self->monitor,
+                                              NULL,
+                                              ide_vcs_monitor_start_cb,
+                                              g_object_ref (self));
+    }
 }
 
 static void
-ide_vcs_monitor_dispose (GObject *object)
+ide_vcs_monitor_destroy (IdeObject *object)
 {
   IdeVcsMonitor *self = (IdeVcsMonitor *)object;
 
-  dzl_clear_source (&self->cache_source);
+  g_clear_handle_id (&self->cache_source, g_source_remove);
   g_clear_pointer (&self->status_by_file, g_hash_table_unref);
 
   if (self->monitor != NULL)
@@ -317,9 +325,25 @@ ide_vcs_monitor_dispose (GObject *object)
       g_clear_object (&self->monitor);
     }
 
+  dzl_signal_group_set_target (self->monitor_signals, NULL);
+  dzl_signal_group_set_target (self->vcs_signals, NULL);
+
+  g_clear_object (&self->vcs);
+
+  IDE_OBJECT_CLASS (ide_vcs_monitor_parent_class)->destroy (object);
+}
+
+static void
+ide_vcs_monitor_finalize (GObject *object)
+{
+  IdeVcsMonitor *self = (IdeVcsMonitor *)object;
+
+  g_clear_pointer (&self->status_by_file, g_hash_table_unref);
   g_clear_object (&self->root);
+  g_clear_object (&self->monitor_signals);
+  g_clear_object (&self->vcs_signals);
 
-  G_OBJECT_CLASS (ide_vcs_monitor_parent_class)->dispose (object);
+  G_OBJECT_CLASS (ide_vcs_monitor_parent_class)->finalize (object);
 }
 
 static void
@@ -333,7 +357,11 @@ ide_vcs_monitor_get_property (GObject    *object,
   switch (prop_id)
     {
     case PROP_ROOT:
-      g_value_set_object (value, self->root);
+      g_value_take_object (value, ide_vcs_monitor_ref_root (self));
+      break;
+
+    case PROP_VCS:
+      g_value_take_object (value, ide_vcs_monitor_ref_vcs (self));
       break;
 
     default:
@@ -352,7 +380,11 @@ ide_vcs_monitor_set_property (GObject      *object,
   switch (prop_id)
     {
     case PROP_ROOT:
-      self->root = g_value_dup_object (value);
+      ide_vcs_monitor_set_root (self, g_value_get_object (value));
+      break;
+
+    case PROP_VCS:
+      ide_vcs_monitor_set_vcs (self, g_value_get_object (value));
       break;
 
     default:
@@ -364,21 +396,59 @@ static void
 ide_vcs_monitor_class_init (IdeVcsMonitorClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
 
-  object_class->constructed = ide_vcs_monitor_constructed;
-  object_class->dispose = ide_vcs_monitor_dispose;
+  object_class->finalize = ide_vcs_monitor_finalize;
   object_class->get_property = ide_vcs_monitor_get_property;
   object_class->set_property = ide_vcs_monitor_set_property;
 
+  i_object_class->destroy = ide_vcs_monitor_destroy;
+
+  /**
+   * IdeVcsMonitor:root:
+   *
+   * The "root" property is the root of the file-system to begin
+   * monitoring for changes.
+   *
+   * Since: 3.32
+   */
   properties [PROP_ROOT] =
     g_param_spec_object ("root",
                          "Root",
                          "The root of the directory tree",
                          G_TYPE_FILE,
-                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-  
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  /**
+   * IdeVcsMonitor:vcs:
+   *
+   * The "vcs" property is the version control system to be queried for
+   * additional status information when a file has been discovered to
+   * have been changed.
+   *
+   * Since: 3.32
+   */
+  properties [PROP_VCS] =
+    g_param_spec_object ("vcs",
+                         "VCS",
+                         "The version control system in use",
+                         IDE_TYPE_VCS,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_properties (object_class, N_PROPS, properties);
 
+  /**
+   * IdeVcsMonitor::changed:
+   * @self: an #IdeVcsMonitor
+   * @file: a #GFile
+   * @other_file: (nullable): a #GFile or %NULL
+   * @event: a #GFileMonitorEvent
+   *
+   * The "changed" signal is emitted when a file has been discovered to
+   * have been changed on disk.
+   *
+   * Since: 3.32
+   */
   signals [CHANGED] =
     g_signal_new ("changed",
                   G_TYPE_FROM_CLASS (klass),
@@ -391,6 +461,14 @@ ide_vcs_monitor_class_init (IdeVcsMonitorClass *klass)
                   G_TYPE_FILE | G_SIGNAL_TYPE_STATIC_SCOPE,
                   G_TYPE_FILE_MONITOR_EVENT);
 
+  /**
+   * IdeVcsMonitor::reloaded:
+   * @self: an #IdeVcsMonitor
+   *
+   * The "reloaded" signal is emitted when the monitor has been reloaded.
+   *
+   * Since: 3.32
+   */
   signals [RELOADED] =
     g_signal_new ("reloaded",
                   G_TYPE_FROM_CLASS (klass),
@@ -401,10 +479,25 @@ ide_vcs_monitor_class_init (IdeVcsMonitorClass *klass)
 static void
 ide_vcs_monitor_init (IdeVcsMonitor *self)
 {
+  self->monitor_signals = dzl_signal_group_new (DZL_TYPE_RECURSIVE_FILE_MONITOR);
+
+  dzl_signal_group_connect_object (self->monitor_signals,
+                                   "changed",
+                                   G_CALLBACK (ide_vcs_monitor_changed_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  self->vcs_signals = dzl_signal_group_new (IDE_TYPE_VCS);
+
+  dzl_signal_group_connect_object (self->vcs_signals,
+                                   "changed",
+                                   G_CALLBACK (ide_vcs_monitor_vcs_changed_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
 }
 
 /**
- * ide_vcs_monitor_get_info:
+ * ide_vcs_monitor_ref_info:
  * @self: a #IdeVcsMonitor
  * @file: a #GFile
  *
@@ -413,25 +506,112 @@ ide_vcs_monitor_init (IdeVcsMonitor *self)
  * If the file information has not been loaded, %NULL is returned. You
  * can wait for #IdeVcsMonitor::reloaded and query again if you expect
  * the info to be there.
- * 
+ *
  * Returns: (transfer full) (nullable): an #IdeVcsFileInfo or %NULL
  *
  * Since: 3.32
  */
 IdeVcsFileInfo *
-ide_vcs_monitor_get_info (IdeVcsMonitor *self,
+ide_vcs_monitor_ref_info (IdeVcsMonitor *self,
                           GFile         *file)
 {
-  IdeVcsFileInfo *info;
+  IdeVcsFileInfo *info = NULL;
+
+  g_return_val_if_fail (IDE_IS_VCS_MONITOR (self), NULL);
+
+  ide_object_lock (IDE_OBJECT (self));
+  if (self->status_by_file != NULL)
+    {
+      if ((info = g_hash_table_lookup (self->status_by_file, file)))
+        g_object_ref (info);
+    }
+  ide_object_unlock (IDE_OBJECT (self));
+
+  return g_steal_pointer (&info);
+}
+
+/**
+ * ide_vcs_monitor_ref_vcs:
+ * @self: a #IdeVcsMonitor
+ *
+ * Increments the reference count of the #IdeVcs monitored using the
+ * #IdeVcsMonitor and returns it.
+ *
+ * Returns: (transfer full) (nullable): an #IdeVcs or %NULL
+ *
+ * Since: 3.32
+ */
+IdeVcs *
+ide_vcs_monitor_ref_vcs (IdeVcsMonitor *self)
+{
+  IdeVcs *ret;
+
+  g_return_val_if_fail (IDE_IS_VCS_MONITOR (self), NULL);
+
+  ide_object_lock (IDE_OBJECT (self));
+  ret = self->vcs ? g_object_ref (self->vcs) : NULL;
+  ide_object_unlock (IDE_OBJECT (self));
+
+  return g_steal_pointer (&ret);
+}
+
+/**
+ * ide_vcs_monitor_ref_root:
+ * @self: a #IdeVcsMonitor
+ *
+ * Gets the #IdeVcsMonitor:root property and increments the reference
+ * count of the #GFile by one.
+ *
+ * Returns: (transfer full) (nullable): a #GFile or %NULL
+ *
+ * Since: 3.32
+ */
+GFile *
+ide_vcs_monitor_ref_root (IdeVcsMonitor *self)
+{
+  GFile *ret;
 
   g_return_val_if_fail (IDE_IS_VCS_MONITOR (self), NULL);
 
-  if (self->status_by_file == NULL)
-    return NULL;
+  ide_object_lock (IDE_OBJECT (self));
+  ret = self->root ? g_object_ref (self->root) : NULL;
+  ide_vcs_monitor_maybe_reload_locked (self);
+  ide_object_unlock (IDE_OBJECT (self));
 
-  info = g_hash_table_lookup (self->status_by_file, file);
-  if (info == NULL)
-    return NULL;
+  return g_steal_pointer (&ret);
+}
+
+void
+ide_vcs_monitor_set_root (IdeVcsMonitor *self,
+                          GFile         *root)
+{
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_VCS_MONITOR (self));
+  g_return_if_fail (G_IS_FILE (root));
 
-  return g_object_ref (info);
+  ide_object_lock (IDE_OBJECT (self));
+  if (g_set_object (&self->root, root))
+    {
+      ide_object_notify_by_pspec (self, properties [PROP_ROOT]);
+      ide_vcs_monitor_maybe_reload_locked (self);
+    }
+  ide_object_unlock (IDE_OBJECT (self));
+}
+
+void
+ide_vcs_monitor_set_vcs (IdeVcsMonitor *self,
+                         IdeVcs        *vcs)
+{
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_VCS_MONITOR (self));
+  g_return_if_fail (!vcs || IDE_IS_VCS (vcs));
+
+  ide_object_lock (IDE_OBJECT (self));
+  if (g_set_object (&self->vcs, vcs))
+    {
+      dzl_signal_group_set_target (self->vcs_signals, vcs);
+      ide_object_notify_by_pspec (self, properties [PROP_VCS]);
+      ide_vcs_monitor_maybe_reload_locked (self);
+    }
+  ide_object_unlock (IDE_OBJECT (self));
 }
diff --git a/src/libide/vcs/ide-vcs-monitor.h b/src/libide/vcs/ide-vcs-monitor.h
index e9cb83e52..d7835ee8c 100644
--- a/src/libide/vcs/ide-vcs-monitor.h
+++ b/src/libide/vcs/ide-vcs-monitor.h
@@ -20,10 +20,14 @@
 
 #pragma once
 
-#include "ide-object.h"
-#include "ide-version-macros.h"
+#if !defined (IDE_VCS_INSIDE) && !defined (IDE_VCS_COMPILATION)
+# error "Only <libide-vcs.h> can be included directly."
+#endif
 
-#include "vcs/ide-vcs-file-info.h"
+#include <libide-core.h>
+
+#include "ide-vcs.h"
+#include "ide-vcs-file-info.h"
 
 G_BEGIN_DECLS
 
@@ -33,7 +37,17 @@ IDE_AVAILABLE_IN_3_32
 G_DECLARE_FINAL_TYPE (IdeVcsMonitor, ide_vcs_monitor, IDE, VCS_MONITOR, IdeObject)
 
 IDE_AVAILABLE_IN_3_32
-IdeVcsFileInfo *ide_vcs_monitor_get_info (IdeVcsMonitor *self,
+IdeVcsFileInfo *ide_vcs_monitor_ref_info (IdeVcsMonitor *self,
+                                          GFile         *file);
+IDE_AVAILABLE_IN_3_32
+GFile          *ide_vcs_monitor_ref_root (IdeVcsMonitor *self);
+IDE_AVAILABLE_IN_3_32
+void            ide_vcs_monitor_set_root (IdeVcsMonitor *self,
                                           GFile         *file);
+IDE_AVAILABLE_IN_3_32
+IdeVcs         *ide_vcs_monitor_ref_vcs  (IdeVcsMonitor *self);
+IDE_AVAILABLE_IN_3_32
+void            ide_vcs_monitor_set_vcs  (IdeVcsMonitor *self,
+                                          IdeVcs        *vcs);
 
 G_END_DECLS
diff --git a/src/libide/vcs/ide-vcs-uri.c b/src/libide/vcs/ide-vcs-uri.c
index 52235fe83..db44f23d0 100644
--- a/src/libide/vcs/ide-vcs-uri.c
+++ b/src/libide/vcs/ide-vcs-uri.c
@@ -22,11 +22,10 @@
 
 #include "config.h"
 
-#include <dazzle.h>
 #include <stdlib.h>
 #include <string.h>
 
-#include "vcs/ide-vcs-uri.h"
+#include "ide-vcs-uri.h"
 
 G_DEFINE_BOXED_TYPE (IdeVcsUri, ide_vcs_uri, ide_vcs_uri_ref, ide_vcs_uri_unref)
 
@@ -156,7 +155,7 @@ ide_vcs_uri_parse (IdeVcsUri   *self,
           g_free (tmp);
         }
 
-      if (!dzl_str_empty0 (portstr) && g_ascii_isdigit (portstr [1]))
+      if (!ide_str_empty0 (portstr) && g_ascii_isdigit (portstr [1]))
         port = CLAMP (atoi (&portstr [1]), 1, G_MAXINT16);
 
       ide_vcs_uri_set_scheme (self, scheme);
@@ -220,7 +219,7 @@ ide_vcs_uri_new (const gchar *uri)
 {
   IdeVcsUri *self;
 
-  self = g_new0 (IdeVcsUri, 1);
+  self = g_slice_new0 (IdeVcsUri);
   self->ref_count = 1;
 
   if (ide_vcs_uri_parse (self, uri) && ide_vcs_uri_validate (self))
@@ -229,7 +228,7 @@ ide_vcs_uri_new (const gchar *uri)
       return self;
     }
 
-  g_free (self);
+  ide_vcs_uri_unref (self);
 
   return NULL;
 }
@@ -242,7 +241,7 @@ ide_vcs_uri_finalize (IdeVcsUri *self)
   g_free (self->user);
   g_free (self->host);
   g_free (self->path);
-  g_free (self);
+  g_slice_free (IdeVcsUri, self);
 }
 
 IdeVcsUri *
@@ -312,7 +311,7 @@ ide_vcs_uri_set_scheme (IdeVcsUri   *self,
 {
   g_return_if_fail (self);
 
-  if (dzl_str_empty0 (scheme))
+  if (ide_str_empty0 (scheme))
     scheme = NULL;
 
   if (scheme != self->scheme)
@@ -336,7 +335,7 @@ ide_vcs_uri_set_user (IdeVcsUri   *self,
 {
   g_return_if_fail (self);
 
-  if (dzl_str_empty0 (user))
+  if (ide_str_empty0 (user))
     user = NULL;
 
   if (user != self->user)
@@ -360,7 +359,7 @@ ide_vcs_uri_set_host (IdeVcsUri   *self,
 {
   g_return_if_fail (self);
 
-  if (dzl_str_empty0 (host))
+  if (ide_str_empty0 (host))
     host = NULL;
 
   if (host != self->host)
@@ -390,7 +389,7 @@ ide_vcs_uri_set_path (IdeVcsUri   *self,
 {
   g_return_if_fail (self);
 
-  if (dzl_str_empty0 (path))
+  if (ide_str_empty0 (path))
     path = NULL;
 
   if (path != self->path)
@@ -460,3 +459,43 @@ ide_vcs_uri_is_valid (const gchar *uri_string)
 
   return ret;
 }
+
+/**
+ * ide_vcs_uri_get_clone_name:
+ * @self: an #ideVcsUri
+ *
+ * Determines a suggested name for the checkout directory. Some special
+ * handling of suffixes such as ".git" are performed to improve the the
+ * quality of results.
+ *
+ * Returns: (transfer full) (nullable): a string containing the suggested
+ *   clone directory name, or %NULL.
+ *
+ * Since: 3.32
+ */
+gchar *
+ide_vcs_uri_get_clone_name (const IdeVcsUri *self)
+{
+  g_autofree gchar *name = NULL;
+  const gchar *path;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  if (!(path = ide_vcs_uri_get_path (self)))
+    return NULL;
+
+  if (ide_str_empty0 (path))
+    return NULL;
+
+  if (!(name = g_path_get_basename (path)))
+    return NULL;
+
+  /* Trim trailing ".git" */
+  if (g_str_has_suffix (name, ".git"))
+    *(strrchr (name, '.')) = '\0';
+
+  if (!g_str_equal (name, "/") && !g_str_equal (name, "~"))
+    return g_steal_pointer (&name);
+
+  return NULL;
+}
diff --git a/src/libide/vcs/ide-vcs-uri.h b/src/libide/vcs/ide-vcs-uri.h
index ee29b35a6..119275dbc 100644
--- a/src/libide/vcs/ide-vcs-uri.h
+++ b/src/libide/vcs/ide-vcs-uri.h
@@ -20,9 +20,11 @@
 
 #pragma once
 
-#include <glib-object.h>
+#if !defined (IDE_VCS_INSIDE) && !defined (IDE_VCS_COMPILATION)
+# error "Only <libide-vcs.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
@@ -31,42 +33,44 @@ G_BEGIN_DECLS
 typedef struct _IdeVcsUri IdeVcsUri;
 
 IDE_AVAILABLE_IN_3_32
-GType        ide_vcs_uri_get_type   (void);
+GType        ide_vcs_uri_get_type       (void);
 IDE_AVAILABLE_IN_3_32
-IdeVcsUri   *ide_vcs_uri_new        (const gchar     *uri);
+IdeVcsUri   *ide_vcs_uri_new            (const gchar     *uri);
 IDE_AVAILABLE_IN_3_32
-IdeVcsUri   *ide_vcs_uri_ref        (IdeVcsUri       *self);
+IdeVcsUri   *ide_vcs_uri_ref            (IdeVcsUri       *self);
 IDE_AVAILABLE_IN_3_32
-void         ide_vcs_uri_unref      (IdeVcsUri       *self);
+void         ide_vcs_uri_unref          (IdeVcsUri       *self);
 IDE_AVAILABLE_IN_3_32
-const gchar *ide_vcs_uri_get_scheme (const IdeVcsUri *self);
+const gchar *ide_vcs_uri_get_scheme     (const IdeVcsUri *self);
 IDE_AVAILABLE_IN_3_32
-const gchar *ide_vcs_uri_get_user   (const IdeVcsUri *self);
+const gchar *ide_vcs_uri_get_user       (const IdeVcsUri *self);
 IDE_AVAILABLE_IN_3_32
-const gchar *ide_vcs_uri_get_host   (const IdeVcsUri *self);
+const gchar *ide_vcs_uri_get_host       (const IdeVcsUri *self);
 IDE_AVAILABLE_IN_3_32
-guint        ide_vcs_uri_get_port   (const IdeVcsUri *self);
+guint        ide_vcs_uri_get_port       (const IdeVcsUri *self);
 IDE_AVAILABLE_IN_3_32
-const gchar *ide_vcs_uri_get_path   (const IdeVcsUri *self);
+const gchar *ide_vcs_uri_get_path       (const IdeVcsUri *self);
 IDE_AVAILABLE_IN_3_32
-void         ide_vcs_uri_set_scheme (IdeVcsUri       *self,
-                                     const gchar     *scheme);
+void         ide_vcs_uri_set_scheme     (IdeVcsUri       *self,
+                                         const gchar     *scheme);
 IDE_AVAILABLE_IN_3_32
-void         ide_vcs_uri_set_user   (IdeVcsUri       *self,
-                                     const gchar     *user);
+void         ide_vcs_uri_set_user       (IdeVcsUri       *self,
+                                         const gchar     *user);
 IDE_AVAILABLE_IN_3_32
-void         ide_vcs_uri_set_host   (IdeVcsUri       *self,
-                                     const gchar     *host);
+void         ide_vcs_uri_set_host       (IdeVcsUri       *self,
+                                         const gchar     *host);
 IDE_AVAILABLE_IN_3_32
-void         ide_vcs_uri_set_port   (IdeVcsUri       *self,
-                                     guint            port);
+void         ide_vcs_uri_set_port       (IdeVcsUri       *self,
+                                         guint            port);
 IDE_AVAILABLE_IN_3_32
-void         ide_vcs_uri_set_path   (IdeVcsUri       *self,
-                                     const gchar     *path);
+void         ide_vcs_uri_set_path       (IdeVcsUri       *self,
+                                         const gchar     *path);
 IDE_AVAILABLE_IN_3_32
-gchar       *ide_vcs_uri_to_string  (const IdeVcsUri *self);
+gchar       *ide_vcs_uri_to_string      (const IdeVcsUri *self);
 IDE_AVAILABLE_IN_3_32
-gboolean     ide_vcs_uri_is_valid   (const gchar     *uri_string);
+gboolean     ide_vcs_uri_is_valid       (const gchar     *uri_string);
+IDE_AVAILABLE_IN_3_32
+gchar       *ide_vcs_uri_get_clone_name (const IdeVcsUri *self);
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeVcsUri, ide_vcs_uri_unref)
 
diff --git a/src/libide/vcs/ide-vcs.c b/src/libide/vcs/ide-vcs.c
index 2809fdbc1..f4c178efe 100644
--- a/src/libide/vcs/ide-vcs.c
+++ b/src/libide/vcs/ide-vcs.c
@@ -22,13 +22,12 @@
 
 #include "config.h"
 
+#include <libide-io.h>
 #include <string.h>
 
-#include "ide-context.h"
-
-#include "buffers/ide-buffer.h"
-#include "buffers/ide-buffer-change-monitor.h"
-#include "vcs/ide-vcs.h"
+#include "ide-directory-vcs.h"
+#include "ide-vcs.h"
+#include "ide-vcs-enums.h"
 
 G_DEFINE_INTERFACE (IdeVcs, ide_vcs, IDE_TYPE_OBJECT)
 
@@ -38,19 +37,6 @@ enum {
 };
 
 static guint signals [N_SIGNALS];
-static GPtrArray *ignored;
-
-G_LOCK_DEFINE_STATIC (ignored);
-
-void
-ide_vcs_register_ignored (const gchar *pattern)
-{
-  G_LOCK (ignored);
-  if (ignored == NULL)
-    ignored = g_ptr_array_new ();
-  g_ptr_array_add (ignored, g_pattern_spec_new (pattern));
-  G_UNLOCK (ignored);
-}
 
 static void
 ide_vcs_real_list_status_async (IdeVcs              *self,
@@ -93,7 +79,7 @@ ide_vcs_default_init (IdeVcsInterface *iface)
                                                             (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
 
   g_object_interface_install_property (iface,
-                                       g_param_spec_object ("working-directory",
+                                       g_param_spec_object ("workdir",
                                                             "Working Directory",
                                                             "The working directory for the VCS",
                                                             G_TYPE_FILE,
@@ -119,14 +105,6 @@ ide_vcs_default_init (IdeVcsInterface *iface)
   g_signal_set_va_marshaller (signals [CHANGED],
                               G_TYPE_FROM_INTERFACE (iface),
                               g_cclosure_marshal_VOID__VOIDv);
-
-
-  /* Ignore Gio temporary files */
-  ide_vcs_register_ignored (".goutputstream-*");
-
-  /* Ignore minified JS */
-  ide_vcs_register_ignored ("*.min.js");
-  ide_vcs_register_ignored ("*.min.js.*");
 }
 
 /**
@@ -156,54 +134,22 @@ ide_vcs_is_ignored (IdeVcs  *self,
                     GFile   *file,
                     GError **error)
 {
-  g_autofree gchar *name = NULL;
-  g_autofree gchar *reversed = NULL;
-  gboolean ret = FALSE;
-  gsize len;
-
   g_return_val_if_fail (!self || IDE_IS_VCS (self), FALSE);
   g_return_val_if_fail (!file || G_IS_FILE (file), FALSE);
 
   if (file == NULL)
     return TRUE;
 
-  name = g_file_get_basename (file);
-  if (name == NULL || *name == 0)
-    return TRUE;
-
-  len = strlen (name);
-
-  /* Ignore builtin backup files by GIO */
-  if (name[len - 1] == '~')
+  if (ide_g_file_is_ignored (file))
     return TRUE;
 
-  reversed = g_utf8_strreverse (name, len);
-
-  G_LOCK (ignored);
-
-  if G_LIKELY (ignored != NULL)
-    {
-      for (guint i = 0; i < ignored->len; i++)
-        {
-          GPatternSpec *pattern_spec = g_ptr_array_index (ignored, i);
-
-          if (g_pattern_match (pattern_spec, len, name, reversed))
-            {
-              ret = TRUE;
-              break;
-            }
-        }
-    }
-
-  G_UNLOCK (ignored);
-
   if (self != NULL)
     {
-      if (!ret && IDE_VCS_GET_IFACE (self)->is_ignored)
-        ret = IDE_VCS_GET_IFACE (self)->is_ignored (self, file, error);
+      if (IDE_VCS_GET_IFACE (self)->is_ignored)
+        return IDE_VCS_GET_IFACE (self)->is_ignored (self, file, error);
     }
 
-  return ret;
+  return FALSE;
 }
 
 /**
@@ -235,9 +181,6 @@ ide_vcs_path_is_ignored (IdeVcs       *self,
                          const gchar  *path,
                          GError      **error)
 {
-  g_autofree gchar *name = NULL;
-  g_autofree gchar *reversed = NULL;
-  gsize len;
   gboolean ret = FALSE;
 
   g_return_val_if_fail (!self || IDE_IS_VCS (self), FALSE);
@@ -245,36 +188,9 @@ ide_vcs_path_is_ignored (IdeVcs       *self,
   if (path == NULL)
     return TRUE;
 
-  name = g_path_get_basename (path);
-  if (name == NULL || *name == 0)
+  if (ide_path_is_ignored (path))
     return TRUE;
 
-  len = strlen (name);
-
-  /* Ignore builtin backup files by GIO */
-  if (name[len - 1] == '~')
-    return TRUE;
-
-  reversed = g_utf8_strreverse (name, len);
-
-  G_LOCK (ignored);
-
-  if G_LIKELY (ignored != NULL)
-    {
-      for (guint i = 0; i < ignored->len; i++)
-        {
-          GPatternSpec *pattern_spec = g_ptr_array_index (ignored, i);
-
-          if (g_pattern_match (pattern_spec, len, name, reversed))
-            {
-              ret = TRUE;
-              break;
-            }
-        }
-    }
-
-  G_UNLOCK (ignored);
-
   if (self != NULL)
     {
       if (!ret && IDE_VCS_GET_IFACE (self)->is_ignored)
@@ -284,7 +200,7 @@ ide_vcs_path_is_ignored (IdeVcs       *self,
           if (g_path_is_absolute (path))
             file = g_file_new_for_path (path);
           else
-            file = g_file_get_child (ide_vcs_get_working_directory (self), path);
+            file = g_file_get_child (ide_vcs_get_workdir (self), path);
 
           ret = IDE_VCS_GET_IFACE (self)->is_ignored (self, file, error);
         }
@@ -307,7 +223,7 @@ ide_vcs_get_priority (IdeVcs *self)
 }
 
 /**
- * ide_vcs_get_working_directory:
+ * ide_vcs_get_workdir:
  * @self: An #IdeVcs.
  *
  * Retrieves the working directory for the context. This is the root of where
@@ -325,94 +241,16 @@ ide_vcs_get_priority (IdeVcs *self)
  *   implementing #IdeVcs are required to ensure this invariant holds true.
  */
 GFile *
-ide_vcs_get_working_directory (IdeVcs *self)
+ide_vcs_get_workdir (IdeVcs *self)
 {
   g_return_val_if_fail (IDE_IS_VCS (self), NULL);
 
-  if (IDE_VCS_GET_IFACE (self)->get_working_directory)
-    return IDE_VCS_GET_IFACE (self)->get_working_directory (self);
+  if (IDE_VCS_GET_IFACE (self)->get_workdir)
+    return IDE_VCS_GET_IFACE (self)->get_workdir (self);
 
   return NULL;
 }
 
-/**
- * ide_vcs_get_buffer_change_monitor:
- *
- * Gets an #IdeBufferChangeMonitor for the buffer provided. If the #IdeVcs implementation does not
- * support change monitoring, or cannot for the current file, then %NULL is returned.
- *
- * Returns: (transfer full) (nullable): An #IdeBufferChangeMonitor or %NULL.
- *
- * Since: 3.32
- */
-IdeBufferChangeMonitor *
-ide_vcs_get_buffer_change_monitor (IdeVcs    *self,
-                                   IdeBuffer *buffer)
-{
-  IdeBufferChangeMonitor *ret = NULL;
-
-  g_return_val_if_fail (IDE_IS_VCS (self), NULL);
-  g_return_val_if_fail (IDE_IS_BUFFER (buffer), NULL);
-
-  if (IDE_VCS_GET_IFACE (self)->get_buffer_change_monitor)
-    ret = IDE_VCS_GET_IFACE (self)->get_buffer_change_monitor (self, buffer);
-
-  g_return_val_if_fail (!ret || IDE_IS_BUFFER_CHANGE_MONITOR (ret), NULL);
-
-  return ret;
-}
-
-static gint
-sort_by_priority (gconstpointer a,
-                  gconstpointer b,
-                  gpointer      user_data)
-{
-  IdeVcs *vcs_a = *(IdeVcs **)a;
-  IdeVcs *vcs_b = *(IdeVcs **)b;
-
-  return ide_vcs_get_priority (vcs_a) - ide_vcs_get_priority (vcs_b);
-}
-
-void
-ide_vcs_new_async (IdeContext           *context,
-                   int                   io_priority,
-                   GCancellable         *cancellable,
-                   GAsyncReadyCallback   callback,
-                   gpointer              user_data)
-{
-  ide_object_new_for_extension_async (IDE_TYPE_VCS,
-                                      sort_by_priority,
-                                      NULL,
-                                      io_priority,
-                                      cancellable,
-                                      callback,
-                                      user_data,
-                                      "context", context,
-                                      NULL);
-}
-
-/**
- * ide_vcs_new_finish:
- *
- * Completes a call to ide_vcs_new_async().
- *
- * Returns: (transfer full): An #IdeVcs.
- *
- * Since: 3.32
- */
-IdeVcs *
-ide_vcs_new_finish (GAsyncResult  *result,
-                    GError       **error)
-{
-  IdeObject *ret;
-
-  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
-
-  ret = ide_object_new_finish (result, error);
-
-  return IDE_VCS (ret);
-}
-
 void
 ide_vcs_emit_changed (IdeVcs *self)
 {
@@ -458,12 +296,19 @@ ide_vcs_get_config (IdeVcs *self)
 gchar *
 ide_vcs_get_branch_name (IdeVcs *self)
 {
+  gchar *ret = NULL;
+
   g_return_val_if_fail (IDE_IS_VCS (self), NULL);
 
+  ide_object_lock (IDE_OBJECT (self));
   if (IDE_VCS_GET_IFACE (self)->get_branch_name)
-    return IDE_VCS_GET_IFACE (self)->get_branch_name (self);
+    ret = IDE_VCS_GET_IFACE (self)->get_branch_name (self);
+  ide_object_unlock (IDE_OBJECT (self));
+
+  if (ret == NULL)
+    ret = g_strdup ("primary");
 
-  return g_strdup ("primary");
+  return ret;
 }
 
 /**
@@ -502,7 +347,7 @@ ide_vcs_list_status_async (IdeVcs              *self,
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   if (directory_or_file == NULL)
-    directory_or_file = ide_vcs_get_working_directory (self);
+    directory_or_file = ide_vcs_get_workdir (self);
 
   IDE_VCS_GET_IFACE (self)->list_status_async (self,
                                                directory_or_file,
@@ -540,3 +385,58 @@ ide_vcs_list_status_finish (IdeVcs        *self,
 
   return IDE_VCS_GET_IFACE (self)->list_status_finish (self, result, error);
 }
+
+/**
+ * ide_vcs_from_context:
+ * @context: an #IdeContext
+ *
+ * Gets the #IdeVcs for the context.
+ *
+ * Returns: (transfer none): an #IdeVcs
+ *
+ * Since: 3.32
+ */
+IdeVcs *
+ide_vcs_from_context (IdeContext *context)
+{
+  IdeVcs *ret;
+
+  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  /* Release full reference, into borrowed ref */
+  ret = ide_vcs_ref_from_context (context);
+  g_object_unref (ret);
+
+  return ret;
+}
+
+/**
+ * ide_vcs_ref_from_context:
+ * @context: an #IdeContext
+ *
+ * A thread-safe version of ide_vcs_from_context().
+ *
+ * Returns: (transfer full): an #IdeVcs
+ *
+ * Since: 3.32
+ */
+IdeVcs *
+ide_vcs_ref_from_context (IdeContext *context)
+{
+  IdeVcs *ret;
+
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  ide_object_lock (IDE_OBJECT (context));
+  ret = ide_object_get_child_typed (IDE_OBJECT (context), IDE_TYPE_VCS);
+  if (ret == NULL)
+    {
+      g_autoptr(GFile) workdir = ide_context_ref_workdir (context);
+      ret = (IdeVcs *)ide_directory_vcs_new (workdir);
+      ide_object_prepend (IDE_OBJECT (context), IDE_OBJECT (ret));
+    }
+  ide_object_unlock (IDE_OBJECT (context));
+
+  return g_steal_pointer (&ret);
+}
diff --git a/src/libide/vcs/ide-vcs.h b/src/libide/vcs/ide-vcs.h
index 22a78d53b..aa5824a0a 100644
--- a/src/libide/vcs/ide-vcs.h
+++ b/src/libide/vcs/ide-vcs.h
@@ -20,12 +20,13 @@
 
 #pragma once
 
-#include <gio/gio.h>
+#if !defined (IDE_VCS_INSIDE) && !defined (IDE_VCS_COMPILATION)
+# error "Only <libide-vcs.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
-#include "ide-object.h"
-#include "vcs/ide-vcs-config.h"
+#include "ide-vcs-config.h"
 
 G_BEGIN_DECLS
 
@@ -38,9 +39,7 @@ struct _IdeVcsInterface
 {
   GTypeInterface            parent_interface;
 
-  GFile                  *(*get_working_directory)     (IdeVcs               *self);
-  IdeBufferChangeMonitor *(*get_buffer_change_monitor) (IdeVcs               *self,
-                                                        IdeBuffer            *buffer);
+  GFile                  *(*get_workdir)               (IdeVcs               *self);
   gboolean                (*is_ignored)                (IdeVcs               *self,
                                                         GFile                *file,
                                                         GError              **error);
@@ -61,21 +60,11 @@ struct _IdeVcsInterface
 };
 
 IDE_AVAILABLE_IN_3_32
-void                    ide_vcs_register_ignored          (const gchar          *pattern);
+IdeVcs                 *ide_vcs_from_context              (IdeContext           *context);
 IDE_AVAILABLE_IN_3_32
-IdeBufferChangeMonitor *ide_vcs_get_buffer_change_monitor (IdeVcs               *self,
-                                                           IdeBuffer            *buffer);
+IdeVcs                 *ide_vcs_ref_from_context          (IdeContext           *context);
 IDE_AVAILABLE_IN_3_32
-GFile                  *ide_vcs_get_working_directory     (IdeVcs               *self);
-IDE_AVAILABLE_IN_3_32
-void                    ide_vcs_new_async                 (IdeContext           *context,
-                                                           int                   io_priority,
-                                                           GCancellable         *cancellable,
-                                                           GAsyncReadyCallback   callback,
-                                                           gpointer              user_data);
-IDE_AVAILABLE_IN_3_32
-IdeVcs                 *ide_vcs_new_finish                (GAsyncResult         *result,
-                                                           GError              **error);
+GFile                  *ide_vcs_get_workdir               (IdeVcs               *self);
 IDE_AVAILABLE_IN_3_32
 gboolean                ide_vcs_is_ignored                (IdeVcs               *self,
                                                            GFile                *file,
diff --git a/src/libide/vcs/libide-vcs.h b/src/libide/vcs/libide-vcs.h
new file mode 100644
index 000000000..bc6352468
--- /dev/null
+++ b/src/libide/vcs/libide-vcs.h
@@ -0,0 +1,38 @@
+/* ide-vcs.h
+ *
+ * Copyright 2014-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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+#include <libide-io.h>
+
+#define IDE_VCS_INSIDE
+
+#include "ide-directory-vcs.h"
+#include "ide-vcs-cloner.h"
+#include "ide-vcs-config.h"
+#include "ide-vcs-enums.h"
+#include "ide-vcs-initializer.h"
+#include "ide-vcs-uri.h"
+#include "ide-vcs-file-info.h"
+#include "ide-vcs.h"
+#include "ide-vcs-monitor.h"
+
+#undef IDE_VCS_INSIDE
diff --git a/src/libide/vcs/meson.build b/src/libide/vcs/meson.build
index 551dbaa30..14beb6904 100644
--- a/src/libide/vcs/meson.build
+++ b/src/libide/vcs/meson.build
@@ -1,14 +1,38 @@
-vcs_headers = [
+libide_vcs_header_dir = join_paths(libide_header_dir, 'vcs')
+libide_vcs_header_subdir = join_paths(libide_header_subdir, 'vcs')
+libide_include_directories += include_directories('.')
+
+#
+# Public API Headers
+#
+
+libide_vcs_public_headers = [
+  'ide-directory-vcs.h',
   'ide-vcs-config.h',
+  'ide-vcs-cloner.h',
   'ide-vcs-file-info.h',
   'ide-vcs-initializer.h',
   'ide-vcs-monitor.h',
   'ide-vcs-uri.h',
   'ide-vcs.h',
+  'libide-vcs.h',
+]
+
+libide_vcs_enum_headers = [
+  'ide-vcs-config.h',
+  'ide-vcs-file-info.h',
 ]
 
-vcs_sources = [
+install_headers(libide_vcs_public_headers, subdir: libide_vcs_header_subdir)
+
+#
+# Sources
+#
+
+libide_vcs_public_sources = [
+  'ide-directory-vcs.c',
   'ide-vcs-config.c',
+  'ide-vcs-cloner.c',
   'ide-vcs-file-info.c',
   'ide-vcs-initializer.c',
   'ide-vcs-monitor.c',
@@ -16,13 +40,54 @@ vcs_sources = [
   'ide-vcs.c',
 ]
 
-vcs_enums = [
-  'ide-vcs-config.h',
-  'ide-vcs-file-info.h',
+#
+# Enum generation
+#
+
+libide_vcs_enums = gnome.mkenums_simple('ide-vcs-enums',
+     body_prefix: '#include "config.h"',
+   header_prefix: '#include <libide-core.h>',
+       decorator: '_IDE_EXTERN',
+         sources: libide_vcs_enum_headers,
+  install_header: true,
+     install_dir: libide_vcs_header_dir,
+)
+libide_vcs_generated_sources = [libide_vcs_enums[0]]
+libide_vcs_generated_headers = [libide_vcs_enums[1]]
+
+#
+# Dependencies
+#
+
+libide_vcs_deps = [
+  libgio_dep,
+  libgtk_dep,
+
+  libide_core_dep,
+  libide_io_dep,
+  libide_threading_dep,
 ]
 
-libide_public_headers += files(vcs_headers)
-libide_public_sources += files(vcs_sources)
-libide_enum_headers += files(vcs_enums)
+#
+# Library Definitions
+#
+
+libide_vcs = static_library('ide-vcs-' + libide_api_version,
+   libide_vcs_public_sources + libide_vcs_generated_sources + libide_vcs_generated_headers,
+   dependencies: libide_vcs_deps,
+         c_args: libide_args + release_args + ['-DIDE_VCS_COMPILATION'],
+)
+
+libide_vcs_dep = declare_dependency(
+         dependencies: libide_vcs_deps,
+           link_whole: libide_vcs,
+  include_directories: include_directories('.'),
+              sources: libide_vcs_generated_headers,
+)
 
-install_headers(vcs_headers, subdir: join_paths(libide_header_subdir, 'vcs'))
+gnome_builder_public_sources += files(libide_vcs_public_sources)
+gnome_builder_public_headers += files(libide_vcs_public_headers)
+gnome_builder_generated_headers += libide_vcs_generated_headers
+gnome_builder_generated_sources += libide_vcs_generated_sources
+gnome_builder_include_subdirs += libide_vcs_header_subdir
+gnome_builder_gir_extra_args += ['--c-include=libide-vcs.h', '-DIDE_VCS_COMPILATION']
diff --git a/src/plugins/vcsui/gbp-vcsui-editor-page-addin.c b/src/plugins/vcsui/gbp-vcsui-editor-page-addin.c
new file mode 100644
index 000000000..f952b7582
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-editor-page-addin.c
@@ -0,0 +1,137 @@
+/* gbp-vcsui-editor-page-addin.c
+ *
+ * Copyright 2018-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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-vcsui-editor-page-addin"
+
+#include "config.h"
+
+#include <libide-editor.h>
+
+#include "gbp-vcsui-editor-page-addin.h"
+
+struct _GbpVcsuiEditorPageAddin
+{
+  GObject parent_instance;
+};
+
+static void
+on_push_snippet_cb (GbpVcsuiEditorPageAddin *self,
+                    IdeSnippet              *snippet,
+                    GtkTextIter             *iter,
+                    IdeSourceView           *source_view)
+{
+  g_autoptr(IdeVcsConfig) vcs_config = NULL;
+  g_autoptr(IdeContext) ide_context = NULL;
+  IdeSnippetContext *context;
+  IdeBuffer *buffer;
+  IdeVcs *vcs;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_VCSUI_EDITOR_PAGE_ADDIN (self));
+  g_assert (IDE_IS_SNIPPET (snippet));
+  g_assert (iter != NULL);
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+  buffer = IDE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view)));
+  ide_context = ide_buffer_ref_context (buffer);
+  context = ide_snippet_get_context (snippet);
+
+  if ((vcs = ide_vcs_from_context (ide_context)) &&
+       (vcs_config = ide_vcs_get_config (vcs)))
+    {
+      GValue value = G_VALUE_INIT;
+
+      g_value_init (&value, G_TYPE_STRING);
+
+      ide_vcs_config_get_config (vcs_config, IDE_VCS_CONFIG_FULL_NAME, &value);
+
+      if (!ide_str_empty0 (g_value_get_string (&value)))
+        {
+          ide_snippet_context_add_shared_variable (context, "author", g_value_get_string (&value));
+          ide_snippet_context_add_shared_variable (context, "fullname", g_value_get_string (&value));
+          ide_snippet_context_add_shared_variable (context, "username", g_value_get_string (&value));
+        }
+
+      g_value_reset (&value);
+
+      ide_vcs_config_get_config (vcs_config, IDE_VCS_CONFIG_EMAIL, &value);
+
+      if (!ide_str_empty0 (g_value_get_string (&value)))
+        ide_snippet_context_add_shared_variable (context, "email", g_value_get_string (&value));
+
+      g_value_unset (&value);
+    }
+}
+
+static void
+gbp_vcsui_editor_page_addin_load (IdeEditorPageAddin *addin,
+                                  IdeEditorPage      *page)
+{
+  IdeSourceView *source_view;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_EDITOR_PAGE_ADDIN (addin));
+  g_assert (IDE_IS_EDITOR_PAGE (page));
+
+  source_view = ide_editor_page_get_view (page);
+
+  g_signal_connect_object (source_view,
+                           "push-snippet",
+                           G_CALLBACK (on_push_snippet_cb),
+                           addin,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+gbp_vcsui_editor_page_addin_unload (IdeEditorPageAddin *addin,
+                                    IdeEditorPage      *page)
+{
+  IdeSourceView *source_view;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_EDITOR_PAGE_ADDIN (addin));
+  g_assert (IDE_IS_EDITOR_PAGE (page));
+
+  source_view = ide_editor_page_get_view (page);
+
+  g_signal_handlers_disconnect_by_func (source_view,
+                                        G_CALLBACK (on_push_snippet_cb),
+                                        addin);
+}
+
+static void
+editor_page_addin_iface_init (IdeEditorPageAddinInterface *iface)
+{
+  iface->load = gbp_vcsui_editor_page_addin_load;
+  iface->unload = gbp_vcsui_editor_page_addin_unload;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpVcsuiEditorPageAddin, gbp_vcsui_editor_page_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_EDITOR_PAGE_ADDIN, editor_page_addin_iface_init))
+
+static void
+gbp_vcsui_editor_page_addin_class_init (GbpVcsuiEditorPageAddinClass *klass)
+{
+}
+
+static void
+gbp_vcsui_editor_page_addin_init (GbpVcsuiEditorPageAddin *self)
+{
+}
diff --git a/src/plugins/vcsui/gbp-vcsui-editor-page-addin.h b/src/plugins/vcsui/gbp-vcsui-editor-page-addin.h
new file mode 100644
index 000000000..d69bcd0d6
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-editor-page-addin.h
@@ -0,0 +1,31 @@
+/* gbp-vcsui-editor-page-addin.h
+ *
+ * Copyright 2018-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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_VCSUI_EDITOR_PAGE_ADDIN (gbp_vcsui_editor_page_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpVcsuiEditorPageAddin, gbp_vcsui_editor_page_addin, GBP, VCSUI_EDITOR_PAGE_ADDIN, 
GObject)
+
+G_END_DECLS
diff --git a/src/plugins/vcsui/gbp-vcsui-tree-addin.c b/src/plugins/vcsui/gbp-vcsui-tree-addin.c
new file mode 100644
index 000000000..37171f618
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-tree-addin.c
@@ -0,0 +1,209 @@
+/* gbp-vcsui-tree-addin.c
+ *
+ * Copyright 2018-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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-vcsui-tree-addin"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+#include <libide-foundry.h>
+#include <libide-gui.h>
+#include <libide-plugins.h>
+#include <libide-threading.h>
+#include <libide-tree.h>
+#include <libide-vcs.h>
+
+#include "gbp-vcsui-tree-addin.h"
+
+struct _GbpVcsuiTreeAddin
+{
+  GObject        parent_instance;
+
+  IdeTree       *tree;
+  IdeTreeModel  *model;
+  IdeVcs        *vcs;
+  IdeVcsMonitor *monitor;
+
+  GdkRGBA        added_color;
+  GdkRGBA        changed_color;
+};
+
+static void
+get_foreground_for_class (GtkStyleContext   *style_context,
+                          const gchar       *name,
+                          GdkRGBA           *rgba)
+{
+  GtkStateFlags state;
+
+  g_assert (GTK_IS_STYLE_CONTEXT (style_context));
+  g_assert (name != NULL);
+  g_assert (rgba != NULL);
+
+  state = gtk_style_context_get_state (style_context);
+  gtk_style_context_save (style_context);
+  gtk_style_context_add_class (style_context, name);
+  gtk_style_context_get_color (style_context, state, rgba);
+  gtk_style_context_restore (style_context);
+}
+
+static void
+on_tree_style_changed_cb (GbpVcsuiTreeAddin *self,
+                          GtkStyleContext   *context)
+{
+  g_assert (GBP_IS_VCSUI_TREE_ADDIN (self));
+  g_assert (GTK_IS_STYLE_CONTEXT (context));
+
+  get_foreground_for_class (context, "vcs-added", &self->added_color);
+  get_foreground_for_class (context, "vcs-changed", &self->changed_color);
+}
+
+static void
+gbp_vcsui_tree_addin_load (IdeTreeAddin *addin,
+                           IdeTree      *tree,
+                           IdeTreeModel *model)
+{
+  GbpVcsuiTreeAddin *self = (GbpVcsuiTreeAddin *)addin;
+  GtkStyleContext *style_context;
+  IdeWorkbench *workbench;
+  IdeVcsMonitor *monitor;
+  IdeVcs *vcs;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_VCSUI_TREE_ADDIN (self));
+  g_assert (IDE_IS_TREE (tree));
+  g_assert (IDE_IS_TREE_MODEL (model));
+
+  self->model = model;
+  self->tree = tree;
+
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (tree));
+  g_signal_connect_object (style_context,
+                           "changed",
+                           G_CALLBACK (on_tree_style_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  on_tree_style_changed_cb (self, style_context);
+
+  if ((workbench = ide_widget_get_workbench (GTK_WIDGET (tree))) &&
+      (vcs = ide_workbench_get_vcs (workbench)) &&
+      (monitor = ide_workbench_get_vcs_monitor (workbench)))
+    {
+      self->vcs = g_object_ref (vcs);
+      self->monitor = g_object_ref (monitor);
+      g_signal_connect_object (self->monitor,
+                               "changed",
+                               G_CALLBACK (gtk_widget_queue_draw),
+                               tree,
+                               G_CONNECT_SWAPPED);
+    }
+}
+
+static void
+gbp_vcsui_tree_addin_unload (IdeTreeAddin *addin,
+                             IdeTree      *tree,
+                             IdeTreeModel *model)
+{
+  GbpVcsuiTreeAddin *self = (GbpVcsuiTreeAddin *)addin;
+  GtkStyleContext *style_context;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_VCSUI_TREE_ADDIN (self));
+  g_assert (IDE_IS_TREE (tree));
+  g_assert (IDE_IS_TREE_MODEL (model));
+
+  style_context = gtk_widget_get_style_context (GTK_WIDGET (tree));
+  g_signal_handlers_disconnect_by_func (style_context,
+                                        G_CALLBACK (on_tree_style_changed_cb),
+                                        self);
+
+  g_clear_object (&self->monitor);
+  g_clear_object (&self->vcs);
+  self->model = NULL;
+  self->tree = NULL;
+}
+
+static void
+gbp_vcsui_tree_addin_selection_changed (IdeTreeAddin *addin,
+                                        IdeTreeNode  *node)
+{
+  GbpVcsuiTreeAddin *self = (GbpVcsuiTreeAddin *)addin;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_VCSUI_TREE_ADDIN (self));
+  g_assert (!node || IDE_IS_TREE_NODE (node));
+
+}
+
+static void
+gbp_vcsui_tree_addin_cell_data_func (IdeTreeAddin    *addin,
+                                     IdeTreeNode     *node,
+                                     GtkCellRenderer *cell)
+{
+  GbpVcsuiTreeAddin *self = (GbpVcsuiTreeAddin *)addin;
+  g_autoptr(IdeVcsFileInfo) info = NULL;
+  g_autoptr(GFile) file = NULL;
+  IdeProjectFile *project_file;
+
+  g_assert (GBP_IS_VCSUI_TREE_ADDIN (self));
+  g_assert (IDE_IS_TREE_NODE (node));
+  g_assert (GTK_IS_CELL_RENDERER (cell));
+
+  if (self->monitor == NULL)
+    return;
+
+  if (!ide_tree_node_holds (node, IDE_TYPE_PROJECT_FILE))
+    return;
+
+  project_file = ide_tree_node_get_item (node);
+  file = ide_project_file_ref_file (project_file);
+
+  if ((info = ide_vcs_monitor_ref_info (self->monitor, file)))
+    {
+      IdeVcsFileStatus status = ide_vcs_file_info_get_status (info);
+
+      if (status == IDE_VCS_FILE_STATUS_ADDED)
+        g_object_set (cell, "foreground-rgba", &self->added_color, NULL);
+      else if (status == IDE_VCS_FILE_STATUS_CHANGED)
+        g_object_set (cell, "foreground-rgba", &self->changed_color, NULL);
+    }
+}
+
+static void
+tree_addin_iface_init (IdeTreeAddinInterface *iface)
+{
+  iface->cell_data_func = gbp_vcsui_tree_addin_cell_data_func;
+  iface->load = gbp_vcsui_tree_addin_load;
+  iface->selection_changed = gbp_vcsui_tree_addin_selection_changed;
+  iface->unload = gbp_vcsui_tree_addin_unload;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GbpVcsuiTreeAddin, gbp_vcsui_tree_addin, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_TREE_ADDIN, tree_addin_iface_init))
+
+static void
+gbp_vcsui_tree_addin_class_init (GbpVcsuiTreeAddinClass *klass)
+{
+}
+
+static void
+gbp_vcsui_tree_addin_init (GbpVcsuiTreeAddin *self)
+{
+}
diff --git a/src/plugins/vcsui/gbp-vcsui-tree-addin.h b/src/plugins/vcsui/gbp-vcsui-tree-addin.h
new file mode 100644
index 000000000..09a48ded1
--- /dev/null
+++ b/src/plugins/vcsui/gbp-vcsui-tree-addin.h
@@ -0,0 +1,31 @@
+/* gbp-vcsui-tree-addin.h
+ *
+ * Copyright 2018-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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_VCSUI_TREE_ADDIN (gbp_vcsui_tree_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpVcsuiTreeAddin, gbp_vcsui_tree_addin, GBP, VCSUI_TREE_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/src/plugins/vcsui/gtk/menus.ui b/src/plugins/vcsui/gtk/menus.ui
new file mode 100644
index 000000000..6d37a1313
--- /dev/null
+++ b/src/plugins/vcsui/gtk/menus.ui
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!--
+  <menu id="project-tree-menu">
+    <section id="project-tree-menu-placeholder2">
+      <submenu id="project-tree-menu-version-control">
+        <attribute name="label" translatable="yes">Version Control</attribute>
+        <item>
+          <attribute name="label" translatable="yes">Restore File</attribute>
+          <attribute name="action">vcsui.restore-file</attribute>
+        </item>
+        <item>
+          <attribute name="label" translatable="yes">Browse History</attribute>
+          <attribute name="action">vcsui.browse-history</attribute>
+        </item>
+      </submenu>
+    </section>
+  </menu>
+  -->
+</interface>
diff --git a/src/plugins/vcsui/meson.build b/src/plugins/vcsui/meson.build
new file mode 100644
index 000000000..0d198a0af
--- /dev/null
+++ b/src/plugins/vcsui/meson.build
@@ -0,0 +1,13 @@
+plugins_sources += files([
+  'vcsui-plugin.c',
+  'gbp-vcsui-tree-addin.c',
+  'gbp-vcsui-editor-page-addin.c',
+])
+
+plugin_vcsui_resources = gnome.compile_resources(
+  'vcsui-resources',
+  'vcsui.gresource.xml',
+  c_name: 'gbp_vcsui',
+)
+
+plugins_sources += plugin_vcsui_resources[0]
diff --git a/src/plugins/vcsui/vcsui-plugin.c b/src/plugins/vcsui/vcsui-plugin.c
new file mode 100644
index 000000000..3d9d2f601
--- /dev/null
+++ b/src/plugins/vcsui/vcsui-plugin.c
@@ -0,0 +1,42 @@
+/* vcsui-plugin.c
+ *
+ * Copyright 2018-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
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "vcsui-plugin"
+
+#include "config.h"
+
+#include <libpeas/peas.h>
+#include <libide-editor.h>
+#include <libide-gui.h>
+#include <libide-tree.h>
+
+#include "gbp-vcsui-editor-page-addin.h"
+#include "gbp-vcsui-tree-addin.h"
+
+_IDE_EXTERN void
+_gbp_vcsui_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_EDITOR_PAGE_ADDIN,
+                                              GBP_TYPE_VCSUI_EDITOR_PAGE_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_TREE_ADDIN,
+                                              GBP_TYPE_VCSUI_TREE_ADDIN);
+}
diff --git a/src/plugins/vcsui/vcsui.gresource.xml b/src/plugins/vcsui/vcsui.gresource.xml
new file mode 100644
index 000000000..3dd0a29b3
--- /dev/null
+++ b/src/plugins/vcsui/vcsui.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/plugins/vcsui">
+    <file>vcsui.plugin</file>
+    <file preprocess="xml-stripblanks">gtk/menus.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/plugins/vcsui/vcsui.plugin b/src/plugins/vcsui/vcsui.plugin
new file mode 100644
index 000000000..8772dac74
--- /dev/null
+++ b/src/plugins/vcsui/vcsui.plugin
@@ -0,0 +1,11 @@
+[Plugin]
+Authors=Christian Hergert <christian hergert me>
+Builtin=true
+Copyright=Copyright © 2015-2018 Christian Hergert
+Depends=editor;project-tree;
+Description=Provides user interface components to display VCS
+Embedded=_gbp_vcsui_register_types
+Hidden=true
+Module=vcsui
+Name=VCS interface extensions
+X-Workspace-Kind=primary;


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