Use raw binary encoding for the launcher.
[anjuta-git-plugin.git] / plugins / git / git-command.c
blobb69afca5c4b7da2df24ca90170493e1c64cdef6a
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3 * anjuta
4 * Copyright (C) James Liggett 2008 <jrliggett@cox.net>
5 *
6 * anjuta 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 * anjuta 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 anjuta. 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 GitCommandClass *klass;
56 klass = GIT_COMMAND_GET_CLASS (self);
58 switch (output_type)
60 case ANJUTA_LAUNCHER_OUTPUT_STDOUT:
61 if (klass->output_handler)
62 GIT_COMMAND_GET_CLASS (self)->output_handler (self, chars);
63 break;
64 case ANJUTA_LAUNCHER_OUTPUT_STDERR:
65 GIT_COMMAND_GET_CLASS (self)->error_handler (self, chars);
66 break;
67 default:
68 break;
72 /* Split the string up line by line. Works almost like g_strsplit, execpt the
73 * newlines are preserved. */
74 static gchar **
75 split_lines (const gchar *string)
77 GList *string_list;
78 gchar *string_pos;
79 const gchar *remainder;
80 guint n;
81 gchar **lines;
82 GList *current_line;
84 string_list = NULL;
85 string_pos = strchr (string, '\n');
86 remainder = string;
87 n = 0;
89 if (string_pos)
91 while (string_pos)
93 /* Increment string_pos to preserve the newline. */
94 string_pos++;
96 string_list = g_list_prepend (string_list, g_strndup (remainder,
97 (string_pos - remainder)));
98 n++;
100 remainder = string_pos;
101 string_pos = strchr (remainder, '\n');
105 else
107 /* If there are no newlines in the string, just return a vector with
108 * one line in it. */
109 string_list = g_list_prepend (string_list, g_strdup (string));
110 n++;
113 lines = g_new (gchar *, n + 1);
114 lines[n--] = NULL;
116 for (current_line = string_list;
117 current_line;
118 current_line = g_list_next (current_line))
120 lines[n--] = current_line->data;
123 g_list_free (string_list);
125 return lines;
129 static void
130 git_command_single_line_output_arrived (AnjutaLauncher *launcher,
131 AnjutaLauncherOutputType output_type,
132 const gchar *chars, GitCommand *self)
134 void (*output_handler) (GitCommand *git_command, const gchar *output);
135 gchar **lines;
136 gchar **current_line;
138 switch (output_type)
140 case ANJUTA_LAUNCHER_OUTPUT_STDOUT:
141 output_handler = GIT_COMMAND_GET_CLASS (self)->output_handler;
142 break;
143 case ANJUTA_LAUNCHER_OUTPUT_STDERR:
144 output_handler = GIT_COMMAND_GET_CLASS (self)->error_handler;
145 break;
146 default:
147 output_handler = NULL;
148 break;
151 if (output_handler)
153 lines = split_lines (chars);
155 for (current_line = lines; *current_line; current_line++)
156 output_handler (self, *current_line);
158 g_strfreev (lines);
162 static void
163 git_command_launch (GitCommand *self)
165 gchar **args;
166 GList *current_arg;
167 gint i;
168 AnjutaLauncherOutputCallback callback;
170 args = g_new0 (gchar *, self->priv->num_args + 2);
171 current_arg = self->priv->args;
172 i = 1;
174 args[0] = "git";
176 while (current_arg)
178 args[i] = current_arg->data;
179 current_arg = g_list_next (current_arg);
180 i++;
183 if (self->priv->single_line_output)
184 callback = (AnjutaLauncherOutputCallback) git_command_single_line_output_arrived;
185 else
186 callback = (AnjutaLauncherOutputCallback) git_command_multi_line_output_arrived;
188 if (!anjuta_launcher_execute_v (self->priv->launcher,
189 args,
190 callback,
191 self))
193 git_command_append_error (self, "Command execution failed.");
194 anjuta_command_notify_complete (ANJUTA_COMMAND (self), 1);
197 /* Strings aren't copied; don't free them, just the vector */
198 g_free (args);
201 static void
202 git_command_start (AnjutaCommand *command)
204 /* We consider the command to be complete when the launcher notifies us of
205 * the child git process's completion, instead of when ::run returns. In
206 * this case, execute the command if ::run retruns 0. */
207 if (ANJUTA_COMMAND_GET_CLASS (command)->run (command) == 0)
208 git_command_launch (GIT_COMMAND (command));
211 static void
212 git_command_error_handler (GitCommand *self, const gchar *output)
214 GMatchInfo *match_info;
215 gchar *error;
217 if (g_regex_match (self->priv->error_regex, output, 0, &match_info))
219 error = g_match_info_fetch (match_info, 1);
220 g_match_info_free (match_info);
222 g_string_append (self->priv->error_string, error);
223 g_free (error);
225 else
226 g_string_append (self->priv->error_string, output);
229 static void
230 git_command_child_exited (AnjutaLauncher *launcher, gint child_pid, gint status,
231 gulong time, GitCommand *self)
233 if (strlen (self->priv->error_string->str) > 0)
235 anjuta_command_set_error_message (ANJUTA_COMMAND (self),
236 self->priv->error_string->str);
239 anjuta_command_notify_complete (ANJUTA_COMMAND (self),
240 (guint) WEXITSTATUS (status));
243 static void
244 git_command_init (GitCommand *self)
246 self->priv = g_new0 (GitCommandPriv, 1);
247 self->priv->launcher = anjuta_launcher_new ();
248 anjuta_launcher_set_encoding (self->priv->launcher, NULL);
250 g_signal_connect (G_OBJECT (self->priv->launcher), "child-exited",
251 G_CALLBACK (git_command_child_exited),
252 self);
254 self->priv->error_regex = g_regex_new ("^(?:warning|fatal|error): (.*)", 0,
255 0, NULL);
256 self->priv->error_string = g_string_new ("");
257 self->priv->info_queue = g_queue_new ();
260 static void
261 git_command_finalize (GObject *object)
263 GitCommand *self;
264 GList *current_arg;
265 GList *current_info;
267 self = GIT_COMMAND (object);
269 current_arg = self->priv->args;
271 while (current_arg)
273 g_free (current_arg->data);
274 current_arg = g_list_next (current_arg);
277 current_info = self->priv->info_queue->head;
279 while (current_info)
281 g_free (current_info->data);
282 current_info = g_list_next (current_info);
285 g_object_unref (self->priv->launcher);
286 g_regex_unref (self->priv->error_regex);
287 g_string_free (self->priv->error_string, TRUE);
288 g_queue_free (self->priv->info_queue);
289 g_free (self->priv->working_directory);
290 g_free (self->priv);
292 G_OBJECT_CLASS (git_command_parent_class)->finalize (object);
295 static void
296 git_command_set_property (GObject *object, guint prop_id, const GValue *value,
297 GParamSpec *pspec)
299 GitCommand *self;
301 self = GIT_COMMAND (object);
303 switch (prop_id)
305 case PROP_WORKING_DIRECTORY:
306 g_free (self->priv->working_directory);
307 self->priv->working_directory = g_value_dup_string (value);
308 chdir (self->priv->working_directory);
309 break;
310 case PROP_SINGLE_LINE_OUTPUT:
311 self->priv->single_line_output = g_value_get_boolean (value);
312 break;
313 default:
314 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
315 break;
319 static void
320 git_command_get_property (GObject *object, guint prop_id, GValue *value,
321 GParamSpec *pspec)
323 GitCommand *self;
325 self = GIT_COMMAND (object);
327 switch (prop_id)
329 case PROP_WORKING_DIRECTORY:
330 g_value_set_string (value, self->priv->working_directory);
331 break;
332 default:
333 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
334 break;
338 static void
339 git_command_class_init (GitCommandClass *klass)
341 GObjectClass* object_class = G_OBJECT_CLASS (klass);
342 AnjutaCommandClass* command_class = ANJUTA_COMMAND_CLASS (klass);
344 object_class->finalize = git_command_finalize;
345 object_class->set_property = git_command_set_property;
346 object_class->get_property = git_command_get_property;
347 command_class->start = git_command_start;
348 klass->output_handler = NULL;
349 klass->error_handler = git_command_error_handler;
351 g_object_class_install_property (object_class, PROP_WORKING_DIRECTORY,
352 g_param_spec_string ("working-directory",
354 "Directory to run git in.",
356 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
358 g_object_class_install_property (object_class, PROP_SINGLE_LINE_OUTPUT,
359 g_param_spec_boolean ("single-line-output",
361 "If TRUE, output "
362 "handlers are given "
363 "output one line at "
364 "a time.",
365 FALSE,
366 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
370 void
371 git_command_add_arg (GitCommand *self, const gchar *arg)
373 self->priv->args = g_list_append (self->priv->args, g_strdup (arg));
374 self->priv->num_args++;
377 void
378 git_command_add_list_to_args (GitCommand *self, GList *list)
380 GList *current_arg;
382 current_arg = list;
384 while (current_arg)
386 self->priv->args = g_list_append (self->priv->args,
387 g_strdup (current_arg->data));
388 self->priv->num_args++;
390 current_arg = g_list_next (current_arg);
394 void
395 git_command_append_error (GitCommand *self, const gchar *error_line)
397 if (strlen (self->priv->error_string->str) > 0)
398 g_string_append_printf (self->priv->error_string, "\n%s", error_line);
399 else
400 g_string_append (self->priv->error_string, error_line);
403 void
404 git_command_push_info (GitCommand *self, const gchar *info)
406 g_queue_push_tail (self->priv->info_queue, g_strdup (info));
407 anjuta_command_notify_data_arrived (ANJUTA_COMMAND (self));
410 GQueue *
411 git_command_get_info_queue (GitCommand *self)
413 return self->priv->info_queue;
416 void
417 git_command_send_output_to_info (GitCommand *git_command, const gchar *output)
419 gchar *newline;
420 gchar *info_string;
422 /* Strip off the newline before sending it to the queue */
423 newline = strchr (output, '\n');
425 if (newline)
426 info_string = g_strndup (output, (newline - output));
427 else
428 info_string = g_strdup (output);
430 git_command_push_info (git_command, info_string);
433 GList *
434 git_command_copy_path_list (GList *list)
436 GList *current_path;
437 GList *new_list;
439 new_list = NULL;
440 current_path = list;
442 while (current_path)
444 new_list = g_list_append (new_list, g_strdup (current_path->data));
445 current_path = g_list_next (current_path);
448 return new_list;
451 void
452 git_command_free_path_list (GList *list)
454 GList *current_path;
456 current_path = list;
458 while (current_path)
460 g_free (current_path->data);
461 current_path = g_list_next (current_path);
464 g_list_free (list);