[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