]> git.proxmox.com Git - libgit2.git/blame - src/attr_file.c
patch: use strlen to mean string length
[libgit2.git] / src / attr_file.c
CommitLineData
ee1f0b1a 1#include "common.h"
73b51450 2#include "repository.h"
ee1f0b1a 3#include "filebuf.h"
7d490872 4#include "attr_file.h"
823c0e9c 5#include "attrcache.h"
f917481e
RB
6#include "git2/blob.h"
7#include "git2/tree.h"
7d490872 8#include "index.h"
ee1f0b1a
RB
9#include <ctype.h>
10
7d490872
RB
11static void attr_file_free(git_attr_file *file)
12{
e6e8530a
RB
13 bool unlock = !git_mutex_lock(&file->lock);
14 git_attr_file__clear_rules(file, false);
7d490872 15 git_pool_clear(&file->pool);
e6e8530a
RB
16 if (unlock)
17 git_mutex_unlock(&file->lock);
18 git_mutex_free(&file->lock);
19
7d490872
RB
20 git__memzero(file, sizeof(*file));
21 git__free(file);
22}
ee1f0b1a 23
f917481e 24int git_attr_file__new(
7d490872 25 git_attr_file **out,
823c0e9c
RB
26 git_attr_file_entry *entry,
27 git_attr_file_source source)
73b51450 28{
7d490872 29 git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
ae9e29fd 30 GITERR_CHECK_ALLOC(attrs);
df743c7d 31
e6e8530a
RB
32 if (git_mutex_init(&attrs->lock) < 0) {
33 giterr_set(GITERR_OS, "Failed to initialize lock");
34 git__free(attrs);
35 return -1;
36 }
37
1e5e02b4 38 git_pool_init(&attrs->pool, 1);
7d490872 39 GIT_REFCOUNT_INC(attrs);
823c0e9c 40 attrs->entry = entry;
7d490872
RB
41 attrs->source = source;
42 *out = attrs;
43 return 0;
44}
f917481e 45
e6e8530a 46int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
7d490872
RB
47{
48 unsigned int i;
49 git_attr_rule *rule;
f917481e 50
e6e8530a
RB
51 if (need_lock && git_mutex_lock(&file->lock) < 0) {
52 giterr_set(GITERR_OS, "Failed to lock attribute file");
53 return -1;
54 }
55
7d490872
RB
56 git_vector_foreach(&file->rules, i, rule)
57 git_attr_rule__free(rule);
58 git_vector_free(&file->rules);
e6e8530a
RB
59
60 if (need_lock)
61 git_mutex_unlock(&file->lock);
62
63 return 0;
7d490872 64}
f917481e 65
7d490872
RB
66void git_attr_file__free(git_attr_file *file)
67{
68 if (!file)
69 return;
70 GIT_REFCOUNT_DEC(file, attr_file_free);
71}
19fa2bc1 72
7d490872
RB
73static int attr_file_oid_from_index(
74 git_oid *oid, git_repository *repo, const char *path)
75{
76 int error;
77 git_index *idx;
78 size_t pos;
79 const git_index_entry *entry;
80
81 if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
82 (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
83 return error;
84
85 if (!(entry = git_index_get_byindex(idx, pos)))
86 return GIT_ENOTFOUND;
87
88 *oid = entry->id;
19fa2bc1 89 return 0;
7d490872
RB
90}
91
92int git_attr_file__load(
93 git_attr_file **out,
94 git_repository *repo,
9f779aac 95 git_attr_session *attr_session,
823c0e9c
RB
96 git_attr_file_entry *entry,
97 git_attr_file_source source,
98 git_attr_file_parser parser)
7d490872
RB
99{
100 int error = 0;
101 git_blob *blob = NULL;
102 git_buf content = GIT_BUF_INIT;
7d490872 103 git_attr_file *file;
823c0e9c 104 struct stat st;
9f779aac 105 bool nonexistent = false;
7d490872
RB
106
107 *out = NULL;
108
823c0e9c
RB
109 switch (source) {
110 case GIT_ATTR_FILE__IN_MEMORY:
111 /* in-memory attribute file doesn't need data */
112 break;
113 case GIT_ATTR_FILE__FROM_INDEX: {
7d490872 114 git_oid id;
73b51450 115
823c0e9c 116 if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
7d490872
RB
117 (error = git_blob_lookup(&blob, repo, &id)) < 0)
118 return error;
119
72d00241
VM
120 /* Do not assume that data straight from the ODB is NULL-terminated;
121 * copy the contents of a file to a buffer to work on */
122 git_buf_put(&content, git_blob_rawcontent(blob), git_blob_rawsize(blob));
823c0e9c
RB
123 break;
124 }
125 case GIT_ATTR_FILE__FROM_FILE: {
24b8ed2b 126 int fd = -1;
823c0e9c 127
9f779aac 128 /* For open or read errors, pretend that we got ENOTFOUND. */
823c0e9c
RB
129 /* TODO: issue warning when warning API is available */
130
9f779aac
ET
131 if (p_stat(entry->fullpath, &st) < 0 ||
132 S_ISDIR(st.st_mode) ||
133 (fd = git_futils_open_ro(entry->fullpath)) < 0 ||
134 (error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0)
135 nonexistent = true;
24b8ed2b
PS
136
137 if (fd >= 0)
9f779aac 138 p_close(fd);
823c0e9c 139
823c0e9c
RB
140 break;
141 }
142 default:
143 giterr_set(GITERR_INVALID, "Unknown file source %d", source);
144 return -1;
7d490872
RB
145 }
146
823c0e9c 147 if ((error = git_attr_file__new(&file, entry, source)) < 0)
7d490872
RB
148 goto cleanup;
149
9f779aac
ET
150 /* store the key of the attr_reader; don't bother with cache
151 * invalidation during the same attr reader session.
152 */
153 if (attr_session)
154 file->session_key = attr_session->key;
155
72d00241 156 if (parser && (error = parser(repo, file, git_buf_cstr(&content))) < 0) {
7d490872 157 git_attr_file__free(file);
823c0e9c
RB
158 goto cleanup;
159 }
160
9f779aac
ET
161 /* write cache breakers */
162 if (nonexistent)
163 file->nonexistent = 1;
164 else if (source == GIT_ATTR_FILE__FROM_INDEX)
823c0e9c
RB
165 git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
166 else if (source == GIT_ATTR_FILE__FROM_FILE)
167 git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
168 /* else always cacheable */
169
170 *out = file;
7d490872
RB
171
172cleanup:
173 git_blob_free(blob);
174 git_buf_free(&content);
175
176 return error;
73b51450
RB
177}
178
9f779aac
ET
179int git_attr_file__out_of_date(
180 git_repository *repo,
181 git_attr_session *attr_session,
182 git_attr_file *file)
7d490872
RB
183{
184 if (!file)
185 return 1;
186
9f779aac
ET
187 /* we are never out of date if we just created this data in the same
188 * attr_session; otherwise, nonexistent files must be invalidated
189 */
190 if (attr_session && attr_session->key == file->session_key)
191 return 0;
192 else if (file->nonexistent)
193 return 1;
194
823c0e9c
RB
195 switch (file->source) {
196 case GIT_ATTR_FILE__IN_MEMORY:
197 return 0;
198
199 case GIT_ATTR_FILE__FROM_FILE:
200 return git_futils_filestamp_check(
201 &file->cache_data.stamp, file->entry->fullpath);
202
203 case GIT_ATTR_FILE__FROM_INDEX: {
7d490872
RB
204 int error;
205 git_oid id;
206
823c0e9c
RB
207 if ((error = attr_file_oid_from_index(
208 &id, repo, file->entry->path)) < 0)
7d490872
RB
209 return error;
210
211 return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
212 }
213
823c0e9c
RB
214 default:
215 giterr_set(GITERR_INVALID, "Invalid file type %d", file->source);
216 return -1;
217 }
7d490872
RB
218}
219
220static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
221static void git_attr_rule__clear(git_attr_rule *rule);
222static bool parse_optimized_patterns(
223 git_attr_fnmatch *spec,
224 git_pool *pool,
225 const char *pattern);
226
f917481e 227int git_attr_file__parse_buffer(
823c0e9c 228 git_repository *repo, git_attr_file *attrs, const char *data)
ee1f0b1a 229{
ae9e29fd 230 int error = 0;
7d490872 231 const char *scan = data, *context = NULL;
ee1f0b1a
RB
232 git_attr_rule *rule = NULL;
233
f917481e 234 /* if subdir file path, convert context for file paths */
823c0e9c
RB
235 if (attrs->entry &&
236 git_path_root(attrs->entry->path) < 0 &&
237 !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
238 context = attrs->entry->path;
a51cd8e6 239
e6e8530a
RB
240 if (git_mutex_lock(&attrs->lock) < 0) {
241 giterr_set(GITERR_OS, "Failed to lock attribute file");
242 return -1;
243 }
244
ae9e29fd 245 while (!error && *scan) {
ee1f0b1a 246 /* allocate rule if needed */
17ef678c
RB
247 if (!rule && !(rule = git__calloc(1, sizeof(*rule)))) {
248 error = -1;
249 break;
ee1f0b1a
RB
250 }
251
17ef678c
RB
252 rule->match.flags =
253 GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
254
ee1f0b1a 255 /* parse the next "pattern attr attr attr" line */
4ba64794 256 if (!(error = git_attr_fnmatch__parse(
7d490872 257 &rule->match, &attrs->pool, context, &scan)) &&
19fa2bc1 258 !(error = git_attr_assignment__parse(
7d490872 259 repo, &attrs->pool, &rule->assigns, &scan)))
73b51450
RB
260 {
261 if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
e3a2a04c 262 /* TODO: warning if macro found in file below repo root */
73b51450
RB
263 error = git_attr_cache__insert_macro(repo, rule);
264 else
265 error = git_vector_insert(&attrs->rules, rule);
266 }
ee1f0b1a
RB
267
268 /* if the rule wasn't a pattern, on to the next */
ae9e29fd 269 if (error < 0) {
c6d2a2c0 270 git_attr_rule__clear(rule); /* reset rule contents */
ee1f0b1a 271 if (error == GIT_ENOTFOUND)
ae9e29fd 272 error = 0;
ee1f0b1a
RB
273 } else {
274 rule = NULL; /* vector now "owns" the rule */
275 }
276 }
277
e6e8530a 278 git_mutex_unlock(&attrs->lock);
a51cd8e6 279 git_attr_rule__free(rule);
f917481e 280
ee1f0b1a
RB
281 return error;
282}
283
19fa2bc1 284uint32_t git_attr_file__name_hash(const char *name)
ee1f0b1a 285{
19fa2bc1 286 uint32_t h = 5381;
ee1f0b1a
RB
287 int c;
288 assert(name);
289 while ((c = (int)*name++) != 0)
290 h = ((h << 5) + h) + c;
291 return h;
292}
293
ee1f0b1a
RB
294int git_attr_file__lookup_one(
295 git_attr_file *file,
f554611a 296 git_attr_path *path,
ee1f0b1a
RB
297 const char *attr,
298 const char **value)
299{
b8457baa 300 size_t i;
ee1f0b1a
RB
301 git_attr_name name;
302 git_attr_rule *rule;
303
304 *value = NULL;
305
306 name.name = attr;
307 name.name_hash = git_attr_file__name_hash(attr);
308
309 git_attr_file__foreach_matching_rule(file, path, i, rule) {
11d9f6b3 310 size_t pos;
ee1f0b1a 311
11d9f6b3 312 if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
ee1f0b1a
RB
313 *value = ((git_attr_assignment *)
314 git_vector_get(&rule->assigns, pos))->value;
315 break;
316 }
317 }
318
ab43ad2f 319 return 0;
ee1f0b1a
RB
320}
321
823c0e9c 322int git_attr_file__load_standalone(git_attr_file **out, const char *path)
7d490872
RB
323{
324 int error;
325 git_attr_file *file;
326 git_buf content = GIT_BUF_INIT;
327
823c0e9c 328 error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE);
7d490872
RB
329 if (error < 0)
330 return error;
331
823c0e9c
RB
332 error = git_attr_cache__alloc_file_entry(
333 &file->entry, NULL, path, &file->pool);
7d490872
RB
334 if (error < 0) {
335 git_attr_file__free(file);
336 return error;
337 }
338 /* because the cache entry is allocated from the file's own pool, we
339 * don't have to free it - freeing file+pool will free cache entry, too.
340 */
341
342 if (!(error = git_futils_readbuffer(&content, path))) {
823c0e9c 343 error = git_attr_file__parse_buffer(NULL, file, content.ptr);
7d490872
RB
344 git_buf_free(&content);
345 }
346
347 if (error < 0)
348 git_attr_file__free(file);
349 else
350 *out = file;
351
352 return error;
353}
ee1f0b1a 354
ab43ad2f 355bool git_attr_fnmatch__match(
df743c7d 356 git_attr_fnmatch *match,
f554611a 357 git_attr_path *path)
ee1f0b1a 358{
90997e40 359 const char *relpath = path->path;
7d490872
RB
360 const char *filename;
361 int flags = 0;
ee1f0b1a 362
6069042f
CMN
363 /*
364 * If the rule was generated in a subdirectory, we must only
365 * use it for paths inside that directory. We can thus return
366 * a non-match if the prefixes don't match.
367 */
368 if (match->containing_dir) {
369 if (match->flags & GIT_ATTR_FNMATCH_ICASE) {
370 if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length))
371 return 0;
372 } else {
373 if (git__prefixcmp(path->path, match->containing_dir))
374 return 0;
375 }
90997e40
ET
376
377 relpath += match->containing_dir_length;
6069042f
CMN
378 }
379
7d490872
RB
380 if (match->flags & GIT_ATTR_FNMATCH_ICASE)
381 flags |= FNM_CASEFOLD;
916fcbd6
RB
382 if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
383 flags |= FNM_LEADING_DIR;
7d490872
RB
384
385 if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
90997e40 386 filename = relpath;
7d490872
RB
387 flags |= FNM_PATHNAME;
388 } else {
389 filename = path->basename;
390
391 if (path->is_dir)
392 flags |= FNM_LEADING_DIR;
393 }
df743c7d 394
f554611a 395 if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
34593aae
ET
396 bool samename;
397
f554611a
RB
398 /* for attribute checks or root ignore checks, fail match */
399 if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
400 path->basename == path->path)
401 return false;
402
f554611a 403 flags |= FNM_LEADING_DIR;
c02a0e46 404
ef6d0722 405 /* fail match if this is a file with same name as ignored folder */
34593aae 406 samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ?
90997e40
ET
407 !strcasecmp(match->pattern, relpath) :
408 !strcmp(match->pattern, relpath);
ef6d0722
ET
409
410 if (samename)
411 return false;
412
90997e40 413 return (p_fnmatch(match->pattern, relpath, flags) != FNM_NOMATCH);
f554611a
RB
414 }
415
f25bc0b2
RB
416 /* if path is a directory prefix of a negated pattern, then match */
417 if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) && path->is_dir) {
90997e40 418 size_t pathlen = strlen(relpath);
f25bc0b2
RB
419 bool prefixed = (pathlen <= match->length) &&
420 ((match->flags & GIT_ATTR_FNMATCH_ICASE) ?
90997e40
ET
421 !strncasecmp(match->pattern, relpath, pathlen) :
422 !strncmp(match->pattern, relpath, pathlen));
f25bc0b2
RB
423
424 if (prefixed && git_path_at_end_of_segment(&match->pattern[pathlen]))
425 return true;
426 }
427
7d490872 428 return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
df743c7d
RB
429}
430
ab43ad2f 431bool git_attr_rule__match(
df743c7d 432 git_attr_rule *rule,
f554611a 433 git_attr_path *path)
df743c7d 434{
ab43ad2f 435 bool matched = git_attr_fnmatch__match(&rule->match, path);
ee1f0b1a 436
73b51450 437 if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
ab43ad2f 438 matched = !matched;
ee1f0b1a
RB
439
440 return matched;
441}
442
443git_attr_assignment *git_attr_rule__lookup_assignment(
444 git_attr_rule *rule, const char *name)
445{
11d9f6b3 446 size_t pos;
ee1f0b1a
RB
447 git_attr_name key;
448 key.name = name;
449 key.name_hash = git_attr_file__name_hash(name);
450
11d9f6b3
PK
451 if (git_vector_bsearch(&pos, &rule->assigns, &key))
452 return NULL;
ee1f0b1a 453
11d9f6b3 454 return git_vector_get(&rule->assigns, pos);
ee1f0b1a
RB
455}
456
457int git_attr_path__init(
4c09e19a 458 git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
ee1f0b1a 459{
ca1b6e54
RB
460 ssize_t root;
461
d58336dd
RB
462 /* build full path as best we can */
463 git_buf_init(&info->full, 0);
adc9bdb3 464
ca1b6e54
RB
465 if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
466 return -1;
467
468 info->path = info->full.ptr + root;
d58336dd
RB
469
470 /* remove trailing slashes */
471 while (info->full.size > 0) {
472 if (info->full.ptr[info->full.size - 1] != '/')
473 break;
474 info->full.size--;
475 }
476 info->full.ptr[info->full.size] = '\0';
477
478 /* skip leading slashes in path */
479 while (*info->path == '/')
480 info->path++;
481
482 /* find trailing basename component */
483 info->basename = strrchr(info->path, '/');
484 if (info->basename)
485 info->basename++;
486 if (!info->basename || !*info->basename)
487 info->basename = info->path;
488
4c09e19a
W
489 switch (dir_flag)
490 {
491 case GIT_DIR_FLAG_FALSE:
492 info->is_dir = 0;
493 break;
494
495 case GIT_DIR_FLAG_TRUE:
496 info->is_dir = 1;
497 break;
498
499 case GIT_DIR_FLAG_UNKNOWN:
500 default:
501 info->is_dir = (int)git_path_isdir(info->full.ptr);
502 break;
503 }
adc9bdb3 504
ae9e29fd 505 return 0;
ee1f0b1a
RB
506}
507
d58336dd
RB
508void git_attr_path__free(git_attr_path *info)
509{
510 git_buf_free(&info->full);
511 info->path = NULL;
512 info->basename = NULL;
513}
514
ee1f0b1a
RB
515/*
516 * From gitattributes(5):
517 *
518 * Patterns have the following format:
519 *
520 * - A blank line matches no files, so it can serve as a separator for
521 * readability.
522 *
523 * - A line starting with # serves as a comment.
524 *
525 * - An optional prefix ! which negates the pattern; any matching file
526 * excluded by a previous pattern will become included again. If a negated
527 * pattern matches, this will override lower precedence patterns sources.
528 *
529 * - If the pattern ends with a slash, it is removed for the purpose of the
530 * following description, but it would only find a match with a directory. In
531 * other words, foo/ will match a directory foo and paths underneath it, but
532 * will not match a regular file or a symbolic link foo (this is consistent
533 * with the way how pathspec works in general in git).
534 *
535 * - If the pattern does not contain a slash /, git treats it as a shell glob
536 * pattern and checks for a match against the pathname without leading
537 * directories.
538 *
539 * - Otherwise, git treats the pattern as a shell glob suitable for consumption
540 * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
541 * not match a / in the pathname. For example, "Documentation/\*.html" matches
542 * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
543 * slash matches the beginning of the pathname; for example, "/\*.c" matches
544 * "cat-file.c" but not "mozilla-sha1/sha1.c".
545 */
546
547/*
0d0fa7c3 548 * This will return 0 if the spec was filled out,
ee1f0b1a
RB
549 * GIT_ENOTFOUND if the fnmatch does not require matching, or
550 * another error code there was an actual problem.
551 */
4ba64794 552int git_attr_fnmatch__parse(
ee1f0b1a 553 git_attr_fnmatch *spec,
19fa2bc1 554 git_pool *pool,
7d490872 555 const char *context,
ee1f0b1a
RB
556 const char **base)
557{
4ba64794
RB
558 const char *pattern, *scan;
559 int slash_count, allow_space;
ee1f0b1a 560
df743c7d 561 assert(spec && base && *base);
ee1f0b1a 562
4ba64794
RB
563 if (parse_optimized_patterns(spec, pool, *base))
564 return 0;
565
566 spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
567 allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
568
ee1f0b1a
RB
569 pattern = *base;
570
0f49200c 571 while (git__isspace(*pattern)) pattern++;
ee1f0b1a 572 if (!*pattern || *pattern == '#') {
df743c7d
RB
573 *base = git__next_line(pattern);
574 return GIT_ENOTFOUND;
ee1f0b1a
RB
575 }
576
4ba64794 577 if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
73b51450
RB
578 if (strncmp(pattern, "[attr]", 6) == 0) {
579 spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
580 pattern += 6;
73b51450 581 }
df743c7d 582 /* else a character range like [a-e]* which is accepted */
73b51450
RB
583 }
584
4ba64794 585 if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
a0cacc82
RB
586 spec->flags = spec->flags |
587 GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
ee1f0b1a 588 pattern++;
ee1f0b1a
RB
589 }
590
ee1f0b1a
RB
591 slash_count = 0;
592 for (scan = pattern; *scan != '\0'; ++scan) {
df743c7d 593 /* scan until (non-escaped) white space */
2a99df69 594 if (git__isspace(*scan) && *(scan - 1) != '\\') {
5c54e216 595 if (!allow_space || (*scan != ' ' && *scan != '\t' && *scan != '\r'))
2a99df69
RB
596 break;
597 }
ee1f0b1a
RB
598
599 if (*scan == '/') {
73b51450 600 spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
ee1f0b1a 601 slash_count++;
d58336dd
RB
602 if (pattern == scan)
603 pattern++;
ee1f0b1a 604 }
14a513e0 605 /* remember if we see an unescaped wildcard in pattern */
41a82592 606 else if (git__iswildcard(*scan) &&
14a513e0
RB
607 (scan == pattern || (*(scan - 1) != '\\')))
608 spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
ee1f0b1a
RB
609 }
610
611 *base = scan;
df743c7d 612
2d160ef7
ET
613 if ((spec->length = scan - pattern) == 0)
614 return GIT_ENOTFOUND;
ee1f0b1a 615
5c54e216
CMN
616 /*
617 * Remove one trailing \r in case this is a CRLF delimited
618 * file, in the case of Icon\r\r\n, we still leave the first
619 * \r there to match against.
620 */
621 if (pattern[spec->length - 1] == '\r')
622 if (--spec->length == 0)
623 return GIT_ENOTFOUND;
624
ee1f0b1a
RB
625 if (pattern[spec->length - 1] == '/') {
626 spec->length--;
73b51450 627 spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
ee1f0b1a 628 if (--slash_count <= 0)
73b51450 629 spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
ee1f0b1a 630 }
ac16bd0a
RB
631 if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 &&
632 spec->length >= 2 &&
916fcbd6
RB
633 pattern[spec->length - 1] == '*' &&
634 pattern[spec->length - 2] == '/') {
635 spec->length -= 2;
636 spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR;
637 /* leave FULLPATH match on, however */
638 }
ee1f0b1a 639
6069042f 640 if (context) {
90997e40 641 char *slash = strrchr(context, '/');
6069042f
CMN
642 size_t len;
643 if (slash) {
644 /* include the slash for easier matching */
645 len = slash - context + 1;
646 spec->containing_dir = git_pool_strndup(pool, context, len);
647 spec->containing_dir_length = len;
648 }
649 }
650
90997e40 651 spec->pattern = git_pool_strndup(pool, pattern, spec->length);
a51cd8e6
RB
652
653 if (!spec->pattern) {
654 *base = git__next_line(pattern);
ae9e29fd 655 return -1;
a51cd8e6 656 } else {
83bfbdf5 657 /* strip '\' that might have be used for internal whitespace */
02a0d651 658 spec->length = git__unescape(spec->pattern);
7d490872 659 /* TODO: convert remaining '\' into '/' for POSIX ??? */
a51cd8e6
RB
660 }
661
ae9e29fd 662 return 0;
ee1f0b1a
RB
663}
664
0d32f39e 665static bool parse_optimized_patterns(
666 git_attr_fnmatch *spec,
667 git_pool *pool,
668 const char *pattern)
669{
670 if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
671 spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
672 spec->pattern = git_pool_strndup(pool, pattern, 1);
673 spec->length = 1;
674
675 return true;
676 }
677
678 return false;
679}
680
ee1f0b1a
RB
681static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
682{
683 const git_attr_name *a = a_raw;
684 const git_attr_name *b = b_raw;
685
686 if (b->name_hash < a->name_hash)
687 return 1;
688 else if (b->name_hash > a->name_hash)
689 return -1;
690 else
691 return strcmp(b->name, a->name);
692}
693
bd370b14 694static void git_attr_assignment__free(git_attr_assignment *assign)
73b51450 695{
19fa2bc1
RB
696 /* name and value are stored in a git_pool associated with the
697 * git_attr_file, so they do not need to be freed here
698 */
73b51450 699 assign->name = NULL;
19fa2bc1 700 assign->value = NULL;
73b51450
RB
701 git__free(assign);
702}
703
bd370b14
RB
704static int merge_assignments(void **old_raw, void *new_raw)
705{
706 git_attr_assignment **old = (git_attr_assignment **)old_raw;
707 git_attr_assignment *new = (git_attr_assignment *)new_raw;
708
709 GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
710 *old = new;
711 return GIT_EEXISTS;
712}
713
73b51450
RB
714int git_attr_assignment__parse(
715 git_repository *repo,
19fa2bc1 716 git_pool *pool,
ee1f0b1a
RB
717 git_vector *assigns,
718 const char **base)
719{
ae9e29fd 720 int error;
ee1f0b1a
RB
721 const char *scan = *base;
722 git_attr_assignment *assign = NULL;
723
724 assert(assigns && !assigns->length);
725
22b6b82f 726 git_vector_set_cmp(assigns, sort_by_hash_and_name);
bd370b14 727
ae9e29fd 728 while (*scan && *scan != '\n') {
ee1f0b1a
RB
729 const char *name_start, *value_start;
730
731 /* skip leading blanks */
0f49200c 732 while (git__isspace(*scan) && *scan != '\n') scan++;
ee1f0b1a
RB
733
734 /* allocate assign if needed */
735 if (!assign) {
736 assign = git__calloc(1, sizeof(git_attr_assignment));
ae9e29fd 737 GITERR_CHECK_ALLOC(assign);
bd370b14 738 GIT_REFCOUNT_INC(assign);
ee1f0b1a
RB
739 }
740
741 assign->name_hash = 5381;
0c9eacf3 742 assign->value = git_attr__true;
ee1f0b1a
RB
743
744 /* look for magic name prefixes */
745 if (*scan == '-') {
0c9eacf3 746 assign->value = git_attr__false;
ee1f0b1a
RB
747 scan++;
748 } else if (*scan == '!') {
0c9eacf3 749 assign->value = git_attr__unset; /* explicit unspecified state */
ee1f0b1a
RB
750 scan++;
751 } else if (*scan == '#') /* comment rest of line */
752 break;
753
754 /* find the name */
755 name_start = scan;
0f49200c 756 while (*scan && !git__isspace(*scan) && *scan != '=') {
ee1f0b1a
RB
757 assign->name_hash =
758 ((assign->name_hash << 5) + assign->name_hash) + *scan;
759 scan++;
760 }
73b51450 761 if (scan == name_start) {
ee1f0b1a
RB
762 /* must have found lone prefix (" - ") or leading = ("=foo")
763 * or end of buffer -- advance until whitespace and continue
764 */
0f49200c 765 while (*scan && !git__isspace(*scan)) scan++;
ee1f0b1a
RB
766 continue;
767 }
768
73b51450 769 /* allocate permanent storage for name */
19fa2bc1 770 assign->name = git_pool_strndup(pool, name_start, scan - name_start);
ae9e29fd 771 GITERR_CHECK_ALLOC(assign->name);
73b51450 772
ee1f0b1a
RB
773 /* if there is an equals sign, find the value */
774 if (*scan == '=') {
0f49200c 775 for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan);
ee1f0b1a
RB
776
777 /* if we found a value, allocate permanent storage for it */
778 if (scan > value_start) {
19fa2bc1 779 assign->value = git_pool_strndup(pool, value_start, scan - value_start);
ae9e29fd 780 GITERR_CHECK_ALLOC(assign->value);
ee1f0b1a
RB
781 }
782 }
783
bd370b14 784 /* expand macros (if given a repo with a macro cache) */
0c9eacf3 785 if (repo != NULL && assign->value == git_attr__true) {
01fed0a8
RB
786 git_attr_rule *macro =
787 git_attr_cache__lookup_macro(repo, assign->name);
73b51450
RB
788
789 if (macro != NULL) {
790 unsigned int i;
791 git_attr_assignment *massign;
792
bd370b14
RB
793 git_vector_foreach(&macro->assigns, i, massign) {
794 GIT_REFCOUNT_INC(massign);
73b51450 795
bd370b14
RB
796 error = git_vector_insert_sorted(
797 assigns, massign, &merge_assignments);
6f73e026
JG
798 if (error < 0 && error != GIT_EEXISTS) {
799 git_attr_assignment__free(assign);
ae9e29fd 800 return error;
6f73e026 801 }
73b51450 802 }
73b51450 803 }
ee1f0b1a
RB
804 }
805
806 /* insert allocated assign into vector */
bd370b14 807 error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
ae9e29fd
RB
808 if (error < 0 && error != GIT_EEXISTS)
809 return error;
ee1f0b1a
RB
810
811 /* clear assign since it is now "owned" by the vector */
812 assign = NULL;
813 }
814
73b51450 815 if (assign != NULL)
bd370b14 816 git_attr_assignment__free(assign);
ee1f0b1a 817
df743c7d 818 *base = git__next_line(scan);
ee1f0b1a 819
ae9e29fd 820 return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
ee1f0b1a
RB
821}
822
c6d2a2c0 823static void git_attr_rule__clear(git_attr_rule *rule)
ee1f0b1a
RB
824{
825 unsigned int i;
826 git_attr_assignment *assign;
827
828 if (!rule)
73b51450 829 return;
ee1f0b1a 830
df743c7d
RB
831 if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) {
832 git_vector_foreach(&rule->assigns, i, assign)
833 GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
834 git_vector_free(&rule->assigns);
835 }
836
19fa2bc1 837 /* match.pattern is stored in a git_pool, so no need to free */
ee1f0b1a
RB
838 rule->match.pattern = NULL;
839 rule->match.length = 0;
ee1f0b1a 840}
c6d2a2c0
RB
841
842void git_attr_rule__free(git_attr_rule *rule)
843{
844 git_attr_rule__clear(rule);
845 git__free(rule);
846}
847
9f779aac
ET
848int git_attr_session__init(git_attr_session *session, git_repository *repo)
849{
850 assert(repo);
851
852 session->key = git_atomic_inc(&repo->attr_session_key);
853
854 return 0;
855}
d4b1b767
ET
856
857void git_attr_session__free(git_attr_session *session)
858{
859 if (!session)
860 return;
861
862 git_buf_free(&session->sysdir);
f58cc280 863 git_buf_free(&session->tmp);
d4b1b767
ET
864
865 memset(session, 0, sizeof(git_attr_session));
866}