2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
10 #include "repository.h"
12 #include "attrcache.h"
13 #include "git2/blob.h"
14 #include "git2/tree.h"
17 #include "wildmatch.h"
20 static void attr_file_free(git_attr_file
*file
)
22 bool unlock
= !git_mutex_lock(&file
->lock
);
23 git_attr_file__clear_rules(file
, false);
24 git_pool_clear(&file
->pool
);
26 git_mutex_unlock(&file
->lock
);
27 git_mutex_free(&file
->lock
);
29 git__memzero(file
, sizeof(*file
));
33 int git_attr_file__new(
35 git_attr_file_entry
*entry
,
36 git_attr_file_source
*source
)
38 git_attr_file
*attrs
= git__calloc(1, sizeof(git_attr_file
));
39 GIT_ERROR_CHECK_ALLOC(attrs
);
41 if (git_mutex_init(&attrs
->lock
) < 0) {
42 git_error_set(GIT_ERROR_OS
, "failed to initialize lock");
46 if (git_pool_init(&attrs
->pool
, 1) < 0)
49 GIT_REFCOUNT_INC(attrs
);
51 memcpy(&attrs
->source
, source
, sizeof(git_attr_file_source
));
60 int git_attr_file__clear_rules(git_attr_file
*file
, bool need_lock
)
65 if (need_lock
&& git_mutex_lock(&file
->lock
) < 0) {
66 git_error_set(GIT_ERROR_OS
, "failed to lock attribute file");
70 git_vector_foreach(&file
->rules
, i
, rule
)
71 git_attr_rule__free(rule
);
72 git_vector_free(&file
->rules
);
75 git_mutex_unlock(&file
->lock
);
80 void git_attr_file__free(git_attr_file
*file
)
84 GIT_REFCOUNT_DEC(file
, attr_file_free
);
87 static int attr_file_oid_from_index(
88 git_oid
*oid
, git_repository
*repo
, const char *path
)
93 const git_index_entry
*entry
;
95 if ((error
= git_repository_index__weakptr(&idx
, repo
)) < 0 ||
96 (error
= git_index__find_pos(&pos
, idx
, path
, 0, 0)) < 0)
99 if (!(entry
= git_index_get_byindex(idx
, pos
)))
100 return GIT_ENOTFOUND
;
106 int git_attr_file__load(
108 git_repository
*repo
,
109 git_attr_session
*attr_session
,
110 git_attr_file_entry
*entry
,
111 git_attr_file_source
*source
,
112 git_attr_file_parser parser
,
116 git_commit
*commit
= NULL
;
117 git_tree
*tree
= NULL
;
118 git_tree_entry
*tree_entry
= NULL
;
119 git_blob
*blob
= NULL
;
120 git_buf content
= GIT_BUF_INIT
;
121 const char *content_str
;
124 bool nonexistent
= false;
128 git_object_size_t blobsize
;
132 switch (source
->type
) {
133 case GIT_ATTR_FILE_SOURCE_MEMORY
:
134 /* in-memory attribute file doesn't need data */
136 case GIT_ATTR_FILE_SOURCE_INDEX
: {
137 if ((error
= attr_file_oid_from_index(&id
, repo
, entry
->path
)) < 0 ||
138 (error
= git_blob_lookup(&blob
, repo
, &id
)) < 0)
141 /* Do not assume that data straight from the ODB is NULL-terminated;
142 * copy the contents of a file to a buffer to work on */
143 blobsize
= git_blob_rawsize(blob
);
145 GIT_ERROR_CHECK_BLOBSIZE(blobsize
);
146 git_buf_put(&content
, git_blob_rawcontent(blob
), (size_t)blobsize
);
149 case GIT_ATTR_FILE_SOURCE_FILE
: {
152 /* For open or read errors, pretend that we got ENOTFOUND. */
153 /* TODO: issue warning when warning API is available */
155 if (p_stat(entry
->fullpath
, &st
) < 0 ||
156 S_ISDIR(st
.st_mode
) ||
157 (fd
= git_futils_open_ro(entry
->fullpath
)) < 0 ||
158 (error
= git_futils_readbuffer_fd(&content
, fd
, (size_t)st
.st_size
)) < 0)
166 case GIT_ATTR_FILE_SOURCE_HEAD
:
167 case GIT_ATTR_FILE_SOURCE_COMMIT
: {
168 if (source
->type
== GIT_ATTR_FILE_SOURCE_COMMIT
) {
169 if ((error
= git_commit_lookup(&commit
, repo
, source
->commit_id
)) < 0 ||
170 (error
= git_commit_tree(&tree
, commit
)) < 0)
173 if ((error
= git_repository_head_tree(&tree
, repo
)) < 0)
177 if ((error
= git_tree_entry_bypath(&tree_entry
, tree
, entry
->path
)) < 0) {
179 * If the attributes file does not exist, we can
180 * cache an empty file for this commit to prevent
181 * needless future lookups.
183 if (error
== GIT_ENOTFOUND
) {
191 if ((error
= git_blob_lookup(&blob
, repo
, git_tree_entry_id(tree_entry
))) < 0)
195 * Do not assume that data straight from the ODB is NULL-terminated;
196 * copy the contents of a file to a buffer to work on.
198 blobsize
= git_blob_rawsize(blob
);
200 GIT_ERROR_CHECK_BLOBSIZE(blobsize
);
201 if ((error
= git_buf_put(&content
,
202 git_blob_rawcontent(blob
), (size_t)blobsize
)) < 0)
208 git_error_set(GIT_ERROR_INVALID
, "unknown file source %d", source
->type
);
212 if ((error
= git_attr_file__new(&file
, entry
, source
)) < 0)
215 /* advance over a UTF8 BOM */
216 content_str
= git_buf_cstr(&content
);
217 bom_offset
= git_buf_detect_bom(&bom
, &content
);
219 if (bom
== GIT_BUF_BOM_UTF8
)
220 content_str
+= bom_offset
;
222 /* store the key of the attr_reader; don't bother with cache
223 * invalidation during the same attr reader session.
226 file
->session_key
= attr_session
->key
;
228 if (parser
&& (error
= parser(repo
, file
, content_str
, allow_macros
)) < 0) {
229 git_attr_file__free(file
);
233 /* write cache breakers */
235 file
->nonexistent
= 1;
236 else if (source
->type
== GIT_ATTR_FILE_SOURCE_INDEX
)
237 git_oid_cpy(&file
->cache_data
.oid
, git_blob_id(blob
));
238 else if (source
->type
== GIT_ATTR_FILE_SOURCE_HEAD
)
239 git_oid_cpy(&file
->cache_data
.oid
, git_tree_id(tree
));
240 else if (source
->type
== GIT_ATTR_FILE_SOURCE_COMMIT
)
241 git_oid_cpy(&file
->cache_data
.oid
, git_tree_id(tree
));
242 else if (source
->type
== GIT_ATTR_FILE_SOURCE_FILE
)
243 git_futils_filestamp_set_from_stat(&file
->cache_data
.stamp
, &st
);
244 /* else always cacheable */
250 git_tree_entry_free(tree_entry
);
252 git_commit_free(commit
);
253 git_buf_dispose(&content
);
258 int git_attr_file__out_of_date(
259 git_repository
*repo
,
260 git_attr_session
*attr_session
,
262 git_attr_file_source
*source
)
267 /* we are never out of date if we just created this data in the same
268 * attr_session; otherwise, nonexistent files must be invalidated
270 if (attr_session
&& attr_session
->key
== file
->session_key
)
272 else if (file
->nonexistent
)
275 switch (file
->source
.type
) {
276 case GIT_ATTR_FILE_SOURCE_MEMORY
:
279 case GIT_ATTR_FILE_SOURCE_FILE
:
280 return git_futils_filestamp_check(
281 &file
->cache_data
.stamp
, file
->entry
->fullpath
);
283 case GIT_ATTR_FILE_SOURCE_INDEX
: {
287 if ((error
= attr_file_oid_from_index(
288 &id
, repo
, file
->entry
->path
)) < 0)
291 return (git_oid__cmp(&file
->cache_data
.oid
, &id
) != 0);
294 case GIT_ATTR_FILE_SOURCE_HEAD
: {
295 git_tree
*tree
= NULL
;
296 int error
= git_repository_head_tree(&tree
, repo
);
301 error
= (git_oid__cmp(&file
->cache_data
.oid
, git_tree_id(tree
)) != 0);
307 case GIT_ATTR_FILE_SOURCE_COMMIT
: {
308 git_commit
*commit
= NULL
;
309 git_tree
*tree
= NULL
;
312 if ((error
= git_commit_lookup(&commit
, repo
, source
->commit_id
)) < 0)
315 error
= git_commit_tree(&tree
, commit
);
316 git_commit_free(commit
);
321 error
= (git_oid__cmp(&file
->cache_data
.oid
, git_tree_id(tree
)) != 0);
328 git_error_set(GIT_ERROR_INVALID
, "invalid file type %d", file
->source
.type
);
333 static int sort_by_hash_and_name(const void *a_raw
, const void *b_raw
);
334 static void git_attr_rule__clear(git_attr_rule
*rule
);
335 static bool parse_optimized_patterns(
336 git_attr_fnmatch
*spec
,
338 const char *pattern
);
340 int git_attr_file__parse_buffer(
341 git_repository
*repo
, git_attr_file
*attrs
, const char *data
, bool allow_macros
)
343 const char *scan
= data
, *context
= NULL
;
344 git_attr_rule
*rule
= NULL
;
347 /* If subdir file path, convert context for file paths */
348 if (attrs
->entry
&& git_path_root(attrs
->entry
->path
) < 0 &&
349 !git__suffixcmp(attrs
->entry
->path
, "/" GIT_ATTR_FILE
))
350 context
= attrs
->entry
->path
;
352 if (git_mutex_lock(&attrs
->lock
) < 0) {
353 git_error_set(GIT_ERROR_OS
, "failed to lock attribute file");
357 while (!error
&& *scan
) {
358 /* Allocate rule if needed, otherwise re-use previous rule */
360 rule
= git__calloc(1, sizeof(*rule
));
361 GIT_ERROR_CHECK_ALLOC(rule
);
363 git_attr_rule__clear(rule
);
365 rule
->match
.flags
= GIT_ATTR_FNMATCH_ALLOWNEG
| GIT_ATTR_FNMATCH_ALLOWMACRO
;
367 /* Parse the next "pattern attr attr attr" line */
368 if ((error
= git_attr_fnmatch__parse(&rule
->match
, &attrs
->pool
, context
, &scan
)) < 0 ||
369 (error
= git_attr_assignment__parse(repo
, &attrs
->pool
, &rule
->assigns
, &scan
)) < 0)
371 if (error
!= GIT_ENOTFOUND
)
377 if (rule
->match
.flags
& GIT_ATTR_FNMATCH_MACRO
) {
378 /* TODO: warning if macro found in file below repo root */
381 if ((error
= git_attr_cache__insert_macro(repo
, rule
)) < 0)
383 } else if ((error
= git_vector_insert(&attrs
->rules
, rule
)) < 0)
390 git_mutex_unlock(&attrs
->lock
);
391 git_attr_rule__free(rule
);
396 uint32_t git_attr_file__name_hash(const char *name
)
401 GIT_ASSERT_ARG(name
);
403 while ((c
= (int)*name
++) != 0)
404 h
= ((h
<< 5) + h
) + c
;
408 int git_attr_file__lookup_one(
421 name
.name_hash
= git_attr_file__name_hash(attr
);
423 git_attr_file__foreach_matching_rule(file
, path
, i
, rule
) {
426 if (!git_vector_bsearch(&pos
, &rule
->assigns
, &name
)) {
427 *value
= ((git_attr_assignment
*)
428 git_vector_get(&rule
->assigns
, pos
))->value
;
436 int git_attr_file__load_standalone(git_attr_file
**out
, const char *path
)
438 git_buf content
= GIT_BUF_INIT
;
439 git_attr_file_source source
= { GIT_ATTR_FILE_SOURCE_FILE
};
440 git_attr_file
*file
= NULL
;
443 if ((error
= git_futils_readbuffer(&content
, path
)) < 0)
447 * Because the cache entry is allocated from the file's own pool, we
448 * don't have to free it - freeing file+pool will free cache entry, too.
451 if ((error
= git_attr_file__new(&file
, NULL
, &source
)) < 0 ||
452 (error
= git_attr_file__parse_buffer(NULL
, file
, content
.ptr
, true)) < 0 ||
453 (error
= git_attr_cache__alloc_file_entry(&file
->entry
, NULL
, NULL
, path
, &file
->pool
)) < 0)
459 git_attr_file__free(file
);
460 git_buf_dispose(&content
);
465 bool git_attr_fnmatch__match(
466 git_attr_fnmatch
*match
,
469 const char *relpath
= path
->path
;
470 const char *filename
;
474 * If the rule was generated in a subdirectory, we must only
475 * use it for paths inside that directory. We can thus return
476 * a non-match if the prefixes don't match.
478 if (match
->containing_dir
) {
479 if (match
->flags
& GIT_ATTR_FNMATCH_ICASE
) {
480 if (git__strncasecmp(path
->path
, match
->containing_dir
, match
->containing_dir_length
))
483 if (git__prefixcmp(path
->path
, match
->containing_dir
))
487 relpath
+= match
->containing_dir_length
;
490 if (match
->flags
& GIT_ATTR_FNMATCH_ICASE
)
491 flags
|= WM_CASEFOLD
;
493 if (match
->flags
& GIT_ATTR_FNMATCH_FULLPATH
) {
495 flags
|= WM_PATHNAME
;
497 filename
= path
->basename
;
500 if ((match
->flags
& GIT_ATTR_FNMATCH_DIRECTORY
) && !path
->is_dir
) {
504 * for attribute checks or checks at the root of this match's
505 * containing_dir (or root of the repository if no containing_dir),
508 if (!(match
->flags
& GIT_ATTR_FNMATCH_IGNORE
) ||
509 path
->basename
== relpath
)
512 /* fail match if this is a file with same name as ignored folder */
513 samename
= (match
->flags
& GIT_ATTR_FNMATCH_ICASE
) ?
514 !strcasecmp(match
->pattern
, relpath
) :
515 !strcmp(match
->pattern
, relpath
);
520 return (wildmatch(match
->pattern
, relpath
, flags
) == WM_MATCH
);
523 return (wildmatch(match
->pattern
, filename
, flags
) == WM_MATCH
);
526 bool git_attr_rule__match(
530 bool matched
= git_attr_fnmatch__match(&rule
->match
, path
);
532 if (rule
->match
.flags
& GIT_ATTR_FNMATCH_NEGATIVE
)
538 git_attr_assignment
*git_attr_rule__lookup_assignment(
539 git_attr_rule
*rule
, const char *name
)
544 key
.name_hash
= git_attr_file__name_hash(name
);
546 if (git_vector_bsearch(&pos
, &rule
->assigns
, &key
))
549 return git_vector_get(&rule
->assigns
, pos
);
552 int git_attr_path__init(
556 git_dir_flag dir_flag
)
560 /* build full path as best we can */
561 git_buf_init(&info
->full
, 0);
563 if (git_path_join_unrooted(&info
->full
, path
, base
, &root
) < 0)
566 info
->path
= info
->full
.ptr
+ root
;
568 /* remove trailing slashes */
569 while (info
->full
.size
> 0) {
570 if (info
->full
.ptr
[info
->full
.size
- 1] != '/')
574 info
->full
.ptr
[info
->full
.size
] = '\0';
576 /* skip leading slashes in path */
577 while (*info
->path
== '/')
580 /* find trailing basename component */
581 info
->basename
= strrchr(info
->path
, '/');
584 if (!info
->basename
|| !*info
->basename
)
585 info
->basename
= info
->path
;
589 case GIT_DIR_FLAG_FALSE
:
593 case GIT_DIR_FLAG_TRUE
:
597 case GIT_DIR_FLAG_UNKNOWN
:
599 info
->is_dir
= (int)git_path_isdir(info
->full
.ptr
);
606 void git_attr_path__free(git_attr_path
*info
)
608 git_buf_dispose(&info
->full
);
610 info
->basename
= NULL
;
614 * From gitattributes(5):
616 * Patterns have the following format:
618 * - A blank line matches no files, so it can serve as a separator for
621 * - A line starting with # serves as a comment.
623 * - An optional prefix ! which negates the pattern; any matching file
624 * excluded by a previous pattern will become included again. If a negated
625 * pattern matches, this will override lower precedence patterns sources.
627 * - If the pattern ends with a slash, it is removed for the purpose of the
628 * following description, but it would only find a match with a directory. In
629 * other words, foo/ will match a directory foo and paths underneath it, but
630 * will not match a regular file or a symbolic link foo (this is consistent
631 * with the way how pathspec works in general in git).
633 * - If the pattern does not contain a slash /, git treats it as a shell glob
634 * pattern and checks for a match against the pathname without leading
637 * - Otherwise, git treats the pattern as a shell glob suitable for consumption
638 * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
639 * not match a / in the pathname. For example, "Documentation/\*.html" matches
640 * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
641 * slash matches the beginning of the pathname; for example, "/\*.c" matches
642 * "cat-file.c" but not "mozilla-sha1/sha1.c".
646 * Determine the length of trailing spaces. Escaped spaces do not count as
647 * trailing whitespace.
649 static size_t trailing_space_length(const char *p
, size_t len
)
652 for (n
= len
; n
; n
--) {
653 if (p
[n
-1] != ' ' && p
[n
-1] != '\t')
657 * Count escape-characters before space. In case where it's an
658 * even number of escape characters, then the escape char itself
659 * is escaped and the whitespace is an unescaped whitespace.
660 * Otherwise, the last escape char is not escaped and the
661 * whitespace in an escaped whitespace.
664 while (i
> 1 && p
[i
-2] == '\\')
672 static size_t unescape_spaces(char *str
)
674 char *scan
, *pos
= str
;
675 bool escaped
= false;
680 for (scan
= str
; *scan
; scan
++) {
681 if (!escaped
&& *scan
== '\\') {
686 /* Only insert the escape character for escaped non-spaces */
687 if (escaped
&& !git__isspace(*scan
))
701 * This will return 0 if the spec was filled out,
702 * GIT_ENOTFOUND if the fnmatch does not require matching, or
703 * another error code there was an actual problem.
705 int git_attr_fnmatch__parse(
706 git_attr_fnmatch
*spec
,
711 const char *pattern
, *scan
;
712 int slash_count
, allow_space
;
715 GIT_ASSERT_ARG(spec
);
716 GIT_ASSERT_ARG(base
&& *base
);
718 if (parse_optimized_patterns(spec
, pool
, *base
))
721 spec
->flags
= (spec
->flags
& GIT_ATTR_FNMATCH__INCOMING
);
722 allow_space
= ((spec
->flags
& GIT_ATTR_FNMATCH_ALLOWSPACE
) != 0);
726 while (!allow_space
&& git__isspace(*pattern
))
729 if (!*pattern
|| *pattern
== '#' || *pattern
== '\n' ||
730 (*pattern
== '\r' && *(pattern
+ 1) == '\n')) {
731 *base
= git__next_line(pattern
);
732 return GIT_ENOTFOUND
;
735 if (*pattern
== '[' && (spec
->flags
& GIT_ATTR_FNMATCH_ALLOWMACRO
) != 0) {
736 if (strncmp(pattern
, "[attr]", 6) == 0) {
737 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_MACRO
;
740 /* else a character range like [a-e]* which is accepted */
743 if (*pattern
== '!' && (spec
->flags
& GIT_ATTR_FNMATCH_ALLOWNEG
) != 0) {
744 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_NEGATIVE
;
750 /* Scan until a non-escaped whitespace. */
751 for (scan
= pattern
; *scan
!= '\0'; ++scan
) {
754 if (c
== '\\' && !escaped
) {
757 } else if (git__isspace(c
) && !escaped
) {
758 if (!allow_space
|| (c
!= ' ' && c
!= '\t' && c
!= '\r'))
760 } else if (c
== '/') {
761 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_FULLPATH
;
764 if (slash_count
== 1 && pattern
== scan
)
766 } else if (git__iswildcard(c
) && !escaped
) {
767 /* remember if we see an unescaped wildcard in pattern */
768 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_HASWILD
;
776 if ((spec
->length
= scan
- pattern
) == 0)
777 return GIT_ENOTFOUND
;
780 * Remove one trailing \r in case this is a CRLF delimited
781 * file, in the case of Icon\r\r\n, we still leave the first
782 * \r there to match against.
784 if (pattern
[spec
->length
- 1] == '\r')
785 if (--spec
->length
== 0)
786 return GIT_ENOTFOUND
;
788 /* Remove trailing spaces. */
789 spec
->length
-= trailing_space_length(pattern
, spec
->length
);
791 if (spec
->length
== 0)
792 return GIT_ENOTFOUND
;
794 if (pattern
[spec
->length
- 1] == '/') {
796 spec
->flags
= spec
->flags
| GIT_ATTR_FNMATCH_DIRECTORY
;
797 if (--slash_count
<= 0)
798 spec
->flags
= spec
->flags
& ~GIT_ATTR_FNMATCH_FULLPATH
;
802 char *slash
= strrchr(context
, '/');
805 /* include the slash for easier matching */
806 len
= slash
- context
+ 1;
807 spec
->containing_dir
= git_pool_strndup(pool
, context
, len
);
808 spec
->containing_dir_length
= len
;
812 spec
->pattern
= git_pool_strndup(pool
, pattern
, spec
->length
);
814 if (!spec
->pattern
) {
815 *base
= git__next_line(pattern
);
818 /* strip '\' that might have been used for internal whitespace */
819 spec
->length
= unescape_spaces(spec
->pattern
);
825 static bool parse_optimized_patterns(
826 git_attr_fnmatch
*spec
,
830 if (!pattern
[1] && (pattern
[0] == '*' || pattern
[0] == '.')) {
831 spec
->flags
= GIT_ATTR_FNMATCH_MATCH_ALL
;
832 spec
->pattern
= git_pool_strndup(pool
, pattern
, 1);
841 static int sort_by_hash_and_name(const void *a_raw
, const void *b_raw
)
843 const git_attr_name
*a
= a_raw
;
844 const git_attr_name
*b
= b_raw
;
846 if (b
->name_hash
< a
->name_hash
)
848 else if (b
->name_hash
> a
->name_hash
)
851 return strcmp(b
->name
, a
->name
);
854 static void git_attr_assignment__free(git_attr_assignment
*assign
)
856 /* name and value are stored in a git_pool associated with the
857 * git_attr_file, so they do not need to be freed here
860 assign
->value
= NULL
;
864 static int merge_assignments(void **old_raw
, void *new_raw
)
866 git_attr_assignment
**old
= (git_attr_assignment
**)old_raw
;
867 git_attr_assignment
*new = (git_attr_assignment
*)new_raw
;
869 GIT_REFCOUNT_DEC(*old
, git_attr_assignment__free
);
874 int git_attr_assignment__parse(
875 git_repository
*repo
,
881 const char *scan
= *base
;
882 git_attr_assignment
*assign
= NULL
;
884 GIT_ASSERT_ARG(assigns
&& !assigns
->length
);
886 git_vector_set_cmp(assigns
, sort_by_hash_and_name
);
888 while (*scan
&& *scan
!= '\n') {
889 const char *name_start
, *value_start
;
891 /* skip leading blanks */
892 while (git__isspace(*scan
) && *scan
!= '\n') scan
++;
894 /* allocate assign if needed */
896 assign
= git__calloc(1, sizeof(git_attr_assignment
));
897 GIT_ERROR_CHECK_ALLOC(assign
);
898 GIT_REFCOUNT_INC(assign
);
901 assign
->name_hash
= 5381;
902 assign
->value
= git_attr__true
;
904 /* look for magic name prefixes */
906 assign
->value
= git_attr__false
;
908 } else if (*scan
== '!') {
909 assign
->value
= git_attr__unset
; /* explicit unspecified state */
911 } else if (*scan
== '#') /* comment rest of line */
916 while (*scan
&& !git__isspace(*scan
) && *scan
!= '=') {
918 ((assign
->name_hash
<< 5) + assign
->name_hash
) + *scan
;
921 if (scan
== name_start
) {
922 /* must have found lone prefix (" - ") or leading = ("=foo")
923 * or end of buffer -- advance until whitespace and continue
925 while (*scan
&& !git__isspace(*scan
)) scan
++;
929 /* allocate permanent storage for name */
930 assign
->name
= git_pool_strndup(pool
, name_start
, scan
- name_start
);
931 GIT_ERROR_CHECK_ALLOC(assign
->name
);
933 /* if there is an equals sign, find the value */
935 for (value_start
= ++scan
; *scan
&& !git__isspace(*scan
); ++scan
);
937 /* if we found a value, allocate permanent storage for it */
938 if (scan
> value_start
) {
939 assign
->value
= git_pool_strndup(pool
, value_start
, scan
- value_start
);
940 GIT_ERROR_CHECK_ALLOC(assign
->value
);
944 /* expand macros (if given a repo with a macro cache) */
945 if (repo
!= NULL
&& assign
->value
== git_attr__true
) {
946 git_attr_rule
*macro
=
947 git_attr_cache__lookup_macro(repo
, assign
->name
);
951 git_attr_assignment
*massign
;
953 git_vector_foreach(¯o
->assigns
, i
, massign
) {
954 GIT_REFCOUNT_INC(massign
);
956 error
= git_vector_insert_sorted(
957 assigns
, massign
, &merge_assignments
);
958 if (error
< 0 && error
!= GIT_EEXISTS
) {
959 git_attr_assignment__free(assign
);
966 /* insert allocated assign into vector */
967 error
= git_vector_insert_sorted(assigns
, assign
, &merge_assignments
);
968 if (error
< 0 && error
!= GIT_EEXISTS
)
971 /* clear assign since it is now "owned" by the vector */
976 git_attr_assignment__free(assign
);
978 *base
= git__next_line(scan
);
980 return (assigns
->length
== 0) ? GIT_ENOTFOUND
: 0;
983 static void git_attr_rule__clear(git_attr_rule
*rule
)
986 git_attr_assignment
*assign
;
991 if (!(rule
->match
.flags
& GIT_ATTR_FNMATCH_IGNORE
)) {
992 git_vector_foreach(&rule
->assigns
, i
, assign
)
993 GIT_REFCOUNT_DEC(assign
, git_attr_assignment__free
);
994 git_vector_free(&rule
->assigns
);
997 /* match.pattern is stored in a git_pool, so no need to free */
998 rule
->match
.pattern
= NULL
;
999 rule
->match
.length
= 0;
1002 void git_attr_rule__free(git_attr_rule
*rule
)
1004 git_attr_rule__clear(rule
);
1008 int git_attr_session__init(git_attr_session
*session
, git_repository
*repo
)
1010 GIT_ASSERT_ARG(repo
);
1012 memset(session
, 0, sizeof(*session
));
1013 session
->key
= git_atomic32_inc(&repo
->attr_session_key
);
1018 void git_attr_session__free(git_attr_session
*session
)
1023 git_buf_dispose(&session
->sysdir
);
1024 git_buf_dispose(&session
->tmp
);
1026 memset(session
, 0, sizeof(git_attr_session
));