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
)
14 git_attr_file
*attrs
= NULL
;
16 attrs
= git__calloc(1, sizeof(git_attr_file
));
17 GITERR_CHECK_ALLOC(attrs
);
19 if (git_vector_init(&attrs
->rules
, 4, NULL
) < 0) {
26 return attrs
? 0 : -1;
29 int git_attr_file__set_path(
30 git_repository
*repo
, const char *path
, git_attr_file
*file
)
32 if (file
->path
!= NULL
) {
33 git__free(file
->path
);
38 file
->path
= git__strdup(path
);
40 const char *workdir
= git_repository_workdir(repo
);
42 if (workdir
&& git__prefixcmp(path
, workdir
) == 0)
43 file
->path
= git__strdup(path
+ strlen(workdir
));
45 file
->path
= git__strdup(path
);
48 GITERR_CHECK_ALLOC(file
->path
);
53 int git_attr_file__from_buffer(
54 git_repository
*repo
, const char *buffer
, git_attr_file
*attrs
)
57 const char *scan
= NULL
;
59 git_attr_rule
*rule
= NULL
;
61 assert(buffer
&& attrs
);
65 if (attrs
->path
&& git__suffixcmp(attrs
->path
, GIT_ATTR_FILE
) == 0) {
66 context
= git__strndup(attrs
->path
,
67 strlen(attrs
->path
) - strlen(GIT_ATTR_FILE
));
68 GITERR_CHECK_ALLOC(context
);
71 while (!error
&& *scan
) {
72 /* allocate rule if needed */
73 if (!rule
&& !(rule
= git__calloc(1, sizeof(git_attr_rule
)))) {
78 /* parse the next "pattern attr attr attr" line */
79 if (!(error
= git_attr_fnmatch__parse(&rule
->match
, context
, &scan
)) &&
80 !(error
= git_attr_assignment__parse(repo
, &rule
->assigns
, &scan
)))
82 if (rule
->match
.flags
& GIT_ATTR_FNMATCH_MACRO
)
83 /* should generate error/warning if this is coming from any
84 * file other than .gitattributes at repo root.
86 error
= git_attr_cache__insert_macro(repo
, rule
);
88 error
= git_vector_insert(&attrs
->rules
, rule
);
91 /* if the rule wasn't a pattern, on to the next */
93 git_attr_rule__clear(rule
); /* reset rule contents */
94 if (error
== GIT_ENOTFOUND
)
97 rule
= NULL
; /* vector now "owns" the rule */
101 git_attr_rule__free(rule
);
107 int git_attr_file__from_file(
108 git_repository
*repo
, const char *path
, git_attr_file
*file
)
111 git_buf fbuf
= GIT_BUF_INIT
;
113 assert(path
&& file
);
115 if (file
->path
== NULL
&& git_attr_file__set_path(repo
, path
, file
) < 0)
118 if (git_futils_readbuffer(&fbuf
, path
) < 0)
121 error
= git_attr_file__from_buffer(repo
, fbuf
.ptr
, file
);
128 void git_attr_file__free(git_attr_file
*file
)
136 git_vector_foreach(&file
->rules
, i
, rule
)
137 git_attr_rule__free(rule
);
139 git_vector_free(&file
->rules
);
141 git__free(file
->path
);
147 unsigned long git_attr_file__name_hash(const char *name
)
149 unsigned long h
= 5381;
152 while ((c
= (int)*name
++) != 0)
153 h
= ((h
<< 5) + h
) + c
;
158 int git_attr_file__lookup_one(
160 const git_attr_path
*path
,
171 name
.name_hash
= git_attr_file__name_hash(attr
);
173 git_attr_file__foreach_matching_rule(file
, path
, i
, rule
) {
174 int pos
= git_vector_bsearch(&rule
->assigns
, &name
);
177 *value
= ((git_attr_assignment
*)
178 git_vector_get(&rule
->assigns
, pos
))->value
;
187 bool git_attr_fnmatch__match(
188 git_attr_fnmatch
*match
,
189 const git_attr_path
*path
)
193 if (match
->flags
& GIT_ATTR_FNMATCH_DIRECTORY
&& !path
->is_dir
)
196 if (match
->flags
& GIT_ATTR_FNMATCH_FULLPATH
)
197 fnm
= p_fnmatch(match
->pattern
, path
->path
, FNM_PATHNAME
);
198 else if (path
->is_dir
)
199 fnm
= p_fnmatch(match
->pattern
, path
->basename
, FNM_LEADING_DIR
);
201 fnm
= p_fnmatch(match
->pattern
, path
->basename
, 0);
203 return (fnm
== FNM_NOMATCH
) ? false : true;
206 bool git_attr_rule__match(
208 const git_attr_path
*path
)
210 bool matched
= git_attr_fnmatch__match(&rule
->match
, path
);
212 if (rule
->match
.flags
& GIT_ATTR_FNMATCH_NEGATIVE
)
219 git_attr_assignment
*git_attr_rule__lookup_assignment(
220 git_attr_rule
*rule
, const char *name
)
225 key
.name_hash
= git_attr_file__name_hash(name
);
227 pos
= git_vector_bsearch(&rule
->assigns
, &key
);
229 return (pos
>= 0) ? git_vector_get(&rule
->assigns
, pos
) : NULL
;
232 int git_attr_path__init(
233 git_attr_path
*info
, const char *path
, const char *base
)
235 assert(info
&& path
);
237 info
->basename
= strrchr(path
, '/');
240 if (!info
->basename
|| !*info
->basename
)
241 info
->basename
= path
;
243 if (base
!= NULL
&& git_path_root(path
) < 0) {
244 git_buf full_path
= GIT_BUF_INIT
;
245 if (git_buf_joinpath(&full_path
, base
, path
) < 0)
247 info
->is_dir
= (int)git_path_isdir(full_path
.ptr
);
248 git_buf_free(&full_path
);
251 info
->is_dir
= (int)git_path_isdir(path
);
258 * From gitattributes(5):
260 * Patterns have the following format:
262 * - A blank line matches no files, so it can serve as a separator for
265 * - A line starting with # serves as a comment.
267 * - An optional prefix ! which negates the pattern; any matching file
268 * excluded by a previous pattern will become included again. If a negated
269 * pattern matches, this will override lower precedence patterns sources.
271 * - If the pattern ends with a slash, it is removed for the purpose of the
272 * following description, but it would only find a match with a directory. In
273 * other words, foo/ will match a directory foo and paths underneath it, but
274 * will not match a regular file or a symbolic link foo (this is consistent
275 * with the way how pathspec works in general in git).
277 * - If the pattern does not contain a slash /, git treats it as a shell glob
278 * pattern and checks for a match against the pathname without leading
281 * - Otherwise, git treats the pattern as a shell glob suitable for consumption
282 * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
283 * not match a / in the pathname. For example, "Documentation/\*.html" matches
284 * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
285 * slash matches the beginning of the pathname; for example, "/\*.c" matches
286 * "cat-file.c" but not "mozilla-sha1/sha1.c".
290 * This will return 0 if the spec was filled out,
291 * GIT_ENOTFOUND if the fnmatch does not require matching, or
292 * another error code there was an actual problem.
294 int git_attr_fnmatch__parse(
295 git_attr_fnmatch
*spec
,
299 const char *pattern
, *scan
;
302 assert(spec
&& base
&& *base
);
306 while (isspace(*pattern
)) pattern
++;
307 if (!*pattern
|| *pattern
== '#') {
308 *base
= git__next_line(pattern
);
309 return GIT_ENOTFOUND
;
314 if (*pattern
== '[') {
315 if (strncmp(pattern
, "[attr]", 6) == 0) {
316 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_MACRO
;
319 /* else a character range like [a-e]* which is accepted */
322 if (*pattern
== '!') {
323 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_NEGATIVE
;
328 for (scan
= pattern
; *scan
!= '\0'; ++scan
) {
329 /* scan until (non-escaped) white space */
330 if (isspace(*scan
) && *(scan
- 1) != '\\')
334 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_FULLPATH
;
337 /* remember if we see an unescaped wildcard in pattern */
338 else if ((*scan
== '*' || *scan
== '.' || *scan
== '[') &&
339 (scan
== pattern
|| (*(scan
- 1) != '\\')))
340 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_HASWILD
;
345 spec
->length
= scan
- pattern
;
347 if (pattern
[spec
->length
- 1] == '/') {
349 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_DIRECTORY
;
350 if (--slash_count
<= 0)
351 spec
->flags
= spec
->flags
& ~GIT_ATTR_FNMATCH_FULLPATH
;
354 if ((spec
->flags
& GIT_ATTR_FNMATCH_FULLPATH
) != 0 &&
355 source
!= NULL
&& git_path_root(pattern
) < 0)
357 size_t sourcelen
= strlen(source
);
358 /* given an unrooted fullpath match from a file inside a repo,
359 * prefix the pattern with the relative directory of the source file
361 spec
->pattern
= git__malloc(sourcelen
+ spec
->length
+ 1);
363 memcpy(spec
->pattern
, source
, sourcelen
);
364 memcpy(spec
->pattern
+ sourcelen
, pattern
, spec
->length
);
365 spec
->length
+= sourcelen
;
366 spec
->pattern
[spec
->length
] = '\0';
369 spec
->pattern
= git__strndup(pattern
, spec
->length
);
372 if (!spec
->pattern
) {
373 *base
= git__next_line(pattern
);
376 /* strip '\' that might have be used for internal whitespace */
377 char *to
= spec
->pattern
;
378 for (scan
= spec
->pattern
; *scan
; to
++, scan
++) {
380 scan
++; /* skip '\' but include next char */
386 spec
->length
= (to
- spec
->pattern
);
393 static int sort_by_hash_and_name(const void *a_raw
, const void *b_raw
)
395 const git_attr_name
*a
= a_raw
;
396 const git_attr_name
*b
= b_raw
;
398 if (b
->name_hash
< a
->name_hash
)
400 else if (b
->name_hash
> a
->name_hash
)
403 return strcmp(b
->name
, a
->name
);
406 static void git_attr_assignment__free(git_attr_assignment
*assign
)
408 git__free(assign
->name
);
411 if (assign
->is_allocated
) {
412 git__free((void *)assign
->value
);
413 assign
->value
= NULL
;
419 static int merge_assignments(void **old_raw
, void *new_raw
)
421 git_attr_assignment
**old
= (git_attr_assignment
**)old_raw
;
422 git_attr_assignment
*new = (git_attr_assignment
*)new_raw
;
424 GIT_REFCOUNT_DEC(*old
, git_attr_assignment__free
);
429 int git_attr_assignment__parse(
430 git_repository
*repo
,
435 const char *scan
= *base
;
436 git_attr_assignment
*assign
= NULL
;
438 assert(assigns
&& !assigns
->length
);
440 assigns
->_cmp
= sort_by_hash_and_name
;
442 while (*scan
&& *scan
!= '\n') {
443 const char *name_start
, *value_start
;
445 /* skip leading blanks */
446 while (isspace(*scan
) && *scan
!= '\n') scan
++;
448 /* allocate assign if needed */
450 assign
= git__calloc(1, sizeof(git_attr_assignment
));
451 GITERR_CHECK_ALLOC(assign
);
452 GIT_REFCOUNT_INC(assign
);
455 assign
->name_hash
= 5381;
456 assign
->value
= git_attr__true
;
457 assign
->is_allocated
= 0;
459 /* look for magic name prefixes */
461 assign
->value
= git_attr__false
;
463 } else if (*scan
== '!') {
464 assign
->value
= NULL
; /* explicit unspecified state */
466 } else if (*scan
== '#') /* comment rest of line */
471 while (*scan
&& !isspace(*scan
) && *scan
!= '=') {
473 ((assign
->name_hash
<< 5) + assign
->name_hash
) + *scan
;
476 if (scan
== name_start
) {
477 /* must have found lone prefix (" - ") or leading = ("=foo")
478 * or end of buffer -- advance until whitespace and continue
480 while (*scan
&& !isspace(*scan
)) scan
++;
484 /* allocate permanent storage for name */
485 assign
->name
= git__strndup(name_start
, scan
- name_start
);
486 GITERR_CHECK_ALLOC(assign
->name
);
488 /* if there is an equals sign, find the value */
490 for (value_start
= ++scan
; *scan
&& !isspace(*scan
); ++scan
);
492 /* if we found a value, allocate permanent storage for it */
493 if (scan
> value_start
) {
494 assign
->value
= git__strndup(value_start
, scan
- value_start
);
495 GITERR_CHECK_ALLOC(assign
->value
);
496 assign
->is_allocated
= 1;
500 /* expand macros (if given a repo with a macro cache) */
501 if (repo
!= NULL
&& assign
->value
== git_attr__true
) {
502 git_attr_rule
*macro
= git_hashtable_lookup(
503 git_repository_attr_cache(repo
)->macros
, assign
->name
);
507 git_attr_assignment
*massign
;
509 git_vector_foreach(¯o
->assigns
, i
, massign
) {
510 GIT_REFCOUNT_INC(massign
);
512 error
= git_vector_insert_sorted(
513 assigns
, massign
, &merge_assignments
);
514 if (error
< 0 && error
!= GIT_EEXISTS
)
520 /* insert allocated assign into vector */
521 error
= git_vector_insert_sorted(assigns
, assign
, &merge_assignments
);
522 if (error
< 0 && error
!= GIT_EEXISTS
)
525 /* clear assign since it is now "owned" by the vector */
530 git_attr_assignment__free(assign
);
532 *base
= git__next_line(scan
);
534 return (assigns
->length
== 0) ? GIT_ENOTFOUND
: 0;
537 static void git_attr_rule__clear(git_attr_rule
*rule
)
540 git_attr_assignment
*assign
;
545 if (!(rule
->match
.flags
& GIT_ATTR_FNMATCH_IGNORE
)) {
546 git_vector_foreach(&rule
->assigns
, i
, assign
)
547 GIT_REFCOUNT_DEC(assign
, git_attr_assignment__free
);
548 git_vector_free(&rule
->assigns
);
551 git__free(rule
->match
.pattern
);
552 rule
->match
.pattern
= NULL
;
553 rule
->match
.length
= 0;
556 void git_attr_rule__free(git_attr_rule
*rule
)
558 git_attr_rule__clear(rule
);