1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
4 * Copyright (C) James Liggett 2008 <jrliggett@cox.net>
6 * anjuta is free software.
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)
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
;
37 GHashTable
*file_monitors
;
40 G_DEFINE_TYPE (GitRefCommand
, git_ref_command
, GIT_TYPE_COMMAND
);
43 free_refs_list (GList
*refs
)
51 g_object_unref (current_ref
->data
);
52 current_ref
= g_list_next (current_ref
);
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
,
68 (GDestroyNotify
) free_refs_list
);
72 git_ref_command_finalize (GObject
*object
)
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
);
85 G_OBJECT_CLASS (git_ref_command_parent_class
)->finalize (object
);
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");
98 git_ref_command_insert_ref (GitRefCommand
*self
, const gchar
*sha
, GitRef
*ref
)
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
);
119 g_hash_table_insert (self
->priv
->refs
, g_strdup (sha
), ref_list
);
125 git_ref_command_handle_output (GitCommand
*git_command
, const gchar
*output
)
128 GMatchInfo
*branch_match_info
;
129 GMatchInfo
*tag_match_info
;
130 GMatchInfo
*remote_match_info
;
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,
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
);
153 else if (g_regex_match (self
->priv
->tag_ref_regex
, output
, 0,
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
);
170 else if (g_regex_match (self
->priv
->remote_ref_regex
, output
, 0,
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
);
183 if (branch_match_info
)
184 g_match_info_free (branch_match_info
);
187 g_match_info_free (tag_match_info
);
189 if (remote_match_info
)
190 g_match_info_free (remote_match_info
);
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
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
),
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
);
223 on_remote_file_monitor_changed (GFileMonitor
*monitor
, GFile
*file
,
224 GFile
*other_file
, GFileMonitorEvent 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
));
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
));
251 git_ref_command_start_automatic_monitor (AnjutaCommand
*command
)
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
;
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
,
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
,
285 git_packed_refs_path
= g_strjoin (G_DIR_SEPARATOR_S
,
290 git_branches_path
= g_strjoin (G_DIR_SEPARATOR_S
,
296 git_tags_path
= g_strjoin (G_DIR_SEPARATOR_S
,
302 git_remotes_path
= g_strjoin (G_DIR_SEPARATOR_S
,
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
),
333 /* Add the monitor to the hash table to simplify cleanup */
334 g_hash_table_insert (self
->priv
->file_monitors
, remotes_file
,
337 remotes_enumerator
= g_file_enumerate_children (remotes_file
,
338 G_FILE_ATTRIBUTE_STANDARD_NAME
","
339 G_FILE_ATTRIBUTE_STANDARD_TYPE
,
342 if (remotes_enumerator
)
344 remotes_info
= g_file_enumerator_next_file (remotes_enumerator
, NULL
, NULL
);
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
,
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
);
377 git_ref_command_stop_automatic_monitor (AnjutaCommand
*command
)
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
;
391 git_ref_command_started (AnjutaCommand
*command
)
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
);
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
;
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
,
430 git_ref_command_get_refs (GitRefCommand
*self
)
432 return g_hash_table_ref (self
->priv
->refs
);