[geany/geany-plugins] f86e45: Merge branch 'git-changebar/wip'

Colomban Wendling git-noreply at xxxxx
Mon Jun 1 18:00:13 UTC 2015


Branch:      refs/heads/master
Author:      Colomban Wendling <ban at herbesfolles.org>
Committer:   Colomban Wendling <ban at herbesfolles.org>
Date:        Mon, 01 Jun 2015 18:00:13 UTC
Commit:      f86e458666ded448269c6c5ee2182109534968ec
             https://github.com/geany/geany-plugins/commit/f86e458666ded448269c6c5ee2182109534968ec

Log Message:
-----------
Merge branch 'git-changebar/wip'

Various Git Change Bar improvements, including Windows support and
handling of Git filters.

Closes #214.


Modified Paths:
--------------
    build/git-changebar.m4
    git-changebar/src/gcb-plugin.c
    git-changebar/wscript_configure

Modified: build/git-changebar.m4
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -5,7 +5,7 @@ AC_DEFUN([GP_CHECK_GITCHANGEBAR],
     GP_CHECK_PLUGIN_DEPS([GitChangeBar], [GITCHANGEBAR],
                          [$GP_GTK_PACKAGE >= 2.18
                           glib-2.0
-                          libgit2 >= 0.18])
+                          libgit2 >= 0.21])
 
     GP_COMMIT_PLUGIN_STATUS([GitChangeBar])
 


Modified: git-changebar/src/gcb-plugin.c
323 lines changed, 189 insertions(+), 134 deletions(-)
===================================================================
@@ -36,9 +36,6 @@
 # define git_libgit2_init     git_threads_init
 # define git_libgit2_shutdown git_threads_shutdown
 #endif
-#if LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR < 20
-# define git_diff_hunk git_diff_range
-#endif
 
 
 GeanyPlugin      *geany_plugin;
@@ -59,7 +56,7 @@ PLUGIN_SET_TRANSLATABLE_INFO (
 
 /* g_async_queue_push() doesn't allow for NULL data, so use a non-NULL fake
  * data that we know cannot ever be a valid job */
-#define QUIT_THREAD_JOB ((AsyncBlobJob *) (&G_queue))
+#define QUIT_THREAD_JOB ((AsyncBlobContentsJob *) (&G_queue))
 
 #define RESOURCES_ALLOCATED_QTAG \
   (g_quark_from_static_string (PLUGIN"/git-resources-allocated"))
@@ -78,17 +75,17 @@ enum {
   KB_COUNT
 };
 
-typedef void (*BlobReadyFunc) (const gchar *path,
-                               git_blob    *blob,
-                               gpointer     data);
-
-typedef struct AsyncBlobJob AsyncBlobJob;
-struct AsyncBlobJob {
-  gboolean      force;
-  gchar        *path;
-  git_blob     *blob;
-  BlobReadyFunc callback;
-  gpointer      user_data;
+typedef void (*BlobContentsReadyFunc) (const gchar *path,
+                                       git_buf     *buf,
+                                       gpointer     data);
+
+typedef struct AsyncBlobContentsJob AsyncBlobContentsJob;
+struct AsyncBlobContentsJob {
+  gboolean              force;
+  gchar                *path;
+  git_buf               buf;
+  BlobContentsReadyFunc callback;
+  gpointer              user_data;
 };
 
 typedef struct TooltipHunkData TooltipHunkData;
@@ -96,12 +93,12 @@ struct TooltipHunkData {
   gint            line;
   gboolean        found;
   GeanyDocument  *doc;
-  const git_blob *file_blob;
+  const git_buf  *buf;
   GtkTooltip     *tooltip;
 };
 
-#define TOOLTIP_HUNK_DATA_INIT(line, doc, blob, tooltip) \
-  { line, FALSE, doc, blob, tooltip }
+#define TOOLTIP_HUNK_DATA_INIT(line, doc, buf, tooltip) \
+  { line, FALSE, doc, buf, tooltip }
 
 typedef struct GotoNextHunkData GotoNextHunkData;
 struct GotoNextHunkData {
@@ -142,7 +139,7 @@ static void         write_setting_boolean       (GKeyFile      *kf,
 
 
 /* cache */
-static git_blob        *G_file_blob           = NULL;
+static git_buf          G_blob_contents       = { 0 };
 /* global state */
 static GAsyncQueue     *G_queue               = NULL;
 static GThread         *G_thread              = NULL;
@@ -183,21 +180,33 @@ static const struct {
 
 
 static void
-clear_cached_blob (void)
+buf_zero (git_buf *buf)
 {
-  if (G_file_blob) {
-    git_blob_free (G_file_blob);
-    G_file_blob = NULL;
+  if (buf) {
+    buf->ptr = NULL;
+    buf->size = 0;
+    buf->asize = 0;
+  }
+}
+
+static void
+clear_cached_blob_contents (void)
+{
+  if (G_blob_contents.ptr) {
+    git_buf_free (&G_blob_contents);
+    buf_zero (&G_blob_contents);
   }
 }
 
 /* get the file blob for @relpath at HEAD */
-static git_blob *
-repo_get_file_blob (git_repository *repo,
-                    const gchar    *relpath)
+static gboolean
+repo_get_file_blob_contents (git_repository  *repo,
+                             const gchar     *relpath,
+                             git_buf         *contents,
+                             int              check_for_binary_data)
 {
-  git_reference  *head = NULL;
-  git_blob       *blob = NULL;
+  git_reference  *head    = NULL;
+  gboolean        success = FALSE;
   
   if (git_repository_head (&head, repo) == 0) {
     git_commit *commit = NULL;
@@ -209,7 +218,16 @@ repo_get_file_blob (git_repository *repo,
         git_tree_entry *entry = NULL;
         
         if (git_tree_entry_bypath (&entry, tree, relpath) == 0) {
-          git_blob_lookup (&blob, repo, git_tree_entry_id (entry));
+          git_blob *blob;
+          
+          if (git_blob_lookup (&blob, repo, git_tree_entry_id (entry)) == 0) {
+            if (git_blob_filtered_content (contents, blob, relpath,
+                                           check_for_binary_data) == 0 &&
+                git_buf_grow (contents, 0) == 0) {
+              success = TRUE;
+            }
+            git_blob_free (blob);
+          }
           git_tree_entry_free (entry);
         }
         git_tree_free (tree);
@@ -219,14 +237,18 @@ repo_get_file_blob (git_repository *repo,
     git_reference_free (head);
   }
   
-  return blob;
+  return success;
 }
 
 static void
 free_job (gpointer data)
 {
-  AsyncBlobJob *job = data;
+  AsyncBlobContentsJob *job = data;
   
+  /* unlikely, but if we still have the buffer, free it */
+  if (job->buf.ptr) {
+    git_buf_free (&job->buf);
+  }
   g_free (job->path);
   g_slice_free1 (sizeof *job, job);
 }
@@ -234,13 +256,15 @@ free_job (gpointer data)
 static gboolean
 report_work_in_idle (gpointer data)
 {
-  AsyncBlobJob *job = data;
+  AsyncBlobContentsJob *job = data;
   
   /* update cached blob */
-  clear_cached_blob ();
-  G_file_blob = job->blob;
+  clear_cached_blob_contents ();
+  G_blob_contents = job->buf;
+  
+  job->callback (job->path, job->buf.ptr ? &job->buf : NULL, job->user_data);
   
-  job->callback (job->path, job->blob, job->user_data);
+  buf_zero (&job->buf);
   
   return FALSE;
 }
@@ -290,20 +314,72 @@ monitor_head_ref (git_repository *repo,
   return monitor;
 }
 
+/* checks whether @path points somewhere inside @dir and returns the pointer
+ * inside @path starting the relative path, or NULL */
+static const gchar *
+path_dir_contains (const gchar *dir,
+                   const gchar *path)
+{
+#ifdef G_OS_WIN32
+  /* FIXME: handle drive letters and such */
+# define NORM_PATH_CH(c) (((c) == '\\') ? '/' : (c))
+#else
+# define NORM_PATH_CH(c) (c)
+#endif
+  
+  g_return_val_if_fail (dir != NULL, NULL);
+  g_return_val_if_fail (path != NULL, NULL);
+  
+  while (*dir && NORM_PATH_CH (*dir) == NORM_PATH_CH (*path)) {
+    dir++, path++;
+  }
+  
+  return *dir ? NULL : path;
+}
+
+/* gets the Git path for @repo pointing to @sys_path, or NULL */
+static gchar *
+get_path_in_repository (git_repository *repo,
+                        const gchar    *sys_path)
+{
+  const gchar  *workdir   = git_repository_workdir (repo);
+  const gchar  *rel_path  = path_dir_contains (workdir, sys_path);
+  
+#ifdef G_OS_WIN32
+  if (rel_path) {
+    /* we want an internal Git path, which uses UNIX format */
+    gchar  *p;
+    gchar  *repo_path = g_strdup (rel_path);
+    
+    for (p = repo_path; *p; p++) {
+      if (*p == '\\') {
+        *p = '/';
+      }
+    }
+    
+    return repo_path;
+  }
+  
+  return NULL;
+#else
+  return g_strdup (rel_path);
+#endif
+}
+
 static gpointer
 worker_thread (gpointer data)
 {
-  GAsyncQueue    *queue       = data;
-  git_repository *repo        = NULL;
-  GFileMonitor   *monitors[2] = { NULL, NULL };
-  AsyncBlobJob   *job;
-  guint           i;
+  GAsyncQueue          *queue       = data;
+  git_repository       *repo        = NULL;
+  GFileMonitor         *monitors[2] = { NULL, NULL };
+  AsyncBlobContentsJob *job;
+  guint                 i;
   
   while ((job = g_async_queue_pop (queue)) != QUIT_THREAD_JOB) {
     const gchar *path = job->path;
     
     if (repo && (job->force ||
-                 ! g_str_has_prefix (path, git_repository_workdir (repo)))) {
+                 ! path_dir_contains (path, git_repository_workdir (repo)))) {
       /* FIXME: this can fail with nested repositories */
       git_repository_free (repo);
       repo = NULL;
@@ -330,12 +406,18 @@ worker_thread (gpointer data)
       }
     }
     
+    buf_zero (&job->buf);
     if (repo) {
-      const gchar *relpath = path + strlen (git_repository_workdir (repo));
+      gchar *relpath = get_path_in_repository (repo, path);
       
-      job->blob = repo_get_file_blob (repo, relpath);
-    } else {
-      job->blob = NULL;
+      if (relpath) {
+        if (! repo_get_file_blob_contents (repo, relpath, &job->buf, 0)) {
+          git_buf_free (&job->buf);
+          buf_zero (&job->buf);
+        }
+        
+        g_free (relpath);
+      }
     }
     
     g_idle_add_full (G_PRIORITY_LOW, report_work_in_idle, job, free_job);
@@ -355,28 +437,28 @@ worker_thread (gpointer data)
 }
 
 static void
-get_cached_blob_async (const gchar   *path,
-                       gboolean       force,
-                       BlobReadyFunc  callback,
-                       gpointer       user_data)
+get_cached_blob_contents_async (const gchar          *path,
+                                gboolean              force,
+                                BlobContentsReadyFunc callback,
+                                gpointer              user_data)
 {
-  if ((! force && G_file_blob) || ! path) {
-    callback (path, G_file_blob, user_data);
+  if ((! force && G_blob_contents.ptr) || ! path) {
+    callback (path, &G_blob_contents, user_data);
   } else {
-    AsyncBlobJob *job = g_slice_alloc (sizeof *job);
+    AsyncBlobContentsJob *job = g_slice_alloc (sizeof *job);
     
     job->force      = force;
     job->path       = g_strdup (path);
-    job->blob       = NULL;
     job->callback   = callback;
     job->user_data  = user_data;
+    buf_zero (&job->buf);
     
     if (! G_thread) {
       G_queue = g_async_queue_new ();
 #if GLIB_CHECK_VERSION (2, 32, 0)
       G_thread = g_thread_new (PLUGIN"/blob-worker", worker_thread, G_queue);
 #else
-      G_thread = g_thread_create (worker_thread, G_queue, NULL, NULL);
+      G_thread = g_thread_create (worker_thread, G_queue, FALSE, NULL);
 #endif
     }
     
@@ -524,10 +606,10 @@ convert_encoding_inplace (gchar       **buffer,
 }
 
 static int
-diff_blob_to_doc (const git_blob   *old_blob,
-                  GeanyDocument    *doc,
-                  git_diff_hunk_cb  hunk_cb,
-                  void             *payload)
+diff_buf_to_doc (const git_buf   *old_buf,
+                 GeanyDocument   *doc,
+                 git_diff_hunk_cb hunk_cb,
+                 void            *payload)
 {
   ScintillaObject  *sci = doc->editor->sci;
   git_diff_options  opts = GIT_DIFF_OPTIONS_INIT;
@@ -549,8 +631,8 @@ diff_blob_to_doc (const git_blob   *old_blob,
   opts.context_lines = 0;
   opts.flags = GIT_DIFF_FORCE_TEXT;
   
-  ret = git_diff_blob_to_buffer (old_blob, NULL, buf, len, NULL, &opts,
-                                 NULL, hunk_cb, NULL, payload);
+  ret = git_diff_buffers (old_buf->ptr, old_buf->size, NULL,
+                          buf, len, NULL, &opts, NULL, hunk_cb, NULL, payload);
   
   if (free_buf) {
     g_free (buf);
@@ -580,24 +662,12 @@ diff_hunk_cb (const git_diff_delta *delta,
   
   return 0;
 }
-#if LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR < 20
-static int
-diff_hunk_cb_wrapper (const git_diff_delta *delta,
-                      const git_diff_hunk  *hunk,
-                      const char           *header,
-                      size_t                header_len,
-                      void                 *data)
-{
-  return diff_hunk_cb (delta, hunk, data);
-}
-# define diff_hunk_cb diff_hunk_cb_wrapper
-#endif
 
 static GtkWidget *
-get_widget_for_blob_range (GeanyDocument   *doc,
-                           const git_blob  *blob,
-                           gint             line_start,
-                           gint             n_lines)
+get_widget_for_buf_range (GeanyDocument *doc,
+                          const git_buf *contents,
+                          gint           line_start,
+                          gint           n_lines)
 {
   ScintillaObject        *sci     = editor_create_widget (doc->editor);
   const GeanyIndentPrefs *iprefs  = editor_get_indent_prefs (doc->editor);
@@ -606,9 +676,9 @@ get_widget_for_blob_range (GeanyDocument   *doc,
   gint                    zoom;
   gint                    i;
   GtkAllocation           alloc;
-  gchar                  *buf;
-  gsize                   buf_len;
-  gboolean                free_buf = FALSE;
+  gchar                  *buf       = contents->ptr;
+  gsize                   buf_len   = contents->size;
+  gboolean                free_buf  = FALSE;
   
   gtk_widget_get_allocation (GTK_WIDGET (doc->editor->sci), &alloc);
   
@@ -629,9 +699,6 @@ get_widget_for_blob_range (GeanyDocument   *doc,
     scintilla_send_message (sci, SCI_SETMARGINWIDTHN, i, 0);
   }
   
-  buf_len = git_blob_rawsize (blob);
-  buf = (gchar *) git_blob_rawcontent (blob);
-  
   /* convert the buffer to UTF-8 if necessary */
   if (encoding_needs_coversion (doc->encoding)) {
     free_buf = convert_encoding_inplace (&buf, &buf_len, "UTF-8", doc->encoding,
@@ -683,9 +750,9 @@ tooltip_diff_hunk_cb (const git_diff_delta *delta,
   if (hunk->old_lines > 0 &&
       thd->line >= hunk->new_start &&
       thd->line < hunk->new_start + MAX (1, hunk->new_lines)) {
-    GtkWidget *old = get_widget_for_blob_range (thd->doc, thd->file_blob,
-                                                hunk->old_start - 1,
-                                                hunk->old_lines);
+    GtkWidget *old = get_widget_for_buf_range (thd->doc, thd->buf,
+                                               hunk->old_start - 1,
+                                               hunk->old_lines);
     
     gtk_tooltip_set_custom (thd->tooltip, old);
     thd->found = old != NULL;
@@ -693,18 +760,6 @@ tooltip_diff_hunk_cb (const git_diff_delta *delta,
   
   return thd->found;
 }
-#if LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR < 20
-static int
-tooltip_diff_hunk_cb_wrapper (const git_diff_delta *delta,
-                              const git_diff_hunk  *hunk,
-                              const char           *header,
-                              size_t                header_len,
-                              void                 *data)
-{
-  return tooltip_diff_hunk_cb (delta, hunk, data);
-}
-# define tooltip_diff_hunk_cb tooltip_diff_hunk_cb_wrapper
-#endif
 
 static gboolean
 on_sci_query_tooltip (GtkWidget  *widget,
@@ -729,17 +784,17 @@ on_sci_query_tooltip (GtkWidget  *widget,
   min_x = scintilla_send_message (sci, SCI_GETMARGINWIDTHN, 0, 0);
   max_x = min_x + scintilla_send_message (sci, SCI_GETMARGINWIDTHN, 1, 0);
   
-  if (x >= min_x && x <= max_x && G_file_blob) {
+  if (x >= min_x && x <= max_x && G_blob_contents.ptr) {
     gint pos  = scintilla_send_message (sci, SCI_POSITIONFROMPOINT, x, y);
     gint line = sci_get_line_from_position (sci, pos);
     gint mask = scintilla_send_message (sci, SCI_MARKERGET, line, 0);
     
     if (mask & ((1 << G_markers[MARKER_LINE_CHANGED].num) |
                 (1 << G_markers[MARKER_LINE_REMOVED].num))) {
-      TooltipHunkData thd = TOOLTIP_HUNK_DATA_INIT (line + 1, doc, G_file_blob,
-                                                    tooltip);
+      TooltipHunkData thd = TOOLTIP_HUNK_DATA_INIT (line + 1, doc,
+                                                    &G_blob_contents, tooltip);
       
-      diff_blob_to_doc (G_file_blob, doc, tooltip_diff_hunk_cb, &thd);
+      diff_buf_to_doc (&G_blob_contents, doc, tooltip_diff_hunk_cb, &thd);
       has_tooltip = thd.found;
     }
   }
@@ -749,22 +804,34 @@ on_sci_query_tooltip (GtkWidget  *widget,
 
 static void
 update_diff (const gchar *path,
-             git_blob    *blob,
+             git_buf     *contents,
              gpointer     data)
 {
   GeanyDocument *doc = document_get_current ();
   
-  if (doc && doc->id == GPOINTER_TO_UINT (data) &&
-      blob && allocate_resources (doc->editor->sci)) {
+  if (doc && doc->id == GPOINTER_TO_UINT (data)) {
     ScintillaObject  *sci = doc->editor->sci;
-    guint             i;
+    gboolean    allocated = !! g_object_get_qdata (G_OBJECT (sci),
+                                                   RESOURCES_ALLOCATED_QTAG);
     
-    /* clear previous markers */
-    for (i = 0; i < MARKER_COUNT; i++) {
-      scintilla_send_message (sci, SCI_MARKERDELETEALL, G_markers[i].num, 0);
+    if (allocated) {
+      guint i;
+      
+      /* clear previous markers */
+      for (i = 0; i < MARKER_COUNT; i++) {
+        scintilla_send_message (sci, SCI_MARKERDELETEALL, G_markers[i].num, 0);
+      }
     }
     
-    diff_blob_to_doc (blob, doc, diff_hunk_cb, sci);
+    if (contents && (allocated || allocate_resources (sci))) {
+      diff_buf_to_doc (contents, doc, diff_hunk_cb, sci);
+    } else if (! contents && allocated) {
+      /* if we don't have contents, it probably means the document doesn't
+       * match any object known by Git, so next attempts will fail just the
+       * same.  So, drop allocated resources if any (if it used to be a valid
+       * object, e.g. the document was renamed to something unknown to Git) */
+      release_resources (sci);
+    }
   }
 }
 
@@ -777,8 +844,8 @@ do_update_diff_idle (guint    doc_id,
   G_source_id = 0;
   /* make sure the document is still valid and current */
   if (doc && doc->id == doc_id) {
-    get_cached_blob_async (doc->real_path, force, update_diff,
-                           GUINT_TO_POINTER (doc->id));
+    get_cached_blob_contents_async (doc->real_path, force, update_diff,
+                                    GUINT_TO_POINTER (doc->id));
   }
   
   return FALSE;
@@ -850,7 +917,7 @@ on_document_activate (GObject        *obj,
                       GeanyDocument  *doc,
                       gpointer        user_data)
 {
-  clear_cached_blob ();
+  clear_cached_blob_contents ();
   update_diff_push (doc, FALSE);
 }
 
@@ -875,7 +942,7 @@ on_git_repo_changed (GFileMonitor     *monitor,
   GeanyDocument *doc = document_get_current ();
   
   if (doc) {
-    clear_cached_blob ();
+    clear_cached_blob_contents ();
     update_diff_push (doc, GPOINTER_TO_INT (force));
   }
 }
@@ -905,29 +972,17 @@ goto_next_hunk_diff_hunk_cb (const git_diff_delta *delta,
   
   return 0;
 }
-#if LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR < 20
-static int
-goto_next_hunk_diff_hunk_cb_wrapper (const git_diff_delta *delta,
-                                     const git_diff_hunk  *hunk,
-                                     const char           *header,
-                                     size_t                header_len,
-                                     void                 *data)
-{
-  return goto_next_hunk_diff_hunk_cb (delta, hunk, data);
-}
-# define goto_next_hunk_diff_hunk_cb goto_next_hunk_diff_hunk_cb_wrapper
-#endif
 
 static void
 goto_next_hunk_cb (const gchar *path,
-                   git_blob    *blob,
+                   git_buf     *contents,
                    gpointer     udata)
 {
   GotoNextHunkData *data  = udata;
   GeanyDocument    *doc   = document_get_current ();
   
-  if (doc && doc->id == data->doc_id && blob) {
-    diff_blob_to_doc (blob, doc, goto_next_hunk_diff_hunk_cb, data);
+  if (doc && doc->id == data->doc_id && contents) {
+    diff_buf_to_doc (contents, doc, goto_next_hunk_diff_hunk_cb, data);
     
     if (data->next_line >= 0) {
       gint pos = sci_get_position_from_line (doc->editor->sci, data->next_line);
@@ -952,7 +1007,8 @@ on_kb_goto_next_hunk (guint kb)
     data->line      = sci_get_current_line (doc->editor->sci);
     data->next_line = -1;
     
-    get_cached_blob_async (doc->real_path, FALSE, goto_next_hunk_cb, data);
+    get_cached_blob_contents_async (doc->real_path, FALSE, goto_next_hunk_cb,
+                                    data);
   }
 }
 
@@ -1123,7 +1179,7 @@ plugin_init (GeanyData *data)
 {
   GeanyKeyGroup *kb_group;
   
-  G_file_blob = NULL;
+  buf_zero (&G_blob_contents);
   G_source_id = 0;
   G_thread    = NULL;
   G_queue     = NULL;
@@ -1148,6 +1204,8 @@ plugin_init (GeanyData *data)
                          G_CALLBACK (on_document_activate), NULL);
   plugin_signal_connect (geany_plugin, NULL, "document-reload", TRUE,
                          G_CALLBACK (on_document_activate), NULL);
+  plugin_signal_connect (geany_plugin, NULL, "document-save", TRUE,
+                         G_CALLBACK (on_document_activate), NULL);
   plugin_signal_connect (geany_plugin, NULL, "geany-startup-complete", TRUE,
                          G_CALLBACK (on_startup_complete), NULL);
   
@@ -1174,10 +1232,7 @@ plugin_cleanup (void)
     g_async_queue_unref (G_queue);
     G_queue = NULL;
   }
-  if (G_file_blob) {
-    git_blob_free (G_file_blob);
-    G_file_blob = NULL;
-  }
+  clear_cached_blob_contents ();
   
   foreach_document (i) {
     release_resources (documents[i]->editor->sci);


Modified: git-changebar/wscript_configure
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -26,7 +26,7 @@ from build.wafutils import check_cfg_cached
 packages = [
     ('gtk+-2.0', '2.18', 'GTK'),
     ('glib-2.0', '2.16', 'GLIB'),
-    ('libgit2', '0.18', 'LIBGIT2'),
+    ('libgit2', '0.21', 'LIBGIT2'),
 ]
 
 for package_name, package_version, uselib_store in packages:



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Plugins-Commits mailing list