2 #include "repository.h"
6 const char *git_attr__true
= "[internal]__TRUE__";
7 const char *git_attr__false
= "[internal]__FALSE__";
9 static int sort_by_hash_and_name(const void *a_raw
, const void *b_raw
);
10 static void git_attr_rule__clear(git_attr_rule
*rule
);
12 int git_attr_file__new(git_attr_file
**attrs_ptr
)
15 git_attr_file
*attrs
= NULL
;
17 attrs
= git__calloc(1, sizeof(git_attr_file
));
21 error
= git_vector_init(&attrs
->rules
, 4, NULL
);
23 if (error
!= GIT_SUCCESS
) {
24 git__rethrow(error
, "Could not allocate attribute storage");
34 int git_attr_file__set_path(
35 git_repository
*repo
, const char *path
, git_attr_file
*file
)
37 if (file
->path
!= NULL
) {
38 git__free(file
->path
);
43 file
->path
= git__strdup(path
);
45 const char *workdir
= git_repository_workdir(repo
);
47 if (workdir
&& git__prefixcmp(path
, workdir
) == 0)
48 file
->path
= git__strdup(path
+ strlen(workdir
));
50 file
->path
= git__strdup(path
);
53 return (file
->path
== NULL
) ? GIT_ENOMEM
: GIT_SUCCESS
;
56 int git_attr_file__from_buffer(
57 git_repository
*repo
, const char *buffer
, git_attr_file
*attrs
)
59 int error
= GIT_SUCCESS
;
60 const char *scan
= NULL
;
62 git_attr_rule
*rule
= NULL
;
64 assert(buffer
&& attrs
);
68 if (attrs
->path
&& git__suffixcmp(attrs
->path
, GIT_ATTR_FILE
) == 0) {
69 context
= git__strndup(attrs
->path
,
70 strlen(attrs
->path
) - strlen(GIT_ATTR_FILE
));
71 if (!context
) error
= GIT_ENOMEM
;
74 while (error
== GIT_SUCCESS
&& *scan
) {
75 /* allocate rule if needed */
76 if (!rule
&& !(rule
= git__calloc(1, sizeof(git_attr_rule
)))) {
81 /* parse the next "pattern attr attr attr" line */
82 if (!(error
= git_attr_fnmatch__parse(&rule
->match
, context
, &scan
)) &&
83 !(error
= git_attr_assignment__parse(repo
, &rule
->assigns
, &scan
)))
85 if (rule
->match
.flags
& GIT_ATTR_FNMATCH_MACRO
)
86 /* should generate error/warning if this is coming from any
87 * file other than .gitattributes at repo root.
89 error
= git_attr_cache__insert_macro(repo
, rule
);
91 error
= git_vector_insert(&attrs
->rules
, rule
);
94 /* if the rule wasn't a pattern, on to the next */
95 if (error
!= GIT_SUCCESS
) {
96 git_attr_rule__clear(rule
); /* reset rule contents */
97 if (error
== GIT_ENOTFOUND
)
100 rule
= NULL
; /* vector now "owns" the rule */
104 git_attr_rule__free(rule
);
110 int git_attr_file__from_file(
111 git_repository
*repo
, const char *path
, git_attr_file
*file
)
113 int error
= GIT_SUCCESS
;
114 git_fbuffer fbuf
= GIT_FBUFFER_INIT
;
116 assert(path
&& file
);
118 if (file
->path
== NULL
)
119 error
= git_attr_file__set_path(repo
, path
, file
);
121 if (error
== GIT_SUCCESS
&&
122 (error
= git_futils_readbuffer(&fbuf
, path
)) == GIT_SUCCESS
)
123 error
= git_attr_file__from_buffer(repo
, fbuf
.data
, file
);
125 git_futils_freebuffer(&fbuf
);
126 if (error
!= GIT_SUCCESS
)
127 git__rethrow(error
, "Could not open attribute file '%s'", path
);
132 void git_attr_file__free(git_attr_file
*file
)
140 git_vector_foreach(&file
->rules
, i
, rule
)
141 git_attr_rule__free(rule
);
143 git_vector_free(&file
->rules
);
145 git__free(file
->path
);
151 unsigned long git_attr_file__name_hash(const char *name
)
153 unsigned long h
= 5381;
156 while ((c
= (int)*name
++) != 0)
157 h
= ((h
<< 5) + h
) + c
;
162 int git_attr_file__lookup_one(
164 const git_attr_path
*path
,
175 name
.name_hash
= git_attr_file__name_hash(attr
);
177 git_attr_file__foreach_matching_rule(file
, path
, i
, rule
) {
178 int pos
= git_vector_bsearch(&rule
->assigns
, &name
);
179 git_clearerror(); /* okay if search failed */
182 *value
= ((git_attr_assignment
*)
183 git_vector_get(&rule
->assigns
, pos
))->value
;
192 int git_attr_fnmatch__match(
193 git_attr_fnmatch
*match
,
194 const git_attr_path
*path
)
196 int matched
= FNM_NOMATCH
;
198 if (match
->flags
& GIT_ATTR_FNMATCH_DIRECTORY
&& !path
->is_dir
)
201 if (match
->flags
& GIT_ATTR_FNMATCH_FULLPATH
)
202 matched
= p_fnmatch(match
->pattern
, path
->path
, FNM_PATHNAME
);
203 else if (path
->is_dir
)
204 matched
= p_fnmatch(match
->pattern
, path
->basename
, FNM_LEADING_DIR
);
206 matched
= p_fnmatch(match
->pattern
, path
->basename
, 0);
211 int git_attr_rule__match(
213 const git_attr_path
*path
)
215 int matched
= git_attr_fnmatch__match(&rule
->match
, path
);
217 if (rule
->match
.flags
& GIT_ATTR_FNMATCH_NEGATIVE
)
218 matched
= (matched
== GIT_SUCCESS
) ? FNM_NOMATCH
: GIT_SUCCESS
;
224 git_attr_assignment
*git_attr_rule__lookup_assignment(
225 git_attr_rule
*rule
, const char *name
)
230 key
.name_hash
= git_attr_file__name_hash(name
);
232 pos
= git_vector_bsearch(&rule
->assigns
, &key
);
233 git_clearerror(); /* okay if search failed */
235 return (pos
>= 0) ? git_vector_get(&rule
->assigns
, pos
) : NULL
;
238 int git_attr_path__init(
239 git_attr_path
*info
, const char *path
, const char *base
)
241 assert(info
&& path
);
243 info
->basename
= strrchr(path
, '/');
246 if (!info
->basename
|| !*info
->basename
)
247 info
->basename
= path
;
249 if (base
!= NULL
&& git_path_root(path
) < 0) {
250 git_buf full_path
= GIT_BUF_INIT
;
251 int error
= git_buf_joinpath(&full_path
, base
, path
);
252 if (error
== GIT_SUCCESS
)
253 info
->is_dir
= (git_path_isdir(full_path
.ptr
) == GIT_SUCCESS
);
254 git_buf_free(&full_path
);
257 info
->is_dir
= (git_path_isdir(path
) == GIT_SUCCESS
);
264 * From gitattributes(5):
266 * Patterns have the following format:
268 * - A blank line matches no files, so it can serve as a separator for
271 * - A line starting with # serves as a comment.
273 * - An optional prefix ! which negates the pattern; any matching file
274 * excluded by a previous pattern will become included again. If a negated
275 * pattern matches, this will override lower precedence patterns sources.
277 * - If the pattern ends with a slash, it is removed for the purpose of the
278 * following description, but it would only find a match with a directory. In
279 * other words, foo/ will match a directory foo and paths underneath it, but
280 * will not match a regular file or a symbolic link foo (this is consistent
281 * with the way how pathspec works in general in git).
283 * - If the pattern does not contain a slash /, git treats it as a shell glob
284 * pattern and checks for a match against the pathname without leading
287 * - Otherwise, git treats the pattern as a shell glob suitable for consumption
288 * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
289 * not match a / in the pathname. For example, "Documentation/\*.html" matches
290 * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
291 * slash matches the beginning of the pathname; for example, "/\*.c" matches
292 * "cat-file.c" but not "mozilla-sha1/sha1.c".
296 * This will return GIT_SUCCESS if the spec was filled out,
297 * GIT_ENOTFOUND if the fnmatch does not require matching, or
298 * another error code there was an actual problem.
300 int git_attr_fnmatch__parse(
301 git_attr_fnmatch
*spec
,
305 const char *pattern
, *scan
;
308 assert(spec
&& base
&& *base
);
312 while (isspace(*pattern
)) pattern
++;
313 if (!*pattern
|| *pattern
== '#') {
314 *base
= git__next_line(pattern
);
315 return GIT_ENOTFOUND
;
320 if (*pattern
== '[') {
321 if (strncmp(pattern
, "[attr]", 6) == 0) {
322 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_MACRO
;
325 /* else a character range like [a-e]* which is accepted */
328 if (*pattern
== '!') {
329 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_NEGATIVE
;
334 for (scan
= pattern
; *scan
!= '\0'; ++scan
) {
335 /* scan until (non-escaped) white space */
336 if (isspace(*scan
) && *(scan
- 1) != '\\')
340 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_FULLPATH
;
347 spec
->length
= scan
- pattern
;
349 if (pattern
[spec
->length
- 1] == '/') {
351 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_DIRECTORY
;
352 if (--slash_count
<= 0)
353 spec
->flags
= spec
->flags
& ~GIT_ATTR_FNMATCH_FULLPATH
;
356 if ((spec
->flags
& GIT_ATTR_FNMATCH_FULLPATH
) != 0 &&
357 source
!= NULL
&& git_path_root(pattern
) < 0)
359 size_t sourcelen
= strlen(source
);
360 /* given an unrooted fullpath match from a file inside a repo,
361 * prefix the pattern with the relative directory of the source file
363 spec
->pattern
= git__malloc(sourcelen
+ spec
->length
+ 1);
365 memcpy(spec
->pattern
, source
, sourcelen
);
366 memcpy(spec
->pattern
+ sourcelen
, pattern
, spec
->length
);
367 spec
->length
+= sourcelen
;
368 spec
->pattern
[spec
->length
] = '\0';
371 spec
->pattern
= git__strndup(pattern
, spec
->length
);
374 if (!spec
->pattern
) {
375 *base
= git__next_line(pattern
);
378 /* strip '\' that might have be used for internal whitespace */
379 char *to
= spec
->pattern
;
380 for (scan
= spec
->pattern
; *scan
; to
++, scan
++) {
382 scan
++; /* skip '\' but include next char */
388 spec
->length
= (to
- spec
->pattern
);
395 static int sort_by_hash_and_name(const void *a_raw
, const void *b_raw
)
397 const git_attr_name
*a
= a_raw
;
398 const git_attr_name
*b
= b_raw
;
400 if (b
->name_hash
< a
->name_hash
)
402 else if (b
->name_hash
> a
->name_hash
)
405 return strcmp(b
->name
, a
->name
);
408 static void git_attr_assignment__free(git_attr_assignment
*assign
)
410 git__free(assign
->name
);
413 if (assign
->is_allocated
) {
414 git__free((void *)assign
->value
);
415 assign
->value
= NULL
;
421 static int merge_assignments(void **old_raw
, void *new_raw
)
423 git_attr_assignment
**old
= (git_attr_assignment
**)old_raw
;
424 git_attr_assignment
*new = (git_attr_assignment
*)new_raw
;
426 GIT_REFCOUNT_DEC(*old
, git_attr_assignment__free
);
431 int git_attr_assignment__parse(
432 git_repository
*repo
,
436 int error
= GIT_SUCCESS
;
437 const char *scan
= *base
;
438 git_attr_assignment
*assign
= NULL
;
440 assert(assigns
&& !assigns
->length
);
442 assigns
->_cmp
= sort_by_hash_and_name
;
444 while (*scan
&& *scan
!= '\n' && error
== GIT_SUCCESS
) {
445 const char *name_start
, *value_start
;
447 /* skip leading blanks */
448 while (isspace(*scan
) && *scan
!= '\n') scan
++;
450 /* allocate assign if needed */
452 assign
= git__calloc(1, sizeof(git_attr_assignment
));
457 GIT_REFCOUNT_INC(assign
);
460 assign
->name_hash
= 5381;
461 assign
->value
= GIT_ATTR_TRUE
;
462 assign
->is_allocated
= 0;
464 /* look for magic name prefixes */
466 assign
->value
= GIT_ATTR_FALSE
;
468 } else if (*scan
== '!') {
469 assign
->value
= NULL
; /* explicit unspecified state */
471 } else if (*scan
== '#') /* comment rest of line */
476 while (*scan
&& !isspace(*scan
) && *scan
!= '=') {
478 ((assign
->name_hash
<< 5) + assign
->name_hash
) + *scan
;
481 if (scan
== name_start
) {
482 /* must have found lone prefix (" - ") or leading = ("=foo")
483 * or end of buffer -- advance until whitespace and continue
485 while (*scan
&& !isspace(*scan
)) scan
++;
489 /* allocate permanent storage for name */
490 assign
->name
= git__strndup(name_start
, scan
- name_start
);
496 /* if there is an equals sign, find the value */
498 for (value_start
= ++scan
; *scan
&& !isspace(*scan
); ++scan
);
500 /* if we found a value, allocate permanent storage for it */
501 if (scan
> value_start
) {
502 assign
->value
= git__strndup(value_start
, scan
- value_start
);
503 if (!assign
->value
) {
507 assign
->is_allocated
= 1;
512 /* expand macros (if given a repo with a macro cache) */
513 if (repo
!= NULL
&& assign
->value
== GIT_ATTR_TRUE
) {
514 git_attr_rule
*macro
=
515 git_hashtable_lookup(repo
->attrcache
.macros
, assign
->name
);
519 git_attr_assignment
*massign
;
521 git_vector_foreach(¯o
->assigns
, i
, massign
) {
522 GIT_REFCOUNT_INC(massign
);
524 error
= git_vector_insert_sorted(
525 assigns
, massign
, &merge_assignments
);
527 if (error
== GIT_EEXISTS
)
529 else if (error
!= GIT_SUCCESS
)
535 /* insert allocated assign into vector */
536 error
= git_vector_insert_sorted(assigns
, assign
, &merge_assignments
);
537 if (error
== GIT_EEXISTS
)
539 else if (error
< GIT_SUCCESS
)
542 /* clear assign since it is now "owned" by the vector */
546 if (!assigns
->length
)
547 error
= git__throw(GIT_ENOTFOUND
, "No attribute assignments found for rule");
550 git_attr_assignment__free(assign
);
552 *base
= git__next_line(scan
);
557 static void git_attr_rule__clear(git_attr_rule
*rule
)
560 git_attr_assignment
*assign
;
565 if (!(rule
->match
.flags
& GIT_ATTR_FNMATCH_IGNORE
)) {
566 git_vector_foreach(&rule
->assigns
, i
, assign
)
567 GIT_REFCOUNT_DEC(assign
, git_attr_assignment__free
);
568 git_vector_free(&rule
->assigns
);
571 git__free(rule
->match
.pattern
);
572 rule
->match
.pattern
= NULL
;
573 rule
->match
.length
= 0;
576 void git_attr_rule__free(git_attr_rule
*rule
)
578 git_attr_rule__clear(rule
);