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-status-command.h"
27 #define STATUS_REGEX "(modified|new file|deleted|unmerged|both modified|both added|both deleted): (.*)"
28 #define UNTRACKED_FILES_REGEX "(?:#\\t)(.*)"
29 #define SECTION_COMMIT_REGEX "Changes to be committed:"
30 #define SECTION_NOT_UPDATED_REGEX "Changed but not updated:"
31 #define SECTION_UNTRACKED_REGEX "Untracked files:"
33 struct _GitStatusCommandPriv
36 GHashTable
*path_lookup_table
;
37 GitStatusSections sections
;
38 GitStatusSections current_section
;
39 GRegex
*current_section_regex
;
41 GRegex
*untracked_files_regex
;
42 GRegex
*section_commit_regex
;
43 GRegex
*section_not_updated_regex
;
44 GRegex
*section_untracked_regex
;
45 GFileMonitor
*head_monitor
;
46 GFileMonitor
*index_monitor
;
49 G_DEFINE_TYPE (GitStatusCommand
, git_status_command
, GIT_TYPE_COMMAND
);
52 git_status_command_run (AnjutaCommand
*command
)
54 git_command_add_arg (GIT_COMMAND (command
), "status");
60 git_status_command_handle_output (GitCommand
*git_command
, const gchar
*output
)
62 GitStatusCommand
*self
;
63 GMatchInfo
*match_info
;
64 GitStatus
*status_object
;
68 self
= GIT_STATUS_COMMAND (git_command
);
70 /* See if the section has changed */
71 if (g_regex_match (self
->priv
->section_commit_regex
, output
, 0, NULL
))
73 self
->priv
->current_section
= GIT_STATUS_SECTION_COMMIT
;
74 self
->priv
->current_section_regex
= self
->priv
->status_regex
;
77 else if (g_regex_match (self
->priv
->section_not_updated_regex
, output
, 0,
80 self
->priv
->current_section
= GIT_STATUS_SECTION_NOT_UPDATED
;
81 self
->priv
->current_section_regex
= self
->priv
->status_regex
;
84 else if (g_regex_match (self
->priv
->section_untracked_regex
, output
, 0,
87 self
->priv
->current_section
= GIT_STATUS_SECTION_UNTRACKED
;
88 self
->priv
->current_section_regex
= self
->priv
->untracked_files_regex
;
92 if (self
->priv
->sections
& self
->priv
->current_section
)
94 if (g_regex_match (self
->priv
->current_section_regex
, output
, 0,
97 if (self
->priv
->current_section_regex
== self
->priv
->status_regex
)
99 status
= g_match_info_fetch (match_info
, 1);
100 path
= g_match_info_fetch (match_info
, 2);
104 status
= g_strdup ("untracked");
105 path
= g_match_info_fetch (match_info
, 1);
108 /* Git sometimes mentions paths twice in status output. This can
109 * happen, for example, where there is a conflict, in which case a
110 * path would show up as both "unmerged" and "modified." */
113 if (!g_hash_table_lookup_extended (self
->priv
->path_lookup_table
,
116 status_object
= git_status_new (path
, status
);
117 g_queue_push_tail (self
->priv
->status_queue
, status_object
);
118 g_hash_table_insert (self
->priv
->path_lookup_table
,
119 g_strdup (path
), NULL
);
120 anjuta_command_notify_data_arrived (ANJUTA_COMMAND (git_command
));
127 g_match_info_free (match_info
);
132 git_status_command_init (GitStatusCommand
*self
)
134 self
->priv
= g_new0 (GitStatusCommandPriv
, 1);
135 self
->priv
->status_queue
= g_queue_new ();
136 self
->priv
->path_lookup_table
= g_hash_table_new_full (g_str_hash
,
139 self
->priv
->status_regex
= g_regex_new (STATUS_REGEX
, 0, 0, NULL
);
140 self
->priv
->untracked_files_regex
= g_regex_new (UNTRACKED_FILES_REGEX
,
142 self
->priv
->section_commit_regex
= g_regex_new (SECTION_COMMIT_REGEX
, 0, 0,
144 self
->priv
->section_not_updated_regex
= g_regex_new (SECTION_NOT_UPDATED_REGEX
,
146 self
->priv
->section_untracked_regex
= g_regex_new (SECTION_UNTRACKED_REGEX
,
151 on_file_monitor_changed (GFileMonitor
*monitor
, GFile
*file
, GFile
*other_file
,
152 GFileMonitorEvent event
, AnjutaCommand
*command
)
154 /* Handle created and modified events just to cover all possible cases.
155 * Sometimes git does some odd things... */
156 if (event
== G_FILE_MONITOR_EVENT_CHANGED
||
157 event
== G_FILE_MONITOR_EVENT_CREATED
)
159 anjuta_command_start (command
);
164 git_status_command_start_automatic_monitor (AnjutaCommand
*command
)
166 GitStatusCommand
*self
;
167 gchar
*working_directory
;
168 gchar
*git_head_path
;
169 gchar
*git_index_path
;
170 GFile
*git_head_file
;
171 GFile
*git_index_file
;
173 self
= GIT_STATUS_COMMAND (command
);
175 g_object_get (self
, "working-directory", &working_directory
, NULL
);
177 /* Watch for changes to the HEAD file and the index file, so that we can
178 * at least detect commits and index changes. */
179 git_head_path
= g_strjoin (G_DIR_SEPARATOR_S
,
184 git_index_path
= g_strjoin (G_DIR_SEPARATOR_S
,
189 git_head_file
= g_file_new_for_path (git_head_path
);
190 git_index_file
= g_file_new_for_path (git_index_path
);
191 self
->priv
->head_monitor
= g_file_monitor_file (git_head_file
, 0, NULL
,
193 self
->priv
->index_monitor
= g_file_monitor_file (git_index_file
, 0, NULL
,
196 g_signal_connect (G_OBJECT (self
->priv
->head_monitor
), "changed",
197 G_CALLBACK (on_file_monitor_changed
),
200 g_signal_connect (G_OBJECT (self
->priv
->index_monitor
), "changed",
201 G_CALLBACK (on_file_monitor_changed
),
204 g_free (git_head_path
);
205 g_free (git_index_path
);
206 g_object_unref (git_head_file
);
207 g_object_unref (git_index_file
);
213 git_status_command_stop_automatic_monitor (AnjutaCommand
*command
)
215 GitStatusCommand
*self
;
217 self
= GIT_STATUS_COMMAND (command
);
219 if (self
->priv
->head_monitor
)
221 g_file_monitor_cancel (self
->priv
->head_monitor
);
222 g_object_unref (self
->priv
->head_monitor
);
223 self
->priv
->head_monitor
= NULL
;
226 if (self
->priv
->index_monitor
)
228 g_file_monitor_cancel (self
->priv
->index_monitor
);
229 g_object_unref (self
->priv
->index_monitor
);
230 self
->priv
->index_monitor
= NULL
;
235 git_status_command_clear_output (GitStatusCommand
*self
)
237 GList
*current_output
;
239 current_output
= self
->priv
->status_queue
->head
;
241 while (current_output
)
243 g_object_unref (current_output
->data
);
244 current_output
= g_list_next (current_output
);
247 g_queue_clear (self
->priv
->status_queue
);
251 git_status_command_data_arrived (AnjutaCommand
*command
)
253 git_status_command_clear_output (GIT_STATUS_COMMAND (command
));
257 git_status_command_finished (AnjutaCommand
*command
, guint return_code
)
259 GitStatusCommand
*self
;
261 self
= GIT_STATUS_COMMAND (command
);
263 g_hash_table_remove_all (self
->priv
->path_lookup_table
);
265 ANJUTA_COMMAND_CLASS (git_status_command_parent_class
)->command_finished (command
,
270 git_status_command_finalize (GObject
*object
)
272 GitStatusCommand
*self
;
273 GList
*current_status
;
275 self
= GIT_STATUS_COMMAND (object
);
276 current_status
= self
->priv
->status_queue
->head
;
278 git_status_command_clear_output (self
);
279 git_status_command_stop_automatic_monitor (ANJUTA_COMMAND (self
));
281 g_queue_free (self
->priv
->status_queue
);
282 g_hash_table_destroy (self
->priv
->path_lookup_table
);
283 g_regex_unref (self
->priv
->status_regex
);
284 g_regex_unref (self
->priv
->untracked_files_regex
);
285 g_regex_unref (self
->priv
->section_commit_regex
);
286 g_regex_unref (self
->priv
->section_not_updated_regex
);
287 g_regex_unref (self
->priv
->section_untracked_regex
);
291 G_OBJECT_CLASS (git_status_command_parent_class
)->finalize (object
);
295 git_status_command_class_init (GitStatusCommandClass
*klass
)
297 GObjectClass
* object_class
= G_OBJECT_CLASS (klass
);
298 GitCommandClass
* parent_class
= GIT_COMMAND_CLASS (klass
);
299 AnjutaCommandClass
* command_class
= ANJUTA_COMMAND_CLASS (klass
);
301 object_class
->finalize
= git_status_command_finalize
;
302 parent_class
->output_handler
= git_status_command_handle_output
;
303 command_class
->run
= git_status_command_run
;
304 command_class
->data_arrived
= git_status_command_data_arrived
;
305 command_class
->command_finished
= git_status_command_finished
;
306 command_class
->start_automatic_monitor
= git_status_command_start_automatic_monitor
;
307 command_class
->stop_automatic_monitor
= git_status_command_stop_automatic_monitor
;
312 git_status_command_new (const gchar
*working_directory
,
313 GitStatusSections sections
)
315 GitStatusCommand
*self
;
317 self
= g_object_new (GIT_TYPE_STATUS_COMMAND
,
318 "working-directory", working_directory
,
319 "single-line-output", TRUE
,
322 self
->priv
->sections
= sections
;
328 git_status_command_get_status_queue (GitStatusCommand
*self
)
330 return self
->priv
->status_queue
;