Branch: refs/heads/master Author: LarsDW223 lars_paulsen@web.de Committer: LarsDW223 lars_paulsen@web.de Date: Thu, 26 Oct 2017 18:43:51 UTC Commit: ef0e8ed24cfe99f5521e1fa036ef56a6064336ff https://github.com/geany/geany-plugins/commit/ef0e8ed24cfe99f5521e1fa036ef56...
Log Message: ----------- workbench: Added "open/close all files" feature
This change adds a feature which enables the user to open/close all files of the selected project, directory or folder. The feature can be accessed by using the context menu of the workbench sidebar.
Modified Paths: -------------- workbench/src/popup_menu.c workbench/src/sidebar.c workbench/src/sidebar.h workbench/src/utils.c workbench/src/utils.h
Modified: workbench/src/popup_menu.c 185 lines changed, 185 insertions(+), 0 deletions(-) =================================================================== @@ -31,6 +31,7 @@ #include "dialogs.h" #include "sidebar.h" #include "popup_menu.h" +#include "utils.h"
static struct { @@ -40,11 +41,17 @@ static struct GtkWidget *save_project; GtkWidget *remove_project; GtkWidget *fold_unfold_project; + GtkWidget *project_open_all; + GtkWidget *project_close_all; GtkWidget *add_directory; GtkWidget *remove_directory; GtkWidget *rescan_directory; GtkWidget *directory_settings; GtkWidget *fold_unfold_directory; + GtkWidget *directory_open_all; + GtkWidget *directory_close_all; + GtkWidget *folder_open_all; + GtkWidget *folder_close_all; GtkWidget *expand_all; GtkWidget *collapse_all; GtkWidget *add_wb_bookmark; @@ -70,80 +77,134 @@ void popup_menu_show(POPUP_CONTEXT context, GdkEventButton *event) gtk_widget_set_sensitive (s_popup_menu.add_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_project, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_close_all, TRUE); gtk_widget_set_sensitive (s_popup_menu.add_directory, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.rescan_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.directory_settings, FALSE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_directory, FALSE); + gtk_widget_set_sensitive (s_popup_menu.directory_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.directory_close_all, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_wb_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_prj_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.remove_bookmark, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_close_all, FALSE); break; case POPUP_CONTEXT_DIRECTORY: + gtk_widget_set_sensitive (s_popup_menu.add_project, TRUE); + gtk_widget_set_sensitive (s_popup_menu.remove_project, TRUE); + gtk_widget_set_sensitive (s_popup_menu.fold_unfold_project, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_close_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.add_directory, TRUE); + gtk_widget_set_sensitive (s_popup_menu.remove_directory, TRUE); + gtk_widget_set_sensitive (s_popup_menu.rescan_directory, TRUE); + gtk_widget_set_sensitive (s_popup_menu.directory_settings, TRUE); + gtk_widget_set_sensitive (s_popup_menu.fold_unfold_directory, TRUE); + gtk_widget_set_sensitive (s_popup_menu.directory_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.directory_close_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.add_wb_bookmark, FALSE); + gtk_widget_set_sensitive (s_popup_menu.add_prj_bookmark, FALSE); + gtk_widget_set_sensitive (s_popup_menu.remove_bookmark, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_close_all, FALSE); + break; case POPUP_CONTEXT_FOLDER: gtk_widget_set_sensitive (s_popup_menu.add_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_project, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_close_all, TRUE); gtk_widget_set_sensitive (s_popup_menu.add_directory, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_directory, TRUE); gtk_widget_set_sensitive (s_popup_menu.rescan_directory, TRUE); gtk_widget_set_sensitive (s_popup_menu.directory_settings, TRUE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_directory, TRUE); + gtk_widget_set_sensitive (s_popup_menu.directory_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.directory_close_all, TRUE); gtk_widget_set_sensitive (s_popup_menu.add_wb_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_prj_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.remove_bookmark, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.folder_close_all, TRUE); break; case POPUP_CONTEXT_FILE: gtk_widget_set_sensitive (s_popup_menu.add_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_project, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_close_all, TRUE); gtk_widget_set_sensitive (s_popup_menu.add_directory, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_directory, TRUE); gtk_widget_set_sensitive (s_popup_menu.rescan_directory, TRUE); gtk_widget_set_sensitive (s_popup_menu.directory_settings, TRUE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_directory, TRUE); + gtk_widget_set_sensitive (s_popup_menu.directory_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.directory_close_all, TRUE); gtk_widget_set_sensitive (s_popup_menu.add_wb_bookmark, TRUE); gtk_widget_set_sensitive (s_popup_menu.add_prj_bookmark, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_bookmark, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.folder_close_all, TRUE); break; case POPUP_CONTEXT_BACKGROUND: gtk_widget_set_sensitive (s_popup_menu.add_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_project, FALSE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_project, FALSE); + gtk_widget_set_sensitive (s_popup_menu.project_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.project_close_all, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.remove_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.rescan_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.directory_settings, FALSE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_directory, FALSE); + gtk_widget_set_sensitive (s_popup_menu.directory_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.directory_close_all, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_wb_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_prj_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.remove_bookmark, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_close_all, FALSE); break; case POPUP_CONTEXT_WB_BOOKMARK: gtk_widget_set_sensitive (s_popup_menu.add_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_project, FALSE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_project, FALSE); + gtk_widget_set_sensitive (s_popup_menu.project_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.project_close_all, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.remove_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.rescan_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.directory_settings, FALSE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_directory, FALSE); + gtk_widget_set_sensitive (s_popup_menu.directory_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.directory_close_all, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_wb_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_prj_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.remove_bookmark, TRUE); + gtk_widget_set_sensitive (s_popup_menu.folder_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_close_all, FALSE); break; case POPUP_CONTEXT_PRJ_BOOKMARK: gtk_widget_set_sensitive (s_popup_menu.add_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_project, TRUE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_project, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_open_all, TRUE); + gtk_widget_set_sensitive (s_popup_menu.project_close_all, TRUE); gtk_widget_set_sensitive (s_popup_menu.add_directory, TRUE); gtk_widget_set_sensitive (s_popup_menu.remove_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.rescan_directory, FALSE); gtk_widget_set_sensitive (s_popup_menu.directory_settings, FALSE); gtk_widget_set_sensitive (s_popup_menu.fold_unfold_directory, FALSE); + gtk_widget_set_sensitive (s_popup_menu.directory_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.directory_close_all, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_wb_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.add_prj_bookmark, FALSE); gtk_widget_set_sensitive (s_popup_menu.remove_bookmark, TRUE); + gtk_widget_set_sensitive (s_popup_menu.folder_open_all, FALSE); + gtk_widget_set_sensitive (s_popup_menu.folder_close_all, FALSE); break; } gtk_menu_popup(GTK_MENU(s_popup_menu.widget), NULL, NULL, NULL, NULL, @@ -236,6 +297,34 @@ static void popup_menu_on_fold_unfold_project(G_GNUC_UNUSED GtkMenuItem *menuite }
+/* Handle popup menu item "Open all files" (project) */ +static void popup_menu_on_project_open_all (G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data) +{ + GPtrArray *list; + + list = sidebar_get_selected_project_filelist(); + if (list != NULL) + { + open_all_files_in_list(list); + g_ptr_array_free(list, TRUE); + } +} + + +/* Handle popup menu item "Close all files" (project) */ +static void popup_menu_on_project_close_all (G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data) +{ + GPtrArray *list; + + list = sidebar_get_selected_project_filelist(); + if (list != NULL) + { + close_all_files_in_list(list); + g_ptr_array_free(list, TRUE); + } +} + + /* Handle popup menu item "Add directory" */ static void popup_menu_on_add_directory(G_GNUC_UNUSED GtkMenuItem * menuitem, G_GNUC_UNUSED gpointer user_data) { @@ -313,6 +402,34 @@ static void popup_menu_on_fold_unfold_directory(G_GNUC_UNUSED GtkMenuItem *menui }
+/* Handle popup menu item "Open all files" (directory) */ +static void popup_menu_on_directory_open_all (G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data) +{ + GPtrArray *list; + + list = sidebar_get_selected_directory_filelist(); + if (list != NULL) + { + open_all_files_in_list(list); + g_ptr_array_free(list, TRUE); + } +} + + +/* Handle popup menu item "Close all files" (directory) */ +static void popup_menu_on_directory_close_all (G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data) +{ + GPtrArray *list; + + list = sidebar_get_selected_directory_filelist(); + if (list != NULL) + { + close_all_files_in_list(list); + g_ptr_array_free(list, TRUE); + } +} + + /* Handle popup menu item "Add to workbench bookmarks" */ static void popup_menu_on_add_to_workbench_bookmarks(G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data) { @@ -361,6 +478,34 @@ static void popup_menu_on_remove_from_bookmarks(G_GNUC_UNUSED GtkMenuItem *menui }
+/* Handle popup menu item "Open all files" (folder) */ +static void popup_menu_on_folder_open_all (G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data) +{ + GPtrArray *list; + + list = sidebar_get_selected_folder_filelist(); + if (list != NULL) + { + open_all_files_in_list(list); + g_ptr_array_free(list, TRUE); + } +} + + +/* Handle popup menu item "Close all files" (folder) */ +static void popup_menu_on_folder_close_all (G_GNUC_UNUSED GtkMenuItem *menuitem, G_GNUC_UNUSED gpointer user_data) +{ + GPtrArray *list; + + list = sidebar_get_selected_folder_filelist(); + if (list != NULL) + { + close_all_files_in_list(list); + g_ptr_array_free(list, TRUE); + } +} + + /** Setup/Initialize the popup menu. * **/ @@ -394,6 +539,18 @@ void popup_menu_init(void) g_signal_connect(item, "activate", G_CALLBACK(popup_menu_on_fold_unfold_project), NULL); s_popup_menu.fold_unfold_project = item;
+ item = gtk_menu_item_new_with_mnemonic(_("_Open all files in project")); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item); + g_signal_connect(item, "activate", G_CALLBACK(popup_menu_on_project_open_all), NULL); + s_popup_menu.project_open_all = item; + + item = gtk_menu_item_new_with_mnemonic(_("_Close all files in project")); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item); + g_signal_connect(item, "activate", G_CALLBACK(popup_menu_on_project_close_all), NULL); + s_popup_menu.project_close_all = item; + item = gtk_separator_menu_item_new(); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item); @@ -428,6 +585,34 @@ void popup_menu_init(void) g_signal_connect(item, "activate", G_CALLBACK(popup_menu_on_fold_unfold_directory), NULL); s_popup_menu.fold_unfold_directory = item;
+ item = gtk_menu_item_new_with_mnemonic(_("_Open all files in directory")); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item); + g_signal_connect(item, "activate", G_CALLBACK(popup_menu_on_directory_open_all), NULL); + s_popup_menu.directory_open_all = item; + + item = gtk_menu_item_new_with_mnemonic(_("_Close all files in directory")); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item); + g_signal_connect(item, "activate", G_CALLBACK(popup_menu_on_directory_close_all), NULL); + s_popup_menu.directory_close_all = item; + + item = gtk_separator_menu_item_new(); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item); + + item = gtk_menu_item_new_with_mnemonic(_("_Open all files in folder")); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item); + g_signal_connect(item, "activate", G_CALLBACK(popup_menu_on_folder_open_all), NULL); + s_popup_menu.folder_open_all = item; + + item = gtk_menu_item_new_with_mnemonic(_("_Close all files in folder")); + gtk_widget_show(item); + gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item); + g_signal_connect(item, "activate", G_CALLBACK(popup_menu_on_folder_close_all), NULL); + s_popup_menu.folder_close_all = item; + item = gtk_separator_menu_item_new(); gtk_widget_show(item); gtk_container_add(GTK_CONTAINER(s_popup_menu.widget), item);
Modified: workbench/src/sidebar.c 117 lines changed, 117 insertions(+), 0 deletions(-) =================================================================== @@ -831,6 +831,36 @@ WB_PROJECT *sidebar_file_view_get_selected_project(GtkTreePath **path) }
+/* Search upwards from the selected row and get the iter of the + closest parent with DATA_ID id. */ +static gboolean sidebar_file_view_get_selected_parent_iter(GtkTreeIter *iter, guint id) +{ + gboolean has_parent; + guint dataid; + GtkTreeSelection *treesel; + GtkTreeModel *model; + GtkTreeIter current, parent; + + treesel = gtk_tree_view_get_selection(GTK_TREE_VIEW(sidebar.file_view)); + if (gtk_tree_selection_get_selected(treesel, &model, ¤t)) + { + do + { + gtk_tree_model_get(model, ¤t, FILEVIEW_COLUMN_DATA_ID, &dataid, -1); + if (dataid == id) + { + *iter = current; + return TRUE; + } + has_parent = gtk_tree_model_iter_parent(model, + &parent, ¤t); + current = parent; + }while (has_parent); + } + return FALSE; +} + + /** Get the selected project directory and the path to it. * * Get the selected project directory and return a pointer to it. Also if @a path is not NULL then @@ -948,6 +978,93 @@ gboolean sidebar_file_view_get_selected_context(SIDEBAR_CONTEXT *context) return FALSE; }
+/* Collect all filenames recursively starting from iter and add them to list */ +static void sidebar_get_filelist_for_iter(GPtrArray *list, GtkTreeIter iter) +{ + GtkTreeModel *model; + GtkTreeIter childs; + gboolean has_next; + guint dataid; + char *filename; + + model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view)); + do + { + gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_DATA_ID, &dataid, -1); + switch (dataid) + { + case DATA_ID_FILE: + gtk_tree_model_get(model, &iter, FILEVIEW_COLUMN_ASSIGNED_DATA_POINTER, &filename, -1); + g_ptr_array_add(list, g_strdup(filename)); + break; + case DATA_ID_DIRECTORY: + case DATA_ID_FOLDER: + if (gtk_tree_model_iter_children(model, &childs, &iter) == TRUE) + { + sidebar_get_filelist_for_iter(list, childs); + } + break; + } + has_next = gtk_tree_model_iter_next(model, &iter); + } + while (has_next); +} + + +/* Get the lkist of files belonging to the current selection for + id (id = project, directory, folder) */ +static GPtrArray *sidebar_get_selected_filelist (guint id) +{ + GtkTreeModel *model; + GPtrArray *list; + GtkTreeIter iter, childs; + + if (sidebar_file_view_get_selected_parent_iter(&iter, id)) + { + list = g_ptr_array_new_full(1, g_free); + model = gtk_tree_view_get_model(GTK_TREE_VIEW(sidebar.file_view)); + if (gtk_tree_model_iter_children(model, &childs, &iter) == TRUE) + { + sidebar_get_filelist_for_iter(list, childs); + } + return list; + } + + return NULL; +} + + +/** Get the list of files corresponding to the selected project. + * + * @return GPtrArray containing file names or NULL. + * + **/ +GPtrArray *sidebar_get_selected_project_filelist (void) +{ + return sidebar_get_selected_filelist(DATA_ID_PROJECT); +} + + +/** Get the list of files corresponding to the selected directory. + * + * @return GPtrArray containing file names or NULL. + * + **/ +GPtrArray *sidebar_get_selected_directory_filelist (void) +{ + return sidebar_get_selected_filelist(DATA_ID_DIRECTORY); +} + + +/** Get the list of files corresponding to the selected folder. + * + * @return GPtrArray containing file names or NULL. + * + **/ +GPtrArray *sidebar_get_selected_folder_filelist (void) +{ + return sidebar_get_selected_filelist(DATA_ID_FOLDER); +}
/** Setup the sidebar. *
Modified: workbench/src/sidebar.h 4 lines changed, 4 insertions(+), 0 deletions(-) =================================================================== @@ -72,4 +72,8 @@ void sidebar_toggle_selected_project_dir_expansion (void); WB_PROJECT *sidebar_file_view_get_selected_project(GtkTreePath **path); gboolean sidebar_file_view_get_selected_context(SIDEBAR_CONTEXT *context);
+GPtrArray *sidebar_get_selected_project_filelist (void); +GPtrArray *sidebar_get_selected_directory_filelist (void); +GPtrArray *sidebar_get_selected_folder_filelist (void); + #endif
Modified: workbench/src/utils.c 43 lines changed, 43 insertions(+), 0 deletions(-) =================================================================== @@ -26,6 +26,7 @@ #include <glib.h> #include <glib/gstdio.h> #include <geanyplugin.h> +#include "wb_globals.h" #include "utils.h"
/** Get the relative path. @@ -251,3 +252,45 @@ gchar *get_any_relative_path (const gchar *base, const gchar *target)
return result; } + + +/** Open all files in list. + * + * @param list GPtrArray containing file names. + * + **/ +void open_all_files_in_list(GPtrArray *list) +{ + GeanyData* geany_data = wb_globals.geany_plugin->geany_data; + gboolean is_open; + guint index, doc; + + for (index = 0 ; index < list->len ; index++) + { + document_open_file(list->pdata[index], FALSE, NULL, NULL); + } +} + + +/** Close all files in list. + * + * @param list GPtrArray containing file names. + * + **/ +void close_all_files_in_list(GPtrArray *list) +{ + GeanyData* geany_data = wb_globals.geany_plugin->geany_data; + guint index, doc; + + for (index = 0 ; index < list->len ; index++) + { + foreach_document(doc) + { + if (g_strcmp0(list->pdata[index], documents[doc]->file_name) == 0) + { + document_close(documents[doc]); + break; + } + } + } +}
Modified: workbench/src/utils.h 2 lines changed, 2 insertions(+), 0 deletions(-) =================================================================== @@ -26,5 +26,7 @@ gboolean patterns_match(GSList *patterns, const gchar *str); GSList *get_precompiled_patterns(gchar **patterns); gchar *get_combined_path(const gchar *base, const gchar *relative); gchar *get_any_relative_path (const gchar *base, const gchar *target); +void open_all_files_in_list(GPtrArray *list); +void close_all_files_in_list(GPtrArray *list);
#endif
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).