Implement GitCommand class
[anjuta-git-plugin.git] / plugins / git / git-command.c
blob8b44c1d95a4d43a6763c33486a4e3cd3da645f1e
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3 * git-command-test
4 * Copyright (C) James Liggett 2008 <jrliggett@cox.net>
5 *
6 * git-command-test is free software.
7 *
8 * You may redistribute it and/or modify it under the terms of the
9 * GNU General Public License, as published by the Free Software
10 * Foundation; either version 2 of the License, or (at your option)
11 * any later version.
13 * git-command-test is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 * See the GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with git-command-test. If not, write to:
20 * The Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301, USA.
25 #include "git-command.h"
27 enum
29 PROP_0,
31 PROP_WORKING_DIRECTORY,
32 PROP_SINGLE_LINE_OUTPUT
35 struct _GitCommandPriv
37 AnjutaLauncher *launcher;
38 GList *args;
39 size_t num_args;
40 gchar *working_directory;
41 GRegex *error_regex;
42 GString *error_string;
43 GQueue *info_queue;
44 gboolean single_line_output;
47 G_DEFINE_TYPE (GitCommand, git_command, ANJUTA_TYPE_SYNC_COMMAND);
49 static void
50 git_command_multi_line_output_arrived (AnjutaLauncher *launcher,
51 AnjutaLauncherOutputType output_type,
52 const gchar *chars, GitCommand *self)
54 switch (output_type)
56 case ANJUTA_LAUNCHER_OUTPUT_STDOUT:
57 GIT_COMMAND_GET_CLASS (self)->output_handler (self, chars);
58 break;
59 case ANJUTA_LAUNCHER_OUTPUT_STDERR:
60 GIT_COMMAND_GET_CLASS (self)->error_handler (self, chars);
61 break;
62 default:
63 break;
67 /* Split the string up line by line. Works almost like g_strsplit, execpt the
68 * newlines are preserved. */
69 static gchar **
70 split_lines (const gchar *string)
72 GList *string_list;
73 gchar *string_pos;
74 const gchar *remainder;
75 guint n;
76 gchar **lines;
77 GList *current_line;
79 string_list = NULL;
80 string_pos = strchr (string, '\n');
81 remainder = string;
82 n = 0;
84 if (string_pos)
86 while (string_pos)
88 /* Increment string_pos to preserve the newline. */
89 string_pos++;
91 string_list = g_list_prepend (string_list, g_strndup (remainder,
92 (string_pos - remainder)));
93 n++;
95 remainder = string_pos;
96 string_pos = strchr (remainder, '\n');
100 else
102 /* If there are no newlines in the string, just return a vector with
103 * one line in it. */
104 string_list = g_list_prepend (string_list, g_strdup (string));
105 n++;
108 lines = g_new (gchar *, n + 1);
109 lines[n--] = NULL;
111 for (current_line = string_list;
112 current_line;
113 current_line = g_list_next (current_line))
115 lines[n--] = current_line->data;
118 g_list_free (string_list);
120 return lines;
124 static void
125 git_command_single_line_output_arrived (AnjutaLauncher *launcher,
126 AnjutaLauncherOutputType output_type,
127 const gchar *chars, GitCommand *self)
129 void (*output_handler) (GitCommand *git_command, const gchar *output);
130 gchar **lines;
131 gchar **current_line;
133 switch (output_type)
135 case ANJUTA_LAUNCHER_OUTPUT_STDOUT:
136 output_handler = GIT_COMMAND_GET_CLASS (self)->output_handler;
137 break;
138 case ANJUTA_LAUNCHER_OUTPUT_STDERR:
139 output_handler = GIT_COMMAND_GET_CLASS (self)->error_handler;
140 break;
141 default:
142 output_handler = NULL;
143 break;
146 if (output_handler)
148 lines = split_lines (chars);
150 for (current_line = lines; *current_line; current_line++)
151 output_handler (self, *current_line);
153 g_strfreev (lines);
157 static void
158 git_command_launch (GitCommand *self)
160 gchar **args;
161 GList *current_arg;
162 gint i;
163 AnjutaLauncherOutputCallback callback;
165 args = g_new0 (gchar *, self->priv->num_args + 2);
166 current_arg = self->priv->args;
167 i = 1;
169 args[0] = "git";
171 while (current_arg)
173 args[i] = current_arg->data;
174 current_arg = g_list_next (current_arg);
175 i++;
178 if (self->priv->single_line_output)
179 callback = (AnjutaLauncherOutputCallback) git_command_single_line_output_arrived;
180 else
181 callback = (AnjutaLauncherOutputCallback) git_command_multi_line_output_arrived;
183 if (!anjuta_launcher_execute_v (self->priv->launcher,
184 args,
185 callback,
186 self))
188 git_command_append_error (self, "Command execution failed.");
189 anjuta_command_notify_complete (ANJUTA_COMMAND (self), 1);
192 /* Strings aren't copied; don't free them, just the vector */
193 g_free (args);
196 static void
197 git_command_start (AnjutaCommand *command)
199 /* We consider the command to be complete when the launcher notifies us of
200 * the child git process's completion, instead of when ::run returns. In
201 * this case, execute the command if ::run retruns 0. */
202 if (ANJUTA_COMMAND_GET_CLASS (command)->run (command) == 0)
203 git_command_launch (GIT_COMMAND (command));
206 static void
207 git_command_error_handler (GitCommand *self, const gchar *output)
209 GMatchInfo *match_info;
210 gchar *error;
212 if (g_regex_match (self->priv->error_regex, output, 0, &match_info))
214 error = g_match_info_fetch (match_info, 1);
215 g_match_info_free (match_info);
217 g_string_append (self->priv->error_string, error);
218 g_free (error);
222 static void
223 git_command_child_exited (AnjutaLauncher *launcher, gint child_pid, gint status,
224 gulong time, GitCommand *self)
226 if (strlen (self->priv->error_string->str) > 0)
228 anjuta_command_set_error_message (ANJUTA_COMMAND (self),
229 self->priv->error_string->str);
232 anjuta_command_notify_complete (ANJUTA_COMMAND (self),
233 (guint) WEXITSTATUS (status));
236 static void
237 git_command_init (GitCommand *self)
239 self->priv = g_new0 (GitCommandPriv, 1);
240 self->priv->launcher = anjuta_launcher_new ();
242 g_signal_connect (G_OBJECT (self->priv->launcher), "child-exited",
243 G_CALLBACK (git_command_child_exited),
244 self);
246 self->priv->error_regex = g_regex_new ("^(?:warning|fatal): (.*)", 0, 0,
247 NULL);
248 self->priv->error_string = g_string_new ("");
249 self->priv->info_queue = g_queue_new ();
252 static void
253 git_command_finalize (GObject *object)
255 GitCommand *self;
256 GList *current_arg;
257 GList *current_info;
259 self = GIT_COMMAND (object);
261 current_arg = self->priv->args;
263 while (current_arg)
265 g_free (current_arg->data);
266 current_arg = g_list_next (current_arg);
269 current_info = self->priv->info_queue->head;
271 while (current_info)
273 g_free (current_info->data);
274 current_info = g_list_next (current_info);
277 g_object_unref (self->priv->launcher);
278 g_regex_unref (self->priv->error_regex);
279 g_string_free (self->priv->error_string, TRUE);
280 g_queue_free (self->priv->info_queue);
281 g_free (self->priv->working_directory);
282 g_free (self->priv);
284 G_OBJECT_CLASS (git_command_parent_class)->finalize (object);
287 static void
288 git_command_set_property (GObject *object, guint prop_id, const GValue *value,
289 GParamSpec *pspec)
291 GitCommand *self;
293 self = GIT_COMMAND (object);
295 switch (prop_id)
297 case PROP_WORKING_DIRECTORY:
298 g_free (self->priv->working_directory);
299 self->priv->working_directory = g_value_dup_string (value);
300 chdir (self->priv->working_directory);
301 break;
302 case PROP_SINGLE_LINE_OUTPUT:
303 self->priv->single_line_output = g_value_get_boolean (value);
304 break;
305 default:
306 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
307 break;
311 static void
312 git_command_get_property (GObject *object, guint prop_id, GValue *value,
313 GParamSpec *pspec)
315 GitCommand *self;
317 self = GIT_COMMAND (object);
319 switch (prop_id)
321 case PROP_WORKING_DIRECTORY:
322 g_value_set_string (value, self->priv->working_directory);
323 break;
324 default:
325 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
326 break;
330 static void
331 git_command_class_init (GitCommandClass *klass)
333 GObjectClass* object_class = G_OBJECT_CLASS (klass);
334 AnjutaCommandClass* command_class = ANJUTA_COMMAND_CLASS (klass);
336 object_class->finalize = git_command_finalize;
337 object_class->set_property = git_command_set_property;
338 object_class->get_property = git_command_get_property;
339 command_class->start = git_command_start;
340 klass->output_handler = NULL;
341 klass->error_handler = git_command_error_handler;
343 g_object_class_install_property (object_class, PROP_WORKING_DIRECTORY,
344 g_param_spec_string ("working-directory",
346 "Directory to run git in.",
348 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
350 g_object_class_install_property (object_class, PROP_SINGLE_LINE_OUTPUT,
351 g_param_spec_boolean ("single-line-output",
353 "If TRUE, output "
354 "handlers are given "
355 "output one line at "
356 "a time.",
357 FALSE,
358 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
362 void
363 git_command_add_arg (GitCommand *self, const gchar *arg)
365 self->priv->args = g_list_append (self->priv->args, g_strdup (arg));
366 self->priv->num_args++;
369 void
370 git_command_append_error (GitCommand *self, const gchar *error_line)
372 if (strlen (self->priv->error_string->str) > 0)
373 g_string_append_printf (self->priv->error_string, "\n%s", error_line);
374 else
375 g_string_append (self->priv->error_string, error_line);
378 void
379 git_command_push_info (GitCommand *self, const gchar *info)
381 g_queue_push_tail (self->priv->info_queue, g_strdup (info));
384 GQueue *
385 git_command_get_info_queue (GitCommand *self)
387 return self->priv->info_queue;