[anjuta/git-shell] git: Implement automatic ref handling



commit 4441b02c70a5feb1240cdf906aa4e899f4a5a3e9
Author: James Liggett <jrliggett cox net>
Date:   Sun Sep 19 16:31:45 2010 -0700

    git: Implement automatic ref handling
    
    WARNING: This is completely untested. If it works, it should form the basis of
    automatic log refreshing. It should be able to tell us if any kind of ref (branch,
    tag, remote, or repository head commit) changes, and the log viewer will,
    when it is put together, respond to this command to keep its view updated.

 plugins/git/git-ref-command.c |  198 +++++++++++++++++++++++++++++++++++++++++
 plugins/git/git-ref-command.h |    1 +
 plugins/git/plugin.c          |    9 ++
 plugins/git/plugin.h          |    2 +
 4 files changed, 210 insertions(+), 0 deletions(-)
---
diff --git a/plugins/git/git-ref-command.c b/plugins/git/git-ref-command.c
index 5e8eb31..23c3d67 100644
--- a/plugins/git/git-ref-command.c
+++ b/plugins/git/git-ref-command.c
@@ -34,6 +34,7 @@ struct _GitRefCommandPriv
 	GRegex *tag_ref_regex;
 	GRegex *remote_ref_regex;
 	GHashTable *refs;
+	GHashTable *file_monitors;
 };
 
 G_DEFINE_TYPE (GitRefCommand, git_ref_command, GIT_TYPE_COMMAND);
@@ -190,6 +191,201 @@ git_ref_command_handle_output (GitCommand *git_command, const gchar *output)
 }
 
 static void
+on_file_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file,
+                         GFileMonitorEvent event, AnjutaCommand *command)
+{
+	if (event == G_FILE_MONITOR_EVENT_CREATED ||
+	    event == G_FILE_MONITOR_EVENT_DELETED)
+	{
+		anjuta_command_start (command);
+	}
+}
+
+/* Helper method to add file monitors. The command takes ownership of the 
+ * file */
+static void
+git_ref_command_add_file_monitor (GitRefCommand *self, GFile *file)
+{
+	GFileMonitor *file_monitor;
+
+	file_monitor = g_file_monitor (file, 0, NULL, NULL);
+
+	g_signal_connect (G_OBJECT (file_monitor), "changed",
+	                  G_CALLBACK (on_file_monitor_changed),
+	                  self);
+
+	/* Have the hash table take ownership of both the file and the file
+	 * monitor to keep memory management consistent. */
+	g_hash_table_insert (self->priv->file_monitors, file, file_monitor);
+}
+
+static void
+on_remote_file_monitor_changed (GFileMonitor *monitor, GFile *file, 
+                                GFile *other_file, GFileMonitorEvent event, 
+                                GitRefCommand *self)
+{
+	switch (event)
+	{
+		case G_FILE_MONITOR_EVENT_CREATED:
+			/* A new remote was created */
+			git_ref_command_add_file_monitor (self, g_object_ref (file));
+
+			/* Start the command to reflect changes */
+			anjuta_command_start (ANJUTA_COMMAND (self));
+
+			break;
+		case G_FILE_MONITOR_EVENT_DELETED:
+			/* A remote was deleted--stop monitoring it */
+			g_hash_table_remove (self->priv->file_monitors, file);
+
+			anjuta_command_start (ANJUTA_COMMAND (self));
+
+			break;
+		default:
+			break;
+			
+	};
+}
+
+static gboolean 
+git_ref_command_start_automatic_monitor (AnjutaCommand *command)
+{
+	GitRefCommand *self;
+	gchar *working_directory;
+	gchar *git_head_path;
+	gchar *git_packed_refs_path;
+	gchar *git_branches_path;
+	gchar *git_tags_path;
+	gchar *git_remotes_path;
+	GFile *remotes_file;
+	GFileMonitor *remotes_monitor;
+	GFileEnumerator *remotes_enumerator;
+	GFileInfo *remotes_info;
+	GFile *current_remote_file;
+
+	self = GIT_REF_COMMAND (command);
+
+	g_object_get (self, "working-directory", &working_directory, NULL);
+
+	/* The file monitors tabe keeps track of all the GFiles and GFileMonitors
+	 * that we're tracking. The table takes ownership of the files and the 
+	 * monitors so that cleanup can be done in one step. */
+	self->priv->file_monitors = g_hash_table_new_full (g_file_hash,
+	                                                   (GEqualFunc) g_file_equal,
+	                                                   g_object_unref,
+	                                                   g_object_unref);
+
+	/* Watch for changes to any file that might tell us about changes to 
+	 * branches, tags, remotes, or the active branch */
+	git_head_path = g_strjoin (G_DIR_SEPARATOR_S,
+	                           working_directory,
+	                           ".git",
+	                           "HEAD",
+	                           NULL);
+	git_packed_refs_path = g_strjoin (G_DIR_SEPARATOR_S,
+	                                  working_directory,
+	                                  ".git",
+	                                  "packed-refs",
+	                                  NULL);
+	git_branches_path = g_strjoin (G_DIR_SEPARATOR_S,
+	                               working_directory,
+	                               ".git",
+	                               "refs",
+	                               "heads",
+	                               NULL);
+	git_tags_path = g_strjoin (G_DIR_SEPARATOR_S,
+	                           working_directory,
+	                           ".git",
+	                           "refs",
+	                           "tags",
+	                           NULL);
+	git_remotes_path = g_strjoin (G_DIR_SEPARATOR_S,
+	                              working_directory,
+	                              ".git",
+	                              "refs",
+	                              "remotes",
+	                              NULL);
+
+	git_ref_command_add_file_monitor (self, 
+	                                  g_file_new_for_path (git_head_path));
+
+	git_ref_command_add_file_monitor (self, 
+	                                  g_file_new_for_path (git_packed_refs_path));
+
+	git_ref_command_add_file_monitor (self,
+	                                  g_file_new_for_path (git_branches_path));
+
+	git_ref_command_add_file_monitor (self,
+	                                  g_file_new_for_path (git_tags_path));
+
+	/* Now handle remotes. Git stores the head of each remote branch in its own
+	 * folder under .git/refs/remotes. We need to monitor for changes to each
+	 * remote's folder. */
+	remotes_file = g_file_new_for_path (git_remotes_path);
+
+	/* First, monitor the remotes folder for creation/deletion of remotes */
+	remotes_monitor = g_file_monitor (remotes_file, 0, NULL, NULL);
+
+	g_signal_connect (G_OBJECT (remotes_monitor), "changed",
+	                  G_CALLBACK (on_remote_file_monitor_changed),
+	                  self);
+
+	/* Add the monitor to the hash table to simplify cleanup */
+	g_hash_table_insert (self->priv->file_monitors, remotes_file, 
+						 remotes_monitor);
+	
+	remotes_enumerator = g_file_enumerate_children (remotes_file,
+	                                                G_FILE_ATTRIBUTE_STANDARD_NAME ","
+	                                                G_FILE_ATTRIBUTE_STANDARD_TYPE,
+	                                                0, NULL, NULL);
+
+	remotes_info = g_file_enumerator_next_file (remotes_enumerator, NULL, NULL);
+
+	while (remotes_info)
+	{
+		/* Monitor each remote folder for changes */
+		if (g_file_info_get_attribute_uint32 (remotes_info, 
+		                                      G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY)
+		{
+			current_remote_file = g_file_get_child (remotes_file,
+			                                        g_file_info_get_name (remotes_info));
+
+			git_ref_command_add_file_monitor (self, current_remote_file);
+		}
+
+		g_object_unref (remotes_info);
+		
+		remotes_info = g_file_enumerator_next_file (remotes_enumerator, NULL, 
+		                                            NULL);
+	}
+
+	g_free (working_directory);
+	g_free (git_head_path);
+	g_free (git_packed_refs_path);
+	g_free (git_branches_path);
+	g_free (git_tags_path);
+	g_free (git_remotes_path);
+	g_object_unref (remotes_file);
+	g_object_unref (remotes_enumerator);
+	
+	return TRUE;
+}
+
+static void
+git_ref_command_stop_automatic_monitor (AnjutaCommand *command)
+{
+	GitRefCommand *self;
+
+	self = GIT_REF_COMMAND (command);
+
+	if (self->priv->file_monitors)
+	{
+		g_hash_table_destroy (self->priv->file_monitors);
+		self->priv->file_monitors = NULL;
+	}
+}
+
+static void
 git_ref_command_class_init (GitRefCommandClass *klass)
 {
 	GObjectClass* object_class = G_OBJECT_CLASS (klass);
@@ -199,6 +395,8 @@ git_ref_command_class_init (GitRefCommandClass *klass)
 	object_class->finalize = git_ref_command_finalize;
 	parent_class->output_handler = git_ref_command_handle_output;
 	command_class->run = git_ref_command_run;
+	command_class->start_automatic_monitor = git_ref_command_start_automatic_monitor;
+	command_class->stop_automatic_monitor = git_ref_command_stop_automatic_monitor;
 }
 
 
diff --git a/plugins/git/git-ref-command.h b/plugins/git/git-ref-command.h
index 3b94ba1..0d34f07 100644
--- a/plugins/git/git-ref-command.h
+++ b/plugins/git/git-ref-command.h
@@ -26,6 +26,7 @@
 #define _GIT_REF_COMMAND_H_
 
 #include <glib-object.h>
+#include <gio/gio.h>
 #include "git-command.h"
 #include "git-ref.h"
 
diff --git a/plugins/git/plugin.c b/plugins/git/plugin.c
index 6c60606..c1545ce 100644
--- a/plugins/git/plugin.c
+++ b/plugins/git/plugin.c
@@ -394,17 +394,22 @@ on_project_root_added (AnjutaPlugin *plugin, const gchar *name,
 	g_object_set (G_OBJECT (git_plugin->stash_list_command),
 	              "working-directory", git_plugin->project_root_directory,
 	              NULL);
+	g_object_set (G_OBJECT (git_plugin->ref_command),
+	              "working-directory", git_plugin->project_root_directory,
+	              NULL);
 
 	anjuta_command_start_automatic_monitor (ANJUTA_COMMAND (git_plugin->local_branch_list_command));
 	anjuta_command_start_automatic_monitor (ANJUTA_COMMAND (git_plugin->commit_status_command));
 	anjuta_command_start_automatic_monitor (ANJUTA_COMMAND (git_plugin->remote_list_command));
 	anjuta_command_start_automatic_monitor (ANJUTA_COMMAND (git_plugin->tag_list_command));
 	anjuta_command_start_automatic_monitor (ANJUTA_COMMAND (git_plugin->stash_list_command));
+	anjuta_command_start_automatic_monitor (ANJUTA_COMMAND (git_plugin->ref_command));
 	anjuta_command_start (ANJUTA_COMMAND (git_plugin->local_branch_list_command));
 	anjuta_command_start (ANJUTA_COMMAND (git_plugin->commit_status_command));
 	anjuta_command_start (ANJUTA_COMMAND (git_plugin->remote_list_command));
 	anjuta_command_start (ANJUTA_COMMAND (git_plugin->tag_list_command));
 	anjuta_command_start (ANJUTA_COMMAND (git_plugin->stash_list_command));
+	anjuta_command_start (ANJUTA_COMMAND (git_plugin->ref_command));
 	
 	gtk_widget_set_sensitive (git_plugin->dock, TRUE);
 	gtk_widget_set_sensitive (git_plugin->command_bar, TRUE);
@@ -597,6 +602,9 @@ git_activate_plugin (AnjutaPlugin *plugin)
 	/* Remote list command */
 	git_plugin->remote_list_command = git_remote_list_command_new (NULL);
 
+	/* Ref list command. used to keep the log up to date */
+	git_plugin->ref_command = git_ref_command_new (NULL);
+
 	/* Always run the not updated commmand after the commmit command. */
 	g_signal_connect (G_OBJECT (git_plugin->commit_status_command), 
 	                  "command-finished",
@@ -696,6 +704,7 @@ git_deactivate_plugin (AnjutaPlugin *plugin)
 	g_object_unref (git_plugin->remote_list_command);
 	g_object_unref (git_plugin->tag_list_command);
 	g_object_unref (git_plugin->stash_list_command);
+	g_object_unref (git_plugin->ref_command);
 	
 	g_free (git_plugin->project_root_directory);
 	g_free (git_plugin->current_editor_filename);
diff --git a/plugins/git/plugin.h b/plugins/git/plugin.h
index 0ca3ba9..13110c2 100644
--- a/plugins/git/plugin.h
+++ b/plugins/git/plugin.h
@@ -40,6 +40,7 @@
 #include "git-remote-list-command.h"
 #include "git-tag-list-command.h"
 #include "git-stash-list-command.h"
+#include "git-ref-command.h"
 
 extern GType git_get_type (GTypeModule *module);
 #define ANJUTA_TYPE_PLUGIN_GIT         (git_get_type (NULL))
@@ -85,6 +86,7 @@ struct _Git
 	GitRemoteListCommand *remote_list_command;
 	GitTagListCommand *tag_list_command;
 	GitStashListCommand *stash_list_command;
+	GitRefCommand *ref_command;
 	
 	IAnjutaMessageView *message_view;
 	AnjutaCommandQueue *command_queue;



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