changed the refresh hotkey
[giggle.git] / libgiggle / giggle-git-ignore.c
blob30e0b69dffbe9fdfd09ecfd0232ae8104372a5d7
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2007 Imendio AB
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
21 #include <config.h>
22 #include <string.h>
23 #include <glib.h>
24 #include <fnmatch.h>
26 #include "giggle-git-ignore.h"
27 #include "giggle-git.h"
29 typedef struct GiggleGitIgnorePriv GiggleGitIgnorePriv;
31 struct GiggleGitIgnorePriv {
32 GiggleGit *git;
33 gchar *directory_path;
34 gchar *relative_path;
36 GPtrArray *globs;
37 GPtrArray *global_globs; /* .git/info/exclude */
40 static void git_ignore_finalize (GObject *object);
41 static void git_ignore_get_property (GObject *object,
42 guint param_id,
43 GValue *value,
44 GParamSpec *pspec);
45 static void git_ignore_set_property (GObject *object,
46 guint param_id,
47 const GValue *value,
48 GParamSpec *pspec);
49 static GObject* git_ignore_constructor (GType type,
50 guint n_construct_properties,
51 GObjectConstructParam *construct_params);
54 G_DEFINE_TYPE (GiggleGitIgnore, giggle_git_ignore, G_TYPE_OBJECT);
56 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GIGGLE_TYPE_GIT_IGNORE, GiggleGitIgnorePriv))
58 enum {
59 PROP_0,
60 PROP_DIRECTORY,
63 static void
64 giggle_git_ignore_class_init (GiggleGitIgnoreClass *class)
66 GObjectClass *object_class = G_OBJECT_CLASS (class);
68 object_class->finalize = git_ignore_finalize;
69 object_class->get_property = git_ignore_get_property;
70 object_class->set_property = git_ignore_set_property;
71 object_class->constructor = git_ignore_constructor;
73 g_object_class_install_property (object_class,
74 PROP_DIRECTORY,
75 g_param_spec_string ("directory",
76 "Directory",
77 "Path to the Directory containing the .gitignore file",
78 NULL,
79 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
81 g_type_class_add_private (object_class, sizeof (GiggleGitIgnorePriv));
84 static void
85 giggle_git_ignore_init (GiggleGitIgnore *git_ignore)
87 GiggleGitIgnorePriv *priv;
89 priv = GET_PRIV (git_ignore);
91 priv->git = giggle_git_get ();
94 static void
95 git_ignore_finalize (GObject *object)
97 GiggleGitIgnorePriv *priv;
99 priv = GET_PRIV (object);
101 g_object_unref (priv->git);
102 g_free (priv->directory_path);
103 g_free (priv->relative_path);
105 if (priv->globs) {
106 g_ptr_array_foreach (priv->globs, (GFunc) g_free, NULL);
107 g_ptr_array_free (priv->globs, TRUE);
110 if (priv->global_globs) {
111 g_ptr_array_foreach (priv->global_globs, (GFunc) g_free, NULL);
112 g_ptr_array_free (priv->global_globs, TRUE);
115 G_OBJECT_CLASS (giggle_git_ignore_parent_class)->finalize (object);
118 static void
119 git_ignore_get_property (GObject *object,
120 guint param_id,
121 GValue *value,
122 GParamSpec *pspec)
124 GiggleGitIgnorePriv *priv;
126 priv = GET_PRIV (object);
128 switch (param_id) {
129 case PROP_DIRECTORY:
130 g_value_set_string (value, priv->directory_path);
131 break;
132 default:
133 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
134 break;
138 static void
139 git_ignore_set_property (GObject *object,
140 guint param_id,
141 const GValue *value,
142 GParamSpec *pspec)
144 GiggleGitIgnorePriv *priv;
146 priv = GET_PRIV (object);
148 switch (param_id) {
149 case PROP_DIRECTORY:
150 priv->directory_path = g_value_dup_string (value);
151 break;
152 default:
153 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
154 break;
158 static GPtrArray *
159 git_ignore_read_file (const gchar *path)
161 GPtrArray *array;
162 gchar **strarr;
163 gchar *contents;
164 gint i;
166 if (!g_file_get_contents (path, &contents, NULL, NULL)) {
167 return g_ptr_array_new ();
170 array = g_ptr_array_sized_new (10);
171 strarr = g_strsplit (contents, "\n", -1);
173 for (i = 0; strarr[i]; i++) {
174 if (*strarr[i] && !g_str_has_prefix (strarr[i], "#")) {
175 g_ptr_array_add (array, g_strdup (strarr[i]));
179 g_free (contents);
180 g_strfreev (strarr);
182 return array;
185 static GObject*
186 git_ignore_constructor (GType type,
187 guint n_construct_properties,
188 GObjectConstructParam *construct_params)
190 GObject *object;
191 GiggleGitIgnorePriv *priv;
192 gchar *path;
193 const gchar *project_path;
195 object = (* G_OBJECT_CLASS (giggle_git_ignore_parent_class)->constructor) (type,
196 n_construct_properties,
197 construct_params);
198 priv = GET_PRIV (object);
200 path = g_build_filename (priv->directory_path, ".gitignore", NULL);
201 priv->globs = git_ignore_read_file (path);
202 g_free (path);
204 path = g_build_filename (giggle_git_get_git_dir (priv->git),
205 "info", "exclude", NULL);
206 priv->global_globs = git_ignore_read_file (path);
207 g_free (path);
209 project_path = giggle_git_get_directory (priv->git);
211 if (strcmp (priv->directory_path, project_path) != 0) {
212 priv->relative_path = g_strdup (priv->directory_path
213 + strlen (giggle_git_get_directory (priv->git))
214 + 1 /* dir separator */);
217 return object;
220 static void
221 git_ignore_save_file (GiggleGitIgnore *git_ignore)
223 GiggleGitIgnorePriv *priv;
224 gchar *path;
225 GString *content;
226 gint i;
228 priv = GET_PRIV (git_ignore);
229 path = g_build_filename (priv->directory_path, ".gitignore", NULL);
230 content = g_string_new ("");
232 for (i = 0; i < priv->globs->len; i++) {
233 g_string_append_printf (content, "%s\n", (gchar *) g_ptr_array_index (priv->globs, i));
236 g_file_set_contents (path, content->str, -1, NULL);
237 g_string_free (content, TRUE);
240 GiggleGitIgnore *
241 giggle_git_ignore_new (const gchar *directory_path)
243 g_return_val_if_fail (directory_path != NULL, NULL);
245 return g_object_new (GIGGLE_TYPE_GIT_IGNORE,
246 "directory", directory_path,
247 NULL);
250 static const gchar *
251 git_ignore_get_basename (const gchar *path)
253 const gchar *basename;
255 basename = strrchr (path, G_DIR_SEPARATOR);
257 if (!basename) {
258 basename = path;
259 } else {
260 /* avoid dir separator */
261 basename++;
264 return basename;
267 static gboolean
268 git_ignore_path_matches_glob (const gchar *path,
269 const gchar *glob,
270 const gchar *relative_path)
272 const gchar *filename;
273 gchar *str = NULL;
274 gboolean match;
276 /* Match examples:
277 * Glob String
278 * ==== ======
279 * aaa aaa = match
280 * /aaa aaa = match
281 * aaa b/aaa = match
282 * b/aaa b/aaa = match
283 * /b/aaa b/aaa = match
285 * aaa b/aaa/c = NO match
286 * /aaa b/aaa = NO match
287 * b/aaa aaa = NO match
288 * b/aaa c/b/aaa = NO match
289 * /b/aaa c/b/aaa = NO match
292 if (strchr (glob, G_DIR_SEPARATOR)) {
293 /* contains a dir separator, git wants these
294 * entries to match completely occurrences
295 * from the current dir.
298 if (relative_path) {
299 str = g_build_filename (relative_path, glob, NULL);
300 glob = str;
303 if (glob[0] == G_DIR_SEPARATOR) {
304 glob++;
307 match = (fnmatch (glob, path, FNM_PATHNAME) == 0);
308 g_free (str);
309 } else {
310 /* doesn't contain any dir separator, git will
311 * match these entries with any filename in any
312 * subdir
314 filename = git_ignore_get_basename (path);
315 match = (fnmatch (glob, filename, FNM_PATHNAME) == 0);
318 return match;
321 static gboolean
322 git_ignore_path_matches (const gchar *path,
323 GPtrArray *array,
324 const gchar *relative_path)
326 gboolean match = FALSE;
327 gint n_glob = 0;
328 const gchar *glob;
330 if (!array) {
331 return FALSE;
334 while (!match && n_glob < array->len) {
335 glob = g_ptr_array_index (array, n_glob);
336 n_glob++;
338 match = git_ignore_path_matches_glob (path, glob, relative_path);
341 return match;
344 gboolean
345 giggle_git_ignore_path_matches (GiggleGitIgnore *git_ignore,
346 const gchar *path)
348 GiggleGitIgnorePriv *priv;
350 g_return_val_if_fail (GIGGLE_IS_GIT_IGNORE (git_ignore), FALSE);
352 priv = GET_PRIV (git_ignore);
354 if (git_ignore_path_matches (path, priv->globs, priv->relative_path)) {
355 return TRUE;
358 if (git_ignore_path_matches (path, priv->global_globs, NULL)) {
359 return TRUE;
362 return FALSE;
365 void
366 giggle_git_ignore_add_glob (GiggleGitIgnore *git_ignore,
367 const gchar *glob)
369 GiggleGitIgnorePriv *priv;
371 g_return_if_fail (GIGGLE_IS_GIT_IGNORE (git_ignore));
372 g_return_if_fail (glob != NULL);
374 priv = GET_PRIV (git_ignore);
375 g_ptr_array_add (priv->globs, g_strdup (glob));
377 git_ignore_save_file (git_ignore);
380 void
381 giggle_git_ignore_add_glob_for_path (GiggleGitIgnore *git_ignore,
382 const gchar *path)
384 GiggleGitIgnorePriv *priv;
385 const gchar *glob;
387 g_return_if_fail (GIGGLE_IS_GIT_IGNORE (git_ignore));
388 g_return_if_fail (path != NULL);
390 priv = GET_PRIV (git_ignore);
392 /* FIXME: we add here just the filename, which will
393 * also ignore matching filenames for subdirectories,
394 * do we want it more fine grained?
396 glob = git_ignore_get_basename (path);
397 giggle_git_ignore_add_glob (git_ignore, glob);
400 gboolean
401 giggle_git_ignore_remove_glob_for_path (GiggleGitIgnore *git_ignore,
402 const gchar *path,
403 gboolean perfect_match)
405 GiggleGitIgnorePriv *priv;
406 const gchar *glob;
407 gint i = 0;
408 gboolean removed = FALSE;
409 const gchar *filename;
411 g_return_val_if_fail (GIGGLE_IS_GIT_IGNORE (git_ignore), FALSE);
412 g_return_val_if_fail (path != NULL, FALSE);
414 priv = GET_PRIV (git_ignore);
416 while (i < priv->globs->len) {
417 glob = g_ptr_array_index (priv->globs, i);
418 filename = git_ignore_get_basename (path);
420 if ((perfect_match && strcmp (glob, filename) == 0) ||
421 (!perfect_match &&
422 git_ignore_path_matches_glob (path, glob, priv->relative_path))) {
423 g_ptr_array_remove_index (priv->globs, i);
424 removed = TRUE;
425 } else {
426 /* no match, increment index */
427 i++;
431 if (removed) {
432 git_ignore_save_file (git_ignore);
435 return removed;