[Geany] VCdiff plugin
Yura Siamashka
yurand2 at xxxxx
Tue Nov 6 23:30:36 UTC 2007
Hi
> In future I think there should just be one plugin, say VCdiff for all
> common version control systems. The behaviour should be very similar,
> just with different command names, which could be stored in an array.
> Then the user just sets an option for the VC they want (or perhaps it
> could even be autodetected by looking for CVS/.svn/.hg/...
> subdirectories).
>
I like the idea. Here is the first version of this plugin. It supports SVN, CVS, GIT.
Comments are welcomed.
Best regards,
Yura Siamashka
-------------- next part --------------
Index: plugins/Makefile.am
===================================================================
--- plugins/Makefile.am (revision 2014)
+++ plugins/Makefile.am (working copy)
@@ -11,6 +11,7 @@
htmlchars_la_LDFLAGS = -module -avoid-version
export_la_LDFLAGS = -module -avoid-version
svndiff_la_LDFLAGS = -module -avoid-version
+vcdiff_la_LDFLAGS = -module -avoid-version
filebrowser_la_LDFLAGS = -module -avoid-version
if PLUGINS
@@ -21,6 +22,7 @@
htmlchars.la \
export.la \
svndiff.la \
+ vcdiff.la \
filebrowser.la
# Plugins not to be installed
@@ -32,6 +34,7 @@
htmlchars_la_SOURCES = htmlchars.c
export_la_SOURCES = export.c
svndiff_la_SOURCES = svndiff.c
+vcdiff_la_SOURCES = vcdiff.c
filebrowser_la_SOURCES = filebrowser.c
demoplugin_la_LIBADD = $(GTK_LIBS)
@@ -39,6 +42,7 @@
htmlchars_la_LIBADD = $(GTK_LIBS)
export_la_LIBADD = $(GTK_LIBS)
svndiff_la_LIBADD = $(GTK_LIBS)
+vcdiff_la_LIBADD = $(GTK_LIBS)
filebrowser_la_LIBADD = $(GTK_LIBS)
endif # PLUGINS
Index: plugins/vcdiff.c
===================================================================
--- plugins/vcdiff.c (revision 0)
+++ plugins/vcdiff.c (revision 0)
@@ -0,0 +1,549 @@
+/*
+ * vcdiff.c - this file is part of Geany, a fast and lightweight IDE
+ *
+ * Copyright 2007 Frank Lanitz <frank(at)frank(dot)uvena(dot)de>
+ * Copyright 2007 Enrico Tr?ger <enrico.troeger at uvena.de>
+ * Copyright 2007 Nick Treleaven <nick.treleaven at btinternet.com>
+ * Copyright 2007 Yura Siamashka <yurand2 at gmail.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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/* VCdiff plugin */
+/* This small plugin uses svn/git to generate a diff against the current
+ * version inside vc.*/
+
+#include "geany.h"
+#include "support.h"
+#include "plugindata.h"
+#include "document.h"
+#include "filetypes.h"
+#include "utils.h"
+#include "project.h"
+#include "pluginmacros.h"
+
+PluginFields *plugin_fields;
+GeanyData *geany_data;
+
+
+VERSION_CHECK(27)
+
+PLUGIN_INFO(_("VCdiff"), _("Plugin to create a patch of a file against vc"), VERSION)
+
+struct VC_RECORD
+{
+ void** commands;
+ void** envs;
+ gboolean (*predicate)(const gchar* filename);
+};
+
+#define VC_COMMAND_DIFF_FILE 0
+#define VC_COMMAND_DIFF_DIR 1
+#define VC_COMMAND_DIFF_PROJECT 2
+
+static const gchar DIRNAME[] = "*DIRNAME*";
+static const gchar FILENAME[] = "*FILENAME*";
+static const gchar BASE_FILENAME[] = "*BASE_FILENAME*";
+
+static const gchar* SVN_CMD_DIFF_FILE[] = {"svn", "diff", "--non-interactive", FILENAME, NULL};
+static const gchar* SVN_CMD_DIFF_DIR[] = {"svn", "diff", "--non-interactive", DIRNAME, NULL};
+static const gchar* SVN_CMD_DIFF_PROJECT[] = {"svn", "diff", "--non-interactive", DIRNAME, NULL};
+
+static const gchar* SVN_ENV_DIFF_FILE[] = {NULL};
+static const gchar* SVN_ENV_DIFF_DIR[] = {NULL};
+static const gchar* SVN_ENV_DIFF_PROJECT[] = {NULL};
+
+
+static void* SVN_COMMANDS[] = { SVN_CMD_DIFF_FILE, SVN_CMD_DIFF_DIR, SVN_CMD_DIFF_PROJECT };
+static void* SVN_ENV[] = { SVN_ENV_DIFF_FILE, SVN_ENV_DIFF_DIR, SVN_ENV_DIFF_PROJECT };
+
+static gboolean is_svn(const gchar* filename)
+{
+ gboolean ret;
+ gchar *base;
+ gchar *svndir;
+ gchar *svnpath;
+
+ svnpath = g_find_program_in_path("svn");
+ if (!svnpath)
+ return FALSE;
+ g_free(svnpath);
+
+ if (g_file_test(filename, G_FILE_TEST_IS_DIR))
+ base = g_strdup(filename);
+ else
+ base = g_path_get_dirname(filename);
+ svndir = g_build_path("/", base, ".svn", NULL);
+
+ ret = g_file_test(svndir, G_FILE_TEST_IS_DIR);
+
+ g_free(base);
+ g_free(svndir);
+
+ return ret;
+}
+
+static const gchar* CVS_CMD_DIFF_FILE[] = {"cvs", "diff", "-u", BASE_FILENAME, NULL};
+static const gchar* CVS_CMD_DIFF_DIR[] = {"cvs", "diff", "-u",NULL};
+static const gchar* CVS_CMD_DIFF_PROJECT[] = {"cvs", "diff", "-u", NULL};
+
+static const gchar* CVS_ENV_DIFF_FILE[] = {NULL};
+static const gchar* CVS_ENV_DIFF_DIR[] = {NULL};
+static const gchar* CVS_ENV_DIFF_PROJECT[] = {NULL};
+
+
+static void* CVS_COMMANDS[] = { CVS_CMD_DIFF_FILE, CVS_CMD_DIFF_DIR, CVS_CMD_DIFF_PROJECT };
+static void* CVS_ENV[] = { CVS_ENV_DIFF_FILE, CVS_ENV_DIFF_DIR, CVS_ENV_DIFF_PROJECT };
+
+static gboolean is_cvs(const gchar* filename)
+{
+ gboolean ret;
+ gchar *base;
+ gchar *cvsdir;
+ gchar *cvspath;
+
+ cvspath = g_find_program_in_path("cvs");
+ if (!cvspath)
+ return FALSE;
+ g_free(cvspath);
+
+ if (g_file_test(filename, G_FILE_TEST_IS_DIR))
+ base = g_strdup(filename);
+ else
+ base = g_path_get_dirname(filename);
+ cvsdir = g_build_path("/", base, "CVS", NULL);
+
+ ret = g_file_test(cvsdir, G_FILE_TEST_IS_DIR);
+
+ g_free(base);
+ g_free(cvsdir);
+
+ return ret;
+}
+
+
+static const gchar* GIT_CMD_DIFF_FILE[] = {"git", "diff", BASE_FILENAME, NULL};
+static const gchar* GIT_CMD_DIFF_DIR[] = {"git", "diff", NULL};
+static const gchar* GIT_CMD_DIFF_PROJECT[] = {"git", "diff", NULL};
+
+static const gchar* GIT_ENV_DIFF_FILE[] = {"PAGER=cat", NULL};
+static const gchar* GIT_ENV_DIFF_DIR[] = {"PAGER=cat", NULL};
+static const gchar* GIT_ENV_DIFF_PROJECT[] = {"PAGER=cat", NULL};
+
+
+static void* GIT_COMMANDS[] = { GIT_CMD_DIFF_FILE, GIT_CMD_DIFF_DIR, GIT_CMD_DIFF_PROJECT };
+static void* GIT_ENV[] = { GIT_ENV_DIFF_FILE, GIT_ENV_DIFF_DIR, GIT_ENV_DIFF_PROJECT };
+
+static gboolean is_git(const gchar* filename)
+{
+ gboolean ret = FALSE;
+ gchar *base;
+ gchar *gitdir;
+ gchar *gitpath;
+ gchar *tmp;
+ gchar *base_prev = g_strdup(":");
+
+ gitpath = g_find_program_in_path("git");
+ if (!gitpath)
+ return FALSE;
+ g_free(gitpath);
+
+ if (g_file_test(filename, G_FILE_TEST_IS_DIR))
+ base = g_strdup(filename);
+ else
+ base = g_path_get_dirname(filename);
+
+ while(strcmp(base, base_prev) != 0)
+ {
+ gitdir = g_build_path("/", base, ".git", NULL);
+ ret = g_file_test(gitdir, G_FILE_TEST_IS_DIR);
+ g_free(gitdir);
+ if (ret)
+ break;
+ g_free(base_prev);
+ base_prev = base;
+ base = g_path_get_dirname(base);
+ }
+
+ g_free(base);
+ g_free(base_prev);
+ return ret;
+}
+
+static struct VC_RECORD VC[] = {
+ {SVN_COMMANDS, SVN_ENV, is_svn},
+ {CVS_COMMANDS, CVS_ENV, is_cvs},
+ {GIT_COMMANDS, GIT_ENV, is_git},
+ {NULL, NULL, NULL}
+};
+
+static void* find_cmd_env(gint cmd_type, gboolean cmd, const gchar* filename)
+{
+ int i = 0;
+ while(1)
+ {
+ if (!VC[i].predicate)
+ return NULL;
+ if (VC[i].predicate(filename))
+ if (cmd)
+ return VC[i].commands[cmd_type];
+ else
+ return VC[i].envs[cmd_type];
+ i++;
+ }
+}
+
+static void* get_cmd_env(gint cmd_type, gboolean cmd, const gchar* filename)
+{
+ int i;
+ gint len = 0;
+ gchar** argv;
+ gchar** ret;
+ gchar* dir;
+ gchar* base_filename;
+
+ argv = find_cmd_env(cmd_type, cmd, filename);
+ if (!argv)
+ return NULL;
+
+ if (g_file_test(filename, G_FILE_TEST_IS_DIR))
+ {
+ dir = g_strdup(filename);
+ }
+ else
+ {
+ dir = g_path_get_dirname(filename);
+ }
+ base_filename = g_path_get_basename(filename);
+
+ while(1)
+ {
+ if (argv[len] == NULL)
+ break;
+ len++;
+ }
+ ret = g_malloc(sizeof(gchar*) * (len+1));
+ memset(ret, 0, sizeof(gchar*) * (len+1));
+ for (i = 0; i < len; i++)
+ {
+ if (argv[i] == DIRNAME)
+ {
+ ret[i] = g_strdup(dir);
+ }
+ else if (argv[i] == FILENAME)
+ {
+ ret[i] = g_strdup(filename);
+ }
+ else if (argv[i] == BASE_FILENAME)
+ {
+ ret[i] = g_strdup(base_filename);
+ }
+ else
+ ret[i] = g_strdup(argv[i]);
+ }
+ g_free(dir);
+ g_free(base_filename);
+ return ret;
+}
+
+static int find_by_filename(const gchar* filename)
+{
+ gint i;
+ for (i = 0; i < doc_array->len; i++)
+ {
+ if ( DOC_IDX_VALID(i) && doc_list[i].file_name &&
+ strcmp(doc_list[i].file_name, filename) == 0)
+ return i;
+ }
+ return -1;
+}
+
+/* name_prefix should be in UTF-8, and can have a path. */
+static void show_output(const gchar *std_output, const gchar *name_prefix,
+ const gchar *force_encoding)
+{
+ gchar *text, *detect_enc = NULL;
+ gint idx, page;
+ GtkNotebook *book;
+ gchar *filename;
+
+ filename = g_path_get_basename(name_prefix);
+ setptr(filename, g_strconcat(filename, ".vc.diff", NULL));
+
+ // need to convert input text from the encoding of the original file into
+ // UTF-8 because internally Geany always needs UTF-8
+ if (force_encoding)
+ {
+ text = geany_data->encoding->convert_to_utf8_from_charset(
+ std_output, -1, force_encoding, TRUE);
+ }
+ else
+ {
+ text = geany_data->encoding->convert_to_utf8(std_output, -1, &detect_enc);
+ }
+ if (text)
+ {
+ idx = find_by_filename(filename);
+ if ( idx == -1)
+ {
+ idx = geany_data->document->new_file(filename,
+ geany_data->filetypes[GEANY_FILETYPES_DIFF], text);
+ }
+ else
+ {
+ geany_data->sci->set_text(doc_list[idx].sci, text);
+ book = GTK_NOTEBOOK(app->notebook);
+ page = gtk_notebook_page_num(book, GTK_WIDGET(doc_list[idx].sci));
+ gtk_notebook_set_current_page(book, page);
+ doc_list[idx].changed = FALSE;
+ documents->set_text_changed(idx);
+ }
+
+ geany_data->document->set_encoding(idx,
+ force_encoding ? force_encoding : detect_enc);
+ }
+ else
+ {
+ ui->set_statusbar(FALSE, _("Could not parse the output of the diff"));
+ }
+ g_free(text);
+ g_free(detect_enc);
+ g_free(filename);
+}
+
+static gchar *make_diff(const gchar *filename, gint cmd)
+{
+ gchar *std_output = NULL;
+ gchar *std_error = NULL;
+ gint exit_code;
+ gchar *text = NULL;
+ gchar *dir;
+ gchar **env = get_cmd_env(cmd, FALSE, filename);
+ gchar **argv = get_cmd_env(cmd, TRUE, filename);
+
+ if (!argv)
+ {
+ if (env)
+ g_strfreev(env);
+ return NULL;
+ }
+
+ if (g_file_test(filename, G_FILE_TEST_IS_DIR))
+ {
+ dir = g_strdup(filename);
+ }
+ else
+ {
+ dir = g_path_get_dirname(filename);
+ }
+
+ if (g_spawn_sync(dir, argv, env, G_SPAWN_SEARCH_PATH, NULL, NULL, &std_output, &std_error, &exit_code, NULL))
+ {
+ // CVS dump stuff to stderr when diff nested dirs
+ if (strcmp(argv[0], "cvs") != 0 && NZV(std_error))
+ {
+ dialogs->show_msgbox(1,
+ _("%s exited with an error: \n%s."), argv[0], g_strstrip(std_error));
+ }
+ else if (NZV(std_output))
+ {
+ text = std_output;
+ }
+ else
+ {
+ ui->set_statusbar(FALSE, _("No changes were made."));
+ }
+ }
+ else
+ {
+ ui->set_statusbar(FALSE,
+ _("Something went really wrong."));
+ }
+ g_free(dir);
+ g_free(std_error);
+ g_strfreev(env);
+ g_strfreev(argv);
+ return text;
+}
+
+
+/* Make a diff from the current directory */
+static void vcdirectory_activated(GtkMenuItem *menuitem, gpointer gdata)
+{
+ gint idx;
+ gchar *base_name = NULL;
+ gchar *locale_filename = NULL;
+ gchar *text;
+
+ idx = documents->get_cur_idx();
+
+ g_return_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL);
+
+ if (doc_list[idx].changed)
+ {
+ documents->save_file(idx, FALSE);
+ }
+
+ locale_filename = utils->get_locale_from_utf8(doc_list[idx].file_name);
+ base_name = g_path_get_dirname(locale_filename);
+
+ text = make_diff(base_name, VC_COMMAND_DIFF_DIR);
+ if (text)
+ {
+ show_output(text, base_name, NULL);
+ g_free(text);
+ }
+
+ g_free(base_name);
+ g_free(locale_filename);
+}
+
+
+/* Callback if menu item for the current project was activated */
+static void vcproject_activated(GtkMenuItem *menuitem, gpointer gdata)
+{
+ gint idx;
+ gchar *locale_filename = NULL;
+ gchar *text;
+
+ idx = documents->get_cur_idx();
+
+ g_return_if_fail(project != NULL && NZV(project->base_path));
+
+ if (DOC_IDX_VALID(idx) && doc_list[idx].changed && doc_list[idx].file_name != NULL)
+ {
+ documents->save_file(idx, FALSE);
+ }
+
+ locale_filename = utils->get_locale_from_utf8(project->base_path);
+ text = make_diff(locale_filename, VC_COMMAND_DIFF_PROJECT);
+ if (text)
+ {
+ show_output(text, project->name, NULL);
+ g_free(text);
+ }
+ g_free(locale_filename);
+}
+
+
+/* Callback if menu item for a single file was activated */
+static void vcfile_activated(GtkMenuItem *menuitem, gpointer gdata)
+{
+ gint idx;
+ gchar *locale_filename, *text;
+
+ idx = documents->get_cur_idx();
+
+ g_return_if_fail(DOC_IDX_VALID(idx) && doc_list[idx].file_name != NULL);
+
+ if (doc_list[idx].changed)
+ {
+ documents->save_file(idx, FALSE);
+ }
+
+ locale_filename = utils->get_locale_from_utf8(doc_list[idx].file_name);
+
+ text = make_diff(locale_filename, VC_COMMAND_DIFF_FILE);
+ if (text)
+ {
+ show_output(text, doc_list[idx].file_name, doc_list[idx].encoding);
+ g_free(text);
+ }
+ g_free(locale_filename);
+}
+
+
+static GtkWidget *menu_vcdiff_file = NULL;
+static GtkWidget *menu_vcdiff_dir = NULL;
+static GtkWidget *menu_vcdiff_project = NULL;
+
+static void update_menu_items()
+{
+ document *doc;
+ gboolean have_file;
+ gboolean have_vc = FALSE;
+
+ doc = documents->get_current();
+ have_file = doc && doc->file_name && g_path_is_absolute(doc->file_name);
+ if (find_cmd_env(VC_COMMAND_DIFF_FILE, TRUE, doc->file_name))
+ have_vc = TRUE;
+
+ gtk_widget_set_sensitive(menu_vcdiff_file, have_vc);
+ gtk_widget_set_sensitive(menu_vcdiff_dir, have_vc);
+ gtk_widget_set_sensitive(menu_vcdiff_project,
+ project != NULL && NZV(project->base_path));
+}
+
+
+/* Called by Geany to initialize the plugin */
+void init(GeanyData *data)
+{
+ GtkWidget *menu_vcdiff = NULL;
+ GtkWidget *menu_vcdiff_menu = NULL;
+ GtkTooltips *tooltips = NULL;
+
+ tooltips = gtk_tooltips_new();
+
+ plugin_fields->flags = PLUGIN_IS_DOCUMENT_SENSITIVE;
+
+ menu_vcdiff = gtk_image_menu_item_new_with_mnemonic(_("_VCdiff"));
+ gtk_container_add(GTK_CONTAINER(data->tools_menu), menu_vcdiff);
+
+ g_signal_connect((gpointer) menu_vcdiff, "activate",
+ G_CALLBACK(update_menu_items), NULL);
+
+ menu_vcdiff_menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_vcdiff), menu_vcdiff_menu);
+
+ // Single file
+ menu_vcdiff_file = gtk_menu_item_new_with_mnemonic(_("From Current _File"));
+ gtk_container_add(GTK_CONTAINER (menu_vcdiff_menu), menu_vcdiff_file);
+ gtk_tooltips_set_tip (tooltips, menu_vcdiff_file,
+ _("Make a diff from the current active file"), NULL);
+
+ g_signal_connect((gpointer) menu_vcdiff_file, "activate",
+ G_CALLBACK(vcfile_activated), NULL);
+
+ // Directory
+ menu_vcdiff_dir = gtk_menu_item_new_with_mnemonic(_("From Current _Directory"));
+ gtk_container_add(GTK_CONTAINER (menu_vcdiff_menu), menu_vcdiff_dir);
+ gtk_tooltips_set_tip (tooltips, menu_vcdiff_dir,
+ _("Make a diff from the directory of the current active file"), NULL);
+
+ g_signal_connect((gpointer) menu_vcdiff_dir, "activate",
+ G_CALLBACK(vcdirectory_activated), NULL);
+
+ // Project
+ menu_vcdiff_project = gtk_menu_item_new_with_mnemonic(_("From Current _Project"));
+ gtk_container_add(GTK_CONTAINER (menu_vcdiff_menu), menu_vcdiff_project);
+ gtk_tooltips_set_tip (tooltips, menu_vcdiff_project,
+ _("Make a diff from the current project's base path"), NULL);
+
+ g_signal_connect((gpointer) menu_vcdiff_project, "activate",
+ G_CALLBACK(vcproject_activated), NULL);
+
+ gtk_widget_show_all(menu_vcdiff);
+
+ plugin_fields->menu_item = menu_vcdiff;
+ plugin_fields->flags = PLUGIN_IS_DOCUMENT_SENSITIVE;
+}
+
+
+/* Called by Geany before unloading the plugin. */
+void cleanup()
+{
+ // remove the menu item added in init()
+ gtk_widget_destroy(plugin_fields->menu_item);
+}
More information about the Users
mailing list