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
, git_pool
*pool
)
14 git_attr_file
*attrs
= NULL
;
16 attrs
= git__calloc(1, sizeof(git_attr_file
));
17 GITERR_CHECK_ALLOC(attrs
);
22 attrs
->pool
= git__calloc(1, sizeof(git_pool
));
23 if (!attrs
->pool
|| git_pool_init(attrs
->pool
, 1, 0) < 0)
25 attrs
->pool_is_allocated
= true;
28 if (git_vector_init(&attrs
->rules
, 4, NULL
) < 0)
35 git_attr_file__free(attrs
);
40 int git_attr_file__set_path(
41 git_repository
*repo
, const char *path
, git_attr_file
*file
)
43 if (file
->path
!= NULL
) {
44 git__free(file
->path
);
49 file
->path
= git__strdup(path
);
51 const char *workdir
= git_repository_workdir(repo
);
53 if (workdir
&& git__prefixcmp(path
, workdir
) == 0)
54 file
->path
= git__strdup(path
+ strlen(workdir
));
56 file
->path
= git__strdup(path
);
59 GITERR_CHECK_ALLOC(file
->path
);
64 int git_attr_file__from_buffer(
65 git_repository
*repo
, const char *buffer
, git_attr_file
*attrs
)
68 const char *scan
= NULL
;
70 git_attr_rule
*rule
= NULL
;
72 assert(buffer
&& attrs
);
76 if (attrs
->path
&& git__suffixcmp(attrs
->path
, GIT_ATTR_FILE
) == 0) {
77 context
= git__strndup(attrs
->path
,
78 strlen(attrs
->path
) - strlen(GIT_ATTR_FILE
));
79 GITERR_CHECK_ALLOC(context
);
82 while (!error
&& *scan
) {
83 /* allocate rule if needed */
84 if (!rule
&& !(rule
= git__calloc(1, sizeof(git_attr_rule
)))) {
89 /* parse the next "pattern attr attr attr" line */
90 if (!(error
= git_attr_fnmatch__parse(
91 &rule
->match
, attrs
->pool
, context
, &scan
)) &&
92 !(error
= git_attr_assignment__parse(
93 repo
, attrs
->pool
, &rule
->assigns
, &scan
)))
95 if (rule
->match
.flags
& GIT_ATTR_FNMATCH_MACRO
)
96 /* should generate error/warning if this is coming from any
97 * file other than .gitattributes at repo root.
99 error
= git_attr_cache__insert_macro(repo
, rule
);
101 error
= git_vector_insert(&attrs
->rules
, rule
);
104 /* if the rule wasn't a pattern, on to the next */
106 git_attr_rule__clear(rule
); /* reset rule contents */
107 if (error
== GIT_ENOTFOUND
)
110 rule
= NULL
; /* vector now "owns" the rule */
114 git_attr_rule__free(rule
);
120 int git_attr_file__from_file(
121 git_repository
*repo
, const char *path
, git_attr_file
*file
)
124 git_buf fbuf
= GIT_BUF_INIT
;
126 assert(path
&& file
);
128 if (file
->path
== NULL
&& git_attr_file__set_path(repo
, path
, file
) < 0)
131 if (git_futils_readbuffer(&fbuf
, path
) < 0)
134 error
= git_attr_file__from_buffer(repo
, fbuf
.ptr
, file
);
141 void git_attr_file__free(git_attr_file
*file
)
149 git_vector_foreach(&file
->rules
, i
, rule
)
150 git_attr_rule__free(rule
);
152 git_vector_free(&file
->rules
);
154 git__free(file
->path
);
157 if (file
->pool_is_allocated
) {
158 git_pool_clear(file
->pool
);
159 git__free(file
->pool
);
166 uint32_t git_attr_file__name_hash(const char *name
)
171 while ((c
= (int)*name
++) != 0)
172 h
= ((h
<< 5) + h
) + c
;
177 int git_attr_file__lookup_one(
179 const git_attr_path
*path
,
190 name
.name_hash
= git_attr_file__name_hash(attr
);
192 git_attr_file__foreach_matching_rule(file
, path
, i
, rule
) {
193 int pos
= git_vector_bsearch(&rule
->assigns
, &name
);
196 *value
= ((git_attr_assignment
*)
197 git_vector_get(&rule
->assigns
, pos
))->value
;
206 bool git_attr_fnmatch__match(
207 git_attr_fnmatch
*match
,
208 const git_attr_path
*path
)
212 if (match
->flags
& GIT_ATTR_FNMATCH_DIRECTORY
&& !path
->is_dir
)
215 if (match
->flags
& GIT_ATTR_FNMATCH_FULLPATH
)
216 fnm
= p_fnmatch(match
->pattern
, path
->path
, FNM_PATHNAME
);
217 else if (path
->is_dir
)
218 fnm
= p_fnmatch(match
->pattern
, path
->basename
, FNM_LEADING_DIR
);
220 fnm
= p_fnmatch(match
->pattern
, path
->basename
, 0);
222 return (fnm
== FNM_NOMATCH
) ? false : true;
225 bool git_attr_rule__match(
227 const git_attr_path
*path
)
229 bool matched
= git_attr_fnmatch__match(&rule
->match
, path
);
231 if (rule
->match
.flags
& GIT_ATTR_FNMATCH_NEGATIVE
)
238 git_attr_assignment
*git_attr_rule__lookup_assignment(
239 git_attr_rule
*rule
, const char *name
)
244 key
.name_hash
= git_attr_file__name_hash(name
);
246 pos
= git_vector_bsearch(&rule
->assigns
, &key
);
248 return (pos
>= 0) ? git_vector_get(&rule
->assigns
, pos
) : NULL
;
251 int git_attr_path__init(
252 git_attr_path
*info
, const char *path
, const char *base
)
254 assert(info
&& path
);
256 info
->basename
= strrchr(path
, '/');
259 if (!info
->basename
|| !*info
->basename
)
260 info
->basename
= path
;
262 if (base
!= NULL
&& git_path_root(path
) < 0) {
263 git_buf full_path
= GIT_BUF_INIT
;
264 if (git_buf_joinpath(&full_path
, base
, path
) < 0)
266 info
->is_dir
= (int)git_path_isdir(full_path
.ptr
);
267 git_buf_free(&full_path
);
270 info
->is_dir
= (int)git_path_isdir(path
);
277 * From gitattributes(5):
279 * Patterns have the following format:
281 * - A blank line matches no files, so it can serve as a separator for
284 * - A line starting with # serves as a comment.
286 * - An optional prefix ! which negates the pattern; any matching file
287 * excluded by a previous pattern will become included again. If a negated
288 * pattern matches, this will override lower precedence patterns sources.
290 * - If the pattern ends with a slash, it is removed for the purpose of the
291 * following description, but it would only find a match with a directory. In
292 * other words, foo/ will match a directory foo and paths underneath it, but
293 * will not match a regular file or a symbolic link foo (this is consistent
294 * with the way how pathspec works in general in git).
296 * - If the pattern does not contain a slash /, git treats it as a shell glob
297 * pattern and checks for a match against the pathname without leading
300 * - Otherwise, git treats the pattern as a shell glob suitable for consumption
301 * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
302 * not match a / in the pathname. For example, "Documentation/\*.html" matches
303 * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
304 * slash matches the beginning of the pathname; for example, "/\*.c" matches
305 * "cat-file.c" but not "mozilla-sha1/sha1.c".
309 * This will return 0 if the spec was filled out,
310 * GIT_ENOTFOUND if the fnmatch does not require matching, or
311 * another error code there was an actual problem.
313 int git_attr_fnmatch__parse(
314 git_attr_fnmatch
*spec
,
319 const char *pattern
, *scan
;
322 assert(spec
&& base
&& *base
);
326 while (isspace(*pattern
)) pattern
++;
327 if (!*pattern
|| *pattern
== '#') {
328 *base
= git__next_line(pattern
);
329 return GIT_ENOTFOUND
;
334 if (*pattern
== '[') {
335 if (strncmp(pattern
, "[attr]", 6) == 0) {
336 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_MACRO
;
339 /* else a character range like [a-e]* which is accepted */
342 if (*pattern
== '!') {
343 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_NEGATIVE
;
348 for (scan
= pattern
; *scan
!= '\0'; ++scan
) {
349 /* scan until (non-escaped) white space */
350 if (isspace(*scan
) && *(scan
- 1) != '\\')
354 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_FULLPATH
;
357 /* remember if we see an unescaped wildcard in pattern */
358 else if ((*scan
== '*' || *scan
== '.' || *scan
== '[') &&
359 (scan
== pattern
|| (*(scan
- 1) != '\\')))
360 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_HASWILD
;
365 spec
->length
= scan
- pattern
;
367 if (pattern
[spec
->length
- 1] == '/') {
369 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_DIRECTORY
;
370 if (--slash_count
<= 0)
371 spec
->flags
= spec
->flags
& ~GIT_ATTR_FNMATCH_FULLPATH
;
374 if ((spec
->flags
& GIT_ATTR_FNMATCH_FULLPATH
) != 0 &&
375 source
!= NULL
&& git_path_root(pattern
) < 0)
377 size_t sourcelen
= strlen(source
);
378 /* given an unrooted fullpath match from a file inside a repo,
379 * prefix the pattern with the relative directory of the source file
381 spec
->pattern
= git_pool_malloc(pool
, sourcelen
+ spec
->length
+ 1);
383 memcpy(spec
->pattern
, source
, sourcelen
);
384 memcpy(spec
->pattern
+ sourcelen
, pattern
, spec
->length
);
385 spec
->length
+= sourcelen
;
386 spec
->pattern
[spec
->length
] = '\0';
389 spec
->pattern
= git_pool_strndup(pool
, pattern
, spec
->length
);
392 if (!spec
->pattern
) {
393 *base
= git__next_line(pattern
);
396 /* strip '\' that might have be used for internal whitespace */
397 char *to
= spec
->pattern
;
398 for (scan
= spec
->pattern
; *scan
; to
++, scan
++) {
400 scan
++; /* skip '\' but include next char */
406 spec
->length
= (to
- spec
->pattern
);
413 static int sort_by_hash_and_name(const void *a_raw
, const void *b_raw
)
415 const git_attr_name
*a
= a_raw
;
416 const git_attr_name
*b
= b_raw
;
418 if (b
->name_hash
< a
->name_hash
)
420 else if (b
->name_hash
> a
->name_hash
)
423 return strcmp(b
->name
, a
->name
);
426 static void git_attr_assignment__free(git_attr_assignment
*assign
)
428 /* name and value are stored in a git_pool associated with the
429 * git_attr_file, so they do not need to be freed here
432 assign
->value
= NULL
;
436 static int merge_assignments(void **old_raw
, void *new_raw
)
438 git_attr_assignment
**old
= (git_attr_assignment
**)old_raw
;
439 git_attr_assignment
*new = (git_attr_assignment
*)new_raw
;
441 GIT_REFCOUNT_DEC(*old
, git_attr_assignment__free
);
446 int git_attr_assignment__parse(
447 git_repository
*repo
,
453 const char *scan
= *base
;
454 git_attr_assignment
*assign
= NULL
;
456 assert(assigns
&& !assigns
->length
);
458 assigns
->_cmp
= sort_by_hash_and_name
;
460 while (*scan
&& *scan
!= '\n') {
461 const char *name_start
, *value_start
;
463 /* skip leading blanks */
464 while (isspace(*scan
) && *scan
!= '\n') scan
++;
466 /* allocate assign if needed */
468 assign
= git__calloc(1, sizeof(git_attr_assignment
));
469 GITERR_CHECK_ALLOC(assign
);
470 GIT_REFCOUNT_INC(assign
);
473 assign
->name_hash
= 5381;
474 assign
->value
= git_attr__true
;
476 /* look for magic name prefixes */
478 assign
->value
= git_attr__false
;
480 } else if (*scan
== '!') {
481 assign
->value
= NULL
; /* explicit unspecified state */
483 } else if (*scan
== '#') /* comment rest of line */
488 while (*scan
&& !isspace(*scan
) && *scan
!= '=') {
490 ((assign
->name_hash
<< 5) + assign
->name_hash
) + *scan
;
493 if (scan
== name_start
) {
494 /* must have found lone prefix (" - ") or leading = ("=foo")
495 * or end of buffer -- advance until whitespace and continue
497 while (*scan
&& !isspace(*scan
)) scan
++;
501 /* allocate permanent storage for name */
502 assign
->name
= git_pool_strndup(pool
, name_start
, scan
- name_start
);
503 GITERR_CHECK_ALLOC(assign
->name
);
505 /* if there is an equals sign, find the value */
507 for (value_start
= ++scan
; *scan
&& !isspace(*scan
); ++scan
);
509 /* if we found a value, allocate permanent storage for it */
510 if (scan
> value_start
) {
511 assign
->value
= git_pool_strndup(pool
, value_start
, scan
- value_start
);
512 GITERR_CHECK_ALLOC(assign
->value
);
516 /* expand macros (if given a repo with a macro cache) */
517 if (repo
!= NULL
&& assign
->value
== git_attr__true
) {
518 git_attr_rule
*macro
=
519 git_attr_cache__lookup_macro(repo
, assign
->name
);
523 git_attr_assignment
*massign
;
525 git_vector_foreach(¯o
->assigns
, i
, massign
) {
526 GIT_REFCOUNT_INC(massign
);
528 error
= git_vector_insert_sorted(
529 assigns
, massign
, &merge_assignments
);
530 if (error
< 0 && error
!= GIT_EEXISTS
)
536 /* insert allocated assign into vector */
537 error
= git_vector_insert_sorted(assigns
, assign
, &merge_assignments
);
538 if (error
< 0 && error
!= GIT_EEXISTS
)
541 /* clear assign since it is now "owned" by the vector */
546 git_attr_assignment__free(assign
);
548 *base
= git__next_line(scan
);
550 return (assigns
->length
== 0) ? GIT_ENOTFOUND
: 0;
553 static void git_attr_rule__clear(git_attr_rule
*rule
)
556 git_attr_assignment
*assign
;
561 if (!(rule
->match
.flags
& GIT_ATTR_FNMATCH_IGNORE
)) {
562 git_vector_foreach(&rule
->assigns
, i
, assign
)
563 GIT_REFCOUNT_DEC(assign
, git_attr_assignment__free
);
564 git_vector_free(&rule
->assigns
);
567 /* match.pattern is stored in a git_pool, so no need to free */
568 rule
->match
.pattern
= NULL
;
569 rule
->match
.length
= 0;
572 void git_attr_rule__free(git_attr_rule
*rule
)
574 git_attr_rule__clear(rule
);