From 17fb0895466972c88a8fdb4589e09f44854de73d Mon Sep 17 00:00:00 2001 From: James Liggett Date: Sat, 19 Apr 2008 17:32:47 -0700 Subject: [PATCH] Implement GitCommand class --- TODO.tasks | 10 +- plugins/git/Makefile.am | 4 +- plugins/git/git-command.c | 388 ++++++++++++++++++++++++++++++++++++++++++++++ plugins/git/git-command.h | 72 +++++++++ 4 files changed, 468 insertions(+), 6 deletions(-) create mode 100644 plugins/git/git-command.c create mode 100644 plugins/git/git-command.h diff --git a/TODO.tasks b/TODO.tasks index 2532f420..2db93d29 100644 --- a/TODO.tasks +++ b/TODO.tasks @@ -613,11 +613,6 @@ Fix c++/gobject class generator to allow adding members, methods, signals, prope - - Implement GitCommand class - - - Basic log command and UI Implement the GitLog object. Create the basic Anjuta shell widget, showing commits and log messages @@ -737,5 +732,10 @@ Fix c++/gobject class generator to allow adding members, methods, signals, prope Stub plugin Create stub AnjutaPlugin implementation, and empty menu. Steal a git icon from somewhere :-) + + + Implement GitCommand class + + diff --git a/plugins/git/Makefile.am b/plugins/git/Makefile.am index 6151c14d..57f77e62 100644 --- a/plugins/git/Makefile.am +++ b/plugins/git/Makefile.am @@ -39,7 +39,9 @@ plugin_LTLIBRARIES = libanjuta-git.la # Plugin sources libanjuta_git_la_SOURCES = \ plugin.c \ - plugin.h + plugin.h \ + git-command.c \ + git-command.h libanjuta_git_la_LDFLAGS = $(ANJUTA_PLUGIN_LDFLAGS) diff --git a/plugins/git/git-command.c b/plugins/git/git-command.c new file mode 100644 index 00000000..8b44c1d9 --- /dev/null +++ b/plugins/git/git-command.c @@ -0,0 +1,388 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * git-command-test + * Copyright (C) James Liggett 2008 + * + * git-command-test is free software. + * + * You may 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. + * + * git-command-test 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 git-command-test. If not, write to: + * The Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301, USA. + */ + +#include "git-command.h" + +enum +{ + PROP_0, + + PROP_WORKING_DIRECTORY, + PROP_SINGLE_LINE_OUTPUT +}; + +struct _GitCommandPriv +{ + AnjutaLauncher *launcher; + GList *args; + size_t num_args; + gchar *working_directory; + GRegex *error_regex; + GString *error_string; + GQueue *info_queue; + gboolean single_line_output; +}; + +G_DEFINE_TYPE (GitCommand, git_command, ANJUTA_TYPE_SYNC_COMMAND); + +static void +git_command_multi_line_output_arrived (AnjutaLauncher *launcher, + AnjutaLauncherOutputType output_type, + const gchar *chars, GitCommand *self) +{ + switch (output_type) + { + case ANJUTA_LAUNCHER_OUTPUT_STDOUT: + GIT_COMMAND_GET_CLASS (self)->output_handler (self, chars); + break; + case ANJUTA_LAUNCHER_OUTPUT_STDERR: + GIT_COMMAND_GET_CLASS (self)->error_handler (self, chars); + break; + default: + break; + } +} + +/* Split the string up line by line. Works almost like g_strsplit, execpt the + * newlines are preserved. */ +static gchar ** +split_lines (const gchar *string) +{ + GList *string_list; + gchar *string_pos; + const gchar *remainder; + guint n; + gchar **lines; + GList *current_line; + + string_list = NULL; + string_pos = strchr (string, '\n'); + remainder = string; + n = 0; + + if (string_pos) + { + while (string_pos) + { + /* Increment string_pos to preserve the newline. */ + string_pos++; + + string_list = g_list_prepend (string_list, g_strndup (remainder, + (string_pos - remainder))); + n++; + + remainder = string_pos; + string_pos = strchr (remainder, '\n'); + + } + } + else + { + /* If there are no newlines in the string, just return a vector with + * one line in it. */ + string_list = g_list_prepend (string_list, g_strdup (string)); + n++; + } + + lines = g_new (gchar *, n + 1); + lines[n--] = NULL; + + for (current_line = string_list; + current_line; + current_line = g_list_next (current_line)) + { + lines[n--] = current_line->data; + } + + g_list_free (string_list); + + return lines; + +} + +static void +git_command_single_line_output_arrived (AnjutaLauncher *launcher, + AnjutaLauncherOutputType output_type, + const gchar *chars, GitCommand *self) +{ + void (*output_handler) (GitCommand *git_command, const gchar *output); + gchar **lines; + gchar **current_line; + + switch (output_type) + { + case ANJUTA_LAUNCHER_OUTPUT_STDOUT: + output_handler = GIT_COMMAND_GET_CLASS (self)->output_handler; + break; + case ANJUTA_LAUNCHER_OUTPUT_STDERR: + output_handler = GIT_COMMAND_GET_CLASS (self)->error_handler; + break; + default: + output_handler = NULL; + break; + } + + if (output_handler) + { + lines = split_lines (chars); + + for (current_line = lines; *current_line; current_line++) + output_handler (self, *current_line); + + g_strfreev (lines); + } +} + +static void +git_command_launch (GitCommand *self) +{ + gchar **args; + GList *current_arg; + gint i; + AnjutaLauncherOutputCallback callback; + + args = g_new0 (gchar *, self->priv->num_args + 2); + current_arg = self->priv->args; + i = 1; + + args[0] = "git"; + + while (current_arg) + { + args[i] = current_arg->data; + current_arg = g_list_next (current_arg); + i++; + } + + if (self->priv->single_line_output) + callback = (AnjutaLauncherOutputCallback) git_command_single_line_output_arrived; + else + callback = (AnjutaLauncherOutputCallback) git_command_multi_line_output_arrived; + + if (!anjuta_launcher_execute_v (self->priv->launcher, + args, + callback, + self)) + { + git_command_append_error (self, "Command execution failed."); + anjuta_command_notify_complete (ANJUTA_COMMAND (self), 1); + } + + /* Strings aren't copied; don't free them, just the vector */ + g_free (args); +} + +static void +git_command_start (AnjutaCommand *command) +{ + /* We consider the command to be complete when the launcher notifies us of + * the child git process's completion, instead of when ::run returns. In + * this case, execute the command if ::run retruns 0. */ + if (ANJUTA_COMMAND_GET_CLASS (command)->run (command) == 0) + git_command_launch (GIT_COMMAND (command)); +} + +static void +git_command_error_handler (GitCommand *self, const gchar *output) +{ + GMatchInfo *match_info; + gchar *error; + + if (g_regex_match (self->priv->error_regex, output, 0, &match_info)) + { + error = g_match_info_fetch (match_info, 1); + g_match_info_free (match_info); + + g_string_append (self->priv->error_string, error); + g_free (error); + } +} + +static void +git_command_child_exited (AnjutaLauncher *launcher, gint child_pid, gint status, + gulong time, GitCommand *self) +{ + if (strlen (self->priv->error_string->str) > 0) + { + anjuta_command_set_error_message (ANJUTA_COMMAND (self), + self->priv->error_string->str); + } + + anjuta_command_notify_complete (ANJUTA_COMMAND (self), + (guint) WEXITSTATUS (status)); +} + +static void +git_command_init (GitCommand *self) +{ + self->priv = g_new0 (GitCommandPriv, 1); + self->priv->launcher = anjuta_launcher_new (); + + g_signal_connect (G_OBJECT (self->priv->launcher), "child-exited", + G_CALLBACK (git_command_child_exited), + self); + + self->priv->error_regex = g_regex_new ("^(?:warning|fatal): (.*)", 0, 0, + NULL); + self->priv->error_string = g_string_new (""); + self->priv->info_queue = g_queue_new (); +} + +static void +git_command_finalize (GObject *object) +{ + GitCommand *self; + GList *current_arg; + GList *current_info; + + self = GIT_COMMAND (object); + + current_arg = self->priv->args; + + while (current_arg) + { + g_free (current_arg->data); + current_arg = g_list_next (current_arg); + } + + current_info = self->priv->info_queue->head; + + while (current_info) + { + g_free (current_info->data); + current_info = g_list_next (current_info); + } + + g_object_unref (self->priv->launcher); + g_regex_unref (self->priv->error_regex); + g_string_free (self->priv->error_string, TRUE); + g_queue_free (self->priv->info_queue); + g_free (self->priv->working_directory); + g_free (self->priv); + + G_OBJECT_CLASS (git_command_parent_class)->finalize (object); +} + +static void +git_command_set_property (GObject *object, guint prop_id, const GValue *value, + GParamSpec *pspec) +{ + GitCommand *self; + + self = GIT_COMMAND (object); + + switch (prop_id) + { + case PROP_WORKING_DIRECTORY: + g_free (self->priv->working_directory); + self->priv->working_directory = g_value_dup_string (value); + chdir (self->priv->working_directory); + break; + case PROP_SINGLE_LINE_OUTPUT: + self->priv->single_line_output = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +git_command_get_property (GObject *object, guint prop_id, GValue *value, + GParamSpec *pspec) +{ + GitCommand *self; + + self = GIT_COMMAND (object); + + switch (prop_id) + { + case PROP_WORKING_DIRECTORY: + g_value_set_string (value, self->priv->working_directory); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +git_command_class_init (GitCommandClass *klass) +{ + GObjectClass* object_class = G_OBJECT_CLASS (klass); + AnjutaCommandClass* command_class = ANJUTA_COMMAND_CLASS (klass); + + object_class->finalize = git_command_finalize; + object_class->set_property = git_command_set_property; + object_class->get_property = git_command_get_property; + command_class->start = git_command_start; + klass->output_handler = NULL; + klass->error_handler = git_command_error_handler; + + g_object_class_install_property (object_class, PROP_WORKING_DIRECTORY, + g_param_spec_string ("working-directory", + "", + "Directory to run git in.", + "", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SINGLE_LINE_OUTPUT, + g_param_spec_boolean ("single-line-output", + "", + "If TRUE, output " + "handlers are given " + "output one line at " + "a time.", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); +} + + +void +git_command_add_arg (GitCommand *self, const gchar *arg) +{ + self->priv->args = g_list_append (self->priv->args, g_strdup (arg)); + self->priv->num_args++; +} + +void +git_command_append_error (GitCommand *self, const gchar *error_line) +{ + if (strlen (self->priv->error_string->str) > 0) + g_string_append_printf (self->priv->error_string, "\n%s", error_line); + else + g_string_append (self->priv->error_string, error_line); +} + +void +git_command_push_info (GitCommand *self, const gchar *info) +{ + g_queue_push_tail (self->priv->info_queue, g_strdup (info)); +} + +GQueue * +git_command_get_info_queue (GitCommand *self) +{ + return self->priv->info_queue; +} diff --git a/plugins/git/git-command.h b/plugins/git/git-command.h new file mode 100644 index 00000000..8ba374e8 --- /dev/null +++ b/plugins/git/git-command.h @@ -0,0 +1,72 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ +/* + * git-command-test + * Copyright (C) James Liggett 2008 + * + * git-command-test is free software. + * + * You may 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. + * + * git-command-test 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 git-command-test. If not, write to: + * The Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301, USA. + */ + +#ifndef _GIT_COMMAND_H_ +#define _GIT_COMMAND_H_ + +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GIT_TYPE_COMMAND (git_command_get_type ()) +#define GIT_COMMAND(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIT_TYPE_COMMAND, GitCommand)) +#define GIT_COMMAND_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIT_TYPE_COMMAND, GitCommandClass)) +#define GIT_IS_COMMAND(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIT_TYPE_COMMAND)) +#define GIT_IS_COMMAND_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIT_TYPE_COMMAND)) +#define GIT_COMMAND_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIT_TYPE_COMMAND, GitCommandClass)) + +typedef struct _GitCommandClass GitCommandClass; +typedef struct _GitCommand GitCommand; +typedef struct _GitCommandPriv GitCommandPriv; + +struct _GitCommandClass +{ + AnjutaSyncCommandClass parent_class; + + /* Virtual methods */ + void (*output_handler) (GitCommand *git_command, const gchar *output); + void (*error_handler) (GitCommand *git_command, const gchar *output); +}; + +struct _GitCommand +{ + AnjutaSyncCommand parent_instance; + + GitCommandPriv *priv; +}; + +GType git_command_get_type (void) G_GNUC_CONST; +void git_command_add_arg (GitCommand *self, const gchar *arg); +void git_command_append_error (GitCommand *self, const gchar *error_line); +void git_command_push_info (GitCommand *self, const gchar *info); +GQueue *git_command_get_info_queue (GitCommand *self); + +G_END_DECLS + +#endif /* _GIT_COMMAND_H_ */ -- 2.11.4.GIT