1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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.
26 #include "giggle-git-ignore.h"
27 #include "giggle-git.h"
29 typedef struct GiggleGitIgnorePriv GiggleGitIgnorePriv
;
31 struct GiggleGitIgnorePriv
{
33 gchar
*directory_path
;
37 GPtrArray
*global_globs
; /* .git/info/exclude */
40 static void git_ignore_finalize (GObject
*object
);
41 static void git_ignore_get_property (GObject
*object
,
45 static void git_ignore_set_property (GObject
*object
,
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))
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
,
75 g_param_spec_string ("directory",
77 "Path to the Directory containing the .gitignore file",
79 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
81 g_type_class_add_private (object_class
, sizeof (GiggleGitIgnorePriv
));
85 giggle_git_ignore_init (GiggleGitIgnore
*git_ignore
)
87 GiggleGitIgnorePriv
*priv
;
89 priv
= GET_PRIV (git_ignore
);
91 priv
->git
= giggle_git_get ();
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
);
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
);
119 git_ignore_get_property (GObject
*object
,
124 GiggleGitIgnorePriv
*priv
;
126 priv
= GET_PRIV (object
);
130 g_value_set_string (value
, priv
->directory_path
);
133 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
139 git_ignore_set_property (GObject
*object
,
144 GiggleGitIgnorePriv
*priv
;
146 priv
= GET_PRIV (object
);
150 priv
->directory_path
= g_value_dup_string (value
);
153 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
159 git_ignore_read_file (const gchar
*path
)
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
]));
186 git_ignore_constructor (GType type
,
187 guint n_construct_properties
,
188 GObjectConstructParam
*construct_params
)
191 GiggleGitIgnorePriv
*priv
;
193 const gchar
*project_path
;
195 object
= (* G_OBJECT_CLASS (giggle_git_ignore_parent_class
)->constructor
) (type
,
196 n_construct_properties
,
198 priv
= GET_PRIV (object
);
200 path
= g_build_filename (priv
->directory_path
, ".gitignore", NULL
);
201 priv
->globs
= git_ignore_read_file (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
);
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 */);
221 git_ignore_save_file (GiggleGitIgnore
*git_ignore
)
223 GiggleGitIgnorePriv
*priv
;
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
);
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
,
251 git_ignore_get_basename (const gchar
*path
)
253 const gchar
*basename
;
255 basename
= strrchr (path
, G_DIR_SEPARATOR
);
260 /* avoid dir separator */
268 git_ignore_path_matches_glob (const gchar
*path
,
270 const gchar
*relative_path
)
272 const gchar
*filename
;
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.
299 str
= g_build_filename (relative_path
, glob
, NULL
);
303 if (glob
[0] == G_DIR_SEPARATOR
) {
307 match
= (fnmatch (glob
, path
, FNM_PATHNAME
) == 0);
310 /* doesn't contain any dir separator, git will
311 * match these entries with any filename in any
314 filename
= git_ignore_get_basename (path
);
315 match
= (fnmatch (glob
, filename
, FNM_PATHNAME
) == 0);
322 git_ignore_path_matches (const gchar
*path
,
324 const gchar
*relative_path
)
326 gboolean match
= FALSE
;
334 while (!match
&& n_glob
< array
->len
) {
335 glob
= g_ptr_array_index (array
, n_glob
);
338 match
= git_ignore_path_matches_glob (path
, glob
, relative_path
);
345 giggle_git_ignore_path_matches (GiggleGitIgnore
*git_ignore
,
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
)) {
358 if (git_ignore_path_matches (path
, priv
->global_globs
, NULL
)) {
366 giggle_git_ignore_add_glob (GiggleGitIgnore
*git_ignore
,
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
);
381 giggle_git_ignore_add_glob_for_path (GiggleGitIgnore
*git_ignore
,
384 GiggleGitIgnorePriv
*priv
;
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
);
401 giggle_git_ignore_remove_glob_for_path (GiggleGitIgnore
*git_ignore
,
403 gboolean perfect_match
)
405 GiggleGitIgnorePriv
*priv
;
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) ||
422 git_ignore_path_matches_glob (path
, glob
, priv
->relative_path
))) {
423 g_ptr_array_remove_index (priv
->globs
, i
);
426 /* no match, increment index */
432 git_ignore_save_file (git_ignore
);