debug-manager: use g_spawn_sync() instead of fork() and waitpid()
[anjuta.git] / plugins / git / git-ref-command.c
blob8e609d813a3684ff39adcc7edbd387b47e278a45
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-ref-command.h"
27 #define BRANCH_REF_REGEX "([[:xdigit:]]{40}) refs/heads/(.*)"
28 #define TAG_REF_REGEX "([[:xdigit:]]{40}) refs/tags/(.*)"
29 #define REMOTE_REF_REGEX "([[:xdigit:]]{40}) refs/remotes/(.*)"
31 struct _GitRefCommandPriv
33 GRegex *branch_ref_regex;
34 GRegex *tag_ref_regex;
35 GRegex *remote_ref_regex;
36 GHashTable *refs;
37 GHashTable *file_monitors;
40 G_DEFINE_TYPE (GitRefCommand, git_ref_command, GIT_TYPE_COMMAND);
42 static void
43 free_refs_list (GList *refs)
45 GList *current_ref;
47 current_ref = refs;
49 while (current_ref)
51 g_object_unref (current_ref->data);
52 current_ref = g_list_next (current_ref);
55 g_list_free (refs);
58 static void
59 git_ref_command_init (GitRefCommand *self)
61 self->priv = g_new0 (GitRefCommandPriv, 1);
63 self->priv->branch_ref_regex = g_regex_new (BRANCH_REF_REGEX, 0, 0, NULL);
64 self->priv->tag_ref_regex = g_regex_new (TAG_REF_REGEX, 0, 0, NULL);
65 self->priv->remote_ref_regex = g_regex_new (REMOTE_REF_REGEX, 0, 0, NULL);
66 self->priv->refs = g_hash_table_new_full (g_str_hash, g_str_equal,
67 NULL,
68 (GDestroyNotify) free_refs_list);
71 static void
72 git_ref_command_finalize (GObject *object)
74 GitRefCommand *self;
76 self = GIT_REF_COMMAND (object);
78 g_regex_unref (self->priv->branch_ref_regex);
79 g_regex_unref (self->priv->tag_ref_regex);
80 g_regex_unref (self->priv->remote_ref_regex);
81 g_hash_table_unref (self->priv->refs);
83 g_free (self->priv);
85 G_OBJECT_CLASS (git_ref_command_parent_class)->finalize (object);
88 static guint
89 git_ref_command_run (AnjutaCommand *command)
91 git_command_add_arg (GIT_COMMAND (command), "show-ref");
92 git_command_add_arg (GIT_COMMAND (command), "--dereference");
94 return 0;
97 static void
98 git_ref_command_insert_ref (GitRefCommand *self, const gchar *sha, GitRef *ref)
100 GList *ref_list;
101 gchar *name;
102 gchar *old_sha;
104 name = git_ref_get_name (ref);
106 ref_list = g_hash_table_lookup (self->priv->refs, sha);
108 ref_list = g_list_append (ref_list, ref);
110 if (g_hash_table_lookup_extended (self->priv->refs, sha,
111 (gpointer) &old_sha, NULL))
113 /* Change the list head for this SHA without destroying it */
114 g_hash_table_steal (self->priv->refs, sha);
116 g_free (old_sha);
119 g_hash_table_insert (self->priv->refs, g_strdup (sha), ref_list);
121 g_free (name);
124 static void
125 git_ref_command_handle_output (GitCommand *git_command, const gchar *output)
127 GitRefCommand *self;
128 GMatchInfo *branch_match_info;
129 GMatchInfo *tag_match_info;
130 GMatchInfo *remote_match_info;
131 gchar *sha;
132 gchar *name;
133 GitRef *ref;
135 self = GIT_REF_COMMAND (git_command);
137 branch_match_info = NULL;
138 tag_match_info = NULL;
139 remote_match_info = NULL;
141 if (g_regex_match (self->priv->branch_ref_regex, output, 0,
142 &branch_match_info))
144 sha = g_match_info_fetch (branch_match_info, 1);
145 name = g_match_info_fetch (branch_match_info, 2);
146 ref = git_ref_new (name, GIT_REF_TYPE_BRANCH);
148 git_ref_command_insert_ref (self, sha, ref);
150 g_free (sha);
151 g_free (name);
153 else if (g_regex_match (self->priv->tag_ref_regex, output, 0,
154 &tag_match_info))
156 sha = g_match_info_fetch (tag_match_info, 1);
157 name = g_match_info_fetch (tag_match_info, 2);
159 if (g_str_has_suffix (name, "^{}"))
160 (g_strrstr (name, "^{}")) [0] = '\0';
162 ref = git_ref_new (name, GIT_REF_TYPE_TAG);
165 git_ref_command_insert_ref (self, sha, ref);
167 g_free (sha);
168 g_free (name);
170 else if (g_regex_match (self->priv->remote_ref_regex, output, 0,
171 &remote_match_info))
173 sha = g_match_info_fetch (remote_match_info, 1);
174 name = g_match_info_fetch (remote_match_info, 2);
175 ref = git_ref_new (name, GIT_REF_TYPE_REMOTE);
177 git_ref_command_insert_ref (self, sha, ref);
179 g_free (sha);
180 g_free (name);
183 if (branch_match_info)
184 g_match_info_free (branch_match_info);
186 if (tag_match_info)
187 g_match_info_free (tag_match_info);
189 if (remote_match_info)
190 g_match_info_free (remote_match_info);
193 static void
194 on_file_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file,
195 GFileMonitorEvent event, AnjutaCommand *command)
197 if (event == G_FILE_MONITOR_EVENT_CREATED ||
198 event == G_FILE_MONITOR_EVENT_DELETED)
200 anjuta_command_start (command);
204 /* Helper method to add file monitors. The command takes ownership of the
205 * file */
206 static void
207 git_ref_command_add_file_monitor (GitRefCommand *self, GFile *file)
209 GFileMonitor *file_monitor;
211 file_monitor = g_file_monitor (file, 0, NULL, NULL);
213 g_signal_connect (G_OBJECT (file_monitor), "changed",
214 G_CALLBACK (on_file_monitor_changed),
215 self);
217 /* Have the hash table take ownership of both the file and the file
218 * monitor to keep memory management consistent. */
219 g_hash_table_insert (self->priv->file_monitors, file, file_monitor);
222 static void
223 on_remote_file_monitor_changed (GFileMonitor *monitor, GFile *file,
224 GFile *other_file, GFileMonitorEvent event,
225 GitRefCommand *self)
227 switch (event)
229 case G_FILE_MONITOR_EVENT_CREATED:
230 /* A new remote was created */
231 git_ref_command_add_file_monitor (self, g_object_ref (file));
233 /* Start the command to reflect changes */
234 anjuta_command_start (ANJUTA_COMMAND (self));
236 break;
237 case G_FILE_MONITOR_EVENT_DELETED:
238 /* A remote was deleted--stop monitoring it */
239 g_hash_table_remove (self->priv->file_monitors, file);
241 anjuta_command_start (ANJUTA_COMMAND (self));
243 break;
244 default:
245 break;
250 static gboolean
251 git_ref_command_start_automatic_monitor (AnjutaCommand *command)
253 GitRefCommand *self;
254 gchar *working_directory;
255 gchar *git_head_path;
256 gchar *git_packed_refs_path;
257 gchar *git_branches_path;
258 gchar *git_tags_path;
259 gchar *git_remotes_path;
260 GFile *remotes_file;
261 GFileMonitor *remotes_monitor;
262 GFileEnumerator *remotes_enumerator;
263 GFileInfo *remotes_info;
264 GFile *current_remote_file;
266 self = GIT_REF_COMMAND (command);
268 g_object_get (self, "working-directory", &working_directory, NULL);
270 /* The file monitors tabe keeps track of all the GFiles and GFileMonitors
271 * that we're tracking. The table takes ownership of the files and the
272 * monitors so that cleanup can be done in one step. */
273 self->priv->file_monitors = g_hash_table_new_full (g_file_hash,
274 (GEqualFunc) g_file_equal,
275 g_object_unref,
276 g_object_unref);
278 /* Watch for changes to any file that might tell us about changes to
279 * branches, tags, remotes, or the active branch */
280 git_head_path = g_strjoin (G_DIR_SEPARATOR_S,
281 working_directory,
282 ".git",
283 "HEAD",
284 NULL);
285 git_packed_refs_path = g_strjoin (G_DIR_SEPARATOR_S,
286 working_directory,
287 ".git",
288 "packed-refs",
289 NULL);
290 git_branches_path = g_strjoin (G_DIR_SEPARATOR_S,
291 working_directory,
292 ".git",
293 "refs",
294 "heads",
295 NULL);
296 git_tags_path = g_strjoin (G_DIR_SEPARATOR_S,
297 working_directory,
298 ".git",
299 "refs",
300 "tags",
301 NULL);
302 git_remotes_path = g_strjoin (G_DIR_SEPARATOR_S,
303 working_directory,
304 ".git",
305 "refs",
306 "remotes",
307 NULL);
309 git_ref_command_add_file_monitor (self,
310 g_file_new_for_path (git_head_path));
312 git_ref_command_add_file_monitor (self,
313 g_file_new_for_path (git_packed_refs_path));
315 git_ref_command_add_file_monitor (self,
316 g_file_new_for_path (git_branches_path));
318 git_ref_command_add_file_monitor (self,
319 g_file_new_for_path (git_tags_path));
321 /* Now handle remotes. Git stores the head of each remote branch in its own
322 * folder under .git/refs/remotes. We need to monitor for changes to each
323 * remote's folder. */
324 remotes_file = g_file_new_for_path (git_remotes_path);
326 /* First, monitor the remotes folder for creation/deletion of remotes */
327 remotes_monitor = g_file_monitor (remotes_file, 0, NULL, NULL);
329 g_signal_connect (G_OBJECT (remotes_monitor), "changed",
330 G_CALLBACK (on_remote_file_monitor_changed),
331 self);
333 /* Add the monitor to the hash table to simplify cleanup */
334 g_hash_table_insert (self->priv->file_monitors, remotes_file,
335 remotes_monitor);
337 remotes_enumerator = g_file_enumerate_children (remotes_file,
338 G_FILE_ATTRIBUTE_STANDARD_NAME ","
339 G_FILE_ATTRIBUTE_STANDARD_TYPE,
340 0, NULL, NULL);
342 if (remotes_enumerator)
344 remotes_info = g_file_enumerator_next_file (remotes_enumerator, NULL, NULL);
346 while (remotes_info)
348 /* Monitor each remote folder for changes */
349 if (g_file_info_get_attribute_uint32 (remotes_info,
350 G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY)
352 current_remote_file = g_file_get_child (remotes_file,
353 g_file_info_get_name (remotes_info));
355 git_ref_command_add_file_monitor (self, current_remote_file);
358 g_object_unref (remotes_info);
360 remotes_info = g_file_enumerator_next_file (remotes_enumerator, NULL,
361 NULL);
363 g_object_unref (remotes_enumerator);
366 g_free (working_directory);
367 g_free (git_head_path);
368 g_free (git_packed_refs_path);
369 g_free (git_branches_path);
370 g_free (git_tags_path);
371 g_free (git_remotes_path);
373 return TRUE;
376 static void
377 git_ref_command_stop_automatic_monitor (AnjutaCommand *command)
379 GitRefCommand *self;
381 self = GIT_REF_COMMAND (command);
383 if (self->priv->file_monitors)
385 g_hash_table_destroy (self->priv->file_monitors);
386 self->priv->file_monitors = NULL;
390 static void
391 git_ref_command_started (AnjutaCommand *command)
393 GitRefCommand *self;
395 self = GIT_REF_COMMAND (command);
397 /* Clear out old data from previous runs */
398 g_hash_table_remove_all (self->priv->refs);
400 ANJUTA_COMMAND_CLASS (git_ref_command_parent_class)->command_started (command);
404 static void
405 git_ref_command_class_init (GitRefCommandClass *klass)
407 GObjectClass* object_class = G_OBJECT_CLASS (klass);
408 GitCommandClass* parent_class = GIT_COMMAND_CLASS (klass);
409 AnjutaCommandClass *command_class = ANJUTA_COMMAND_CLASS (klass);
411 object_class->finalize = git_ref_command_finalize;
412 parent_class->output_handler = git_ref_command_handle_output;
413 command_class->run = git_ref_command_run;
414 command_class->command_started = git_ref_command_started;
415 command_class->start_automatic_monitor = git_ref_command_start_automatic_monitor;
416 command_class->stop_automatic_monitor = git_ref_command_stop_automatic_monitor;
420 GitRefCommand *
421 git_ref_command_new (const gchar *working_directory)
423 return g_object_new (GIT_TYPE_REF_COMMAND,
424 "working-directory", working_directory,
425 "single-line-output", TRUE,
426 NULL);
429 GHashTable *
430 git_ref_command_get_refs (GitRefCommand *self)
432 return g_hash_table_ref (self->priv->refs);