]> git.proxmox.com Git - libgit2.git/blame - src/attr_file.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / attr_file.c
CommitLineData
eae0bfdc
PP
1/*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
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.
6 */
7
8#include "attr_file.h"
9
73b51450 10#include "repository.h"
ee1f0b1a 11#include "filebuf.h"
823c0e9c 12#include "attrcache.h"
f917481e
RB
13#include "git2/blob.h"
14#include "git2/tree.h"
ac3d33df 15#include "blob.h"
7d490872 16#include "index.h"
22a2d3d5 17#include "wildmatch.h"
ee1f0b1a
RB
18#include <ctype.h>
19
7d490872
RB
20static void attr_file_free(git_attr_file *file)
21{
e6e8530a
RB
22 bool unlock = !git_mutex_lock(&file->lock);
23 git_attr_file__clear_rules(file, false);
7d490872 24 git_pool_clear(&file->pool);
e6e8530a
RB
25 if (unlock)
26 git_mutex_unlock(&file->lock);
27 git_mutex_free(&file->lock);
28
7d490872
RB
29 git__memzero(file, sizeof(*file));
30 git__free(file);
31}
ee1f0b1a 32
f917481e 33int git_attr_file__new(
7d490872 34 git_attr_file **out,
823c0e9c 35 git_attr_file_entry *entry,
c25aa7cd 36 git_attr_file_source *source)
73b51450 37{
7d490872 38 git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
ac3d33df 39 GIT_ERROR_CHECK_ALLOC(attrs);
df743c7d 40
e6e8530a 41 if (git_mutex_init(&attrs->lock) < 0) {
ac3d33df 42 git_error_set(GIT_ERROR_OS, "failed to initialize lock");
22a2d3d5 43 goto on_error;
e6e8530a
RB
44 }
45
22a2d3d5
UG
46 if (git_pool_init(&attrs->pool, 1) < 0)
47 goto on_error;
48
7d490872 49 GIT_REFCOUNT_INC(attrs);
c25aa7cd
PP
50 attrs->entry = entry;
51 memcpy(&attrs->source, source, sizeof(git_attr_file_source));
7d490872
RB
52 *out = attrs;
53 return 0;
22a2d3d5
UG
54
55on_error:
56 git__free(attrs);
57 return -1;
7d490872 58}
f917481e 59
e6e8530a 60int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
7d490872
RB
61{
62 unsigned int i;
63 git_attr_rule *rule;
f917481e 64
e6e8530a 65 if (need_lock && git_mutex_lock(&file->lock) < 0) {
ac3d33df 66 git_error_set(GIT_ERROR_OS, "failed to lock attribute file");
e6e8530a
RB
67 return -1;
68 }
69
7d490872
RB
70 git_vector_foreach(&file->rules, i, rule)
71 git_attr_rule__free(rule);
72 git_vector_free(&file->rules);
e6e8530a
RB
73
74 if (need_lock)
75 git_mutex_unlock(&file->lock);
76
77 return 0;
7d490872 78}
f917481e 79
7d490872
RB
80void git_attr_file__free(git_attr_file *file)
81{
82 if (!file)
83 return;
84 GIT_REFCOUNT_DEC(file, attr_file_free);
85}
19fa2bc1 86
7d490872
RB
87static int attr_file_oid_from_index(
88 git_oid *oid, git_repository *repo, const char *path)
89{
90 int error;
91 git_index *idx;
92 size_t pos;
93 const git_index_entry *entry;
94
95 if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
96 (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
97 return error;
98
99 if (!(entry = git_index_get_byindex(idx, pos)))
100 return GIT_ENOTFOUND;
101
102 *oid = entry->id;
19fa2bc1 103 return 0;
7d490872
RB
104}
105
106int git_attr_file__load(
107 git_attr_file **out,
108 git_repository *repo,
9f779aac 109 git_attr_session *attr_session,
823c0e9c 110 git_attr_file_entry *entry,
c25aa7cd 111 git_attr_file_source *source,
22a2d3d5
UG
112 git_attr_file_parser parser,
113 bool allow_macros)
7d490872
RB
114{
115 int error = 0;
c25aa7cd 116 git_commit *commit = NULL;
22a2d3d5
UG
117 git_tree *tree = NULL;
118 git_tree_entry *tree_entry = NULL;
7d490872 119 git_blob *blob = NULL;
e579e0f7 120 git_str content = GIT_STR_INIT;
22a2d3d5 121 const char *content_str;
7d490872 122 git_attr_file *file;
823c0e9c 123 struct stat st;
9f779aac 124 bool nonexistent = false;
22a2d3d5 125 int bom_offset;
e579e0f7 126 git_str_bom_t bom;
22a2d3d5
UG
127 git_oid id;
128 git_object_size_t blobsize;
7d490872
RB
129
130 *out = NULL;
131
c25aa7cd
PP
132 switch (source->type) {
133 case GIT_ATTR_FILE_SOURCE_MEMORY:
823c0e9c
RB
134 /* in-memory attribute file doesn't need data */
135 break;
c25aa7cd 136 case GIT_ATTR_FILE_SOURCE_INDEX: {
823c0e9c 137 if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
7d490872
RB
138 (error = git_blob_lookup(&blob, repo, &id)) < 0)
139 return error;
140
72d00241
VM
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 */
ac3d33df
JK
143 blobsize = git_blob_rawsize(blob);
144
145 GIT_ERROR_CHECK_BLOBSIZE(blobsize);
e579e0f7 146 git_str_put(&content, git_blob_rawcontent(blob), (size_t)blobsize);
823c0e9c
RB
147 break;
148 }
c25aa7cd 149 case GIT_ATTR_FILE_SOURCE_FILE: {
24b8ed2b 150 int fd = -1;
823c0e9c 151
9f779aac 152 /* For open or read errors, pretend that we got ENOTFOUND. */
823c0e9c
RB
153 /* TODO: issue warning when warning API is available */
154
9f779aac
ET
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)
159 nonexistent = true;
24b8ed2b
PS
160
161 if (fd >= 0)
9f779aac 162 p_close(fd);
823c0e9c 163
823c0e9c
RB
164 break;
165 }
c25aa7cd
PP
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)
171 goto cleanup;
172 } else {
173 if ((error = git_repository_head_tree(&tree, repo)) < 0)
174 goto cleanup;
175 }
176
177 if ((error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0) {
178 /*
179 * If the attributes file does not exist, we can
180 * cache an empty file for this commit to prevent
181 * needless future lookups.
182 */
183 if (error == GIT_ENOTFOUND) {
184 error = 0;
185 break;
186 }
187
188 goto cleanup;
189 }
190
191 if ((error = git_blob_lookup(&blob, repo, git_tree_entry_id(tree_entry))) < 0)
22a2d3d5
UG
192 goto cleanup;
193
194 /*
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.
197 */
198 blobsize = git_blob_rawsize(blob);
199
200 GIT_ERROR_CHECK_BLOBSIZE(blobsize);
e579e0f7 201 if ((error = git_str_put(&content,
22a2d3d5
UG
202 git_blob_rawcontent(blob), (size_t)blobsize)) < 0)
203 goto cleanup;
204
205 break;
206 }
823c0e9c 207 default:
c25aa7cd 208 git_error_set(GIT_ERROR_INVALID, "unknown file source %d", source->type);
823c0e9c 209 return -1;
7d490872
RB
210 }
211
823c0e9c 212 if ((error = git_attr_file__new(&file, entry, source)) < 0)
7d490872
RB
213 goto cleanup;
214
22a2d3d5 215 /* advance over a UTF8 BOM */
e579e0f7
MB
216 content_str = git_str_cstr(&content);
217 bom_offset = git_str_detect_bom(&bom, &content);
22a2d3d5 218
e579e0f7 219 if (bom == GIT_STR_BOM_UTF8)
22a2d3d5
UG
220 content_str += bom_offset;
221
9f779aac
ET
222 /* store the key of the attr_reader; don't bother with cache
223 * invalidation during the same attr reader session.
224 */
225 if (attr_session)
226 file->session_key = attr_session->key;
227
22a2d3d5 228 if (parser && (error = parser(repo, file, content_str, allow_macros)) < 0) {
7d490872 229 git_attr_file__free(file);
823c0e9c
RB
230 goto cleanup;
231 }
232
9f779aac
ET
233 /* write cache breakers */
234 if (nonexistent)
235 file->nonexistent = 1;
c25aa7cd 236 else if (source->type == GIT_ATTR_FILE_SOURCE_INDEX)
823c0e9c 237 git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
c25aa7cd 238 else if (source->type == GIT_ATTR_FILE_SOURCE_HEAD)
22a2d3d5 239 git_oid_cpy(&file->cache_data.oid, git_tree_id(tree));
c25aa7cd
PP
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)
823c0e9c
RB
243 git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
244 /* else always cacheable */
245
246 *out = file;
7d490872
RB
247
248cleanup:
249 git_blob_free(blob);
22a2d3d5
UG
250 git_tree_entry_free(tree_entry);
251 git_tree_free(tree);
c25aa7cd 252 git_commit_free(commit);
e579e0f7 253 git_str_dispose(&content);
7d490872
RB
254
255 return error;
73b51450
RB
256}
257
9f779aac
ET
258int git_attr_file__out_of_date(
259 git_repository *repo,
260 git_attr_session *attr_session,
c25aa7cd
PP
261 git_attr_file *file,
262 git_attr_file_source *source)
7d490872
RB
263{
264 if (!file)
265 return 1;
266
9f779aac
ET
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
269 */
270 if (attr_session && attr_session->key == file->session_key)
271 return 0;
272 else if (file->nonexistent)
273 return 1;
274
c25aa7cd
PP
275 switch (file->source.type) {
276 case GIT_ATTR_FILE_SOURCE_MEMORY:
823c0e9c
RB
277 return 0;
278
c25aa7cd 279 case GIT_ATTR_FILE_SOURCE_FILE:
823c0e9c
RB
280 return git_futils_filestamp_check(
281 &file->cache_data.stamp, file->entry->fullpath);
282
c25aa7cd 283 case GIT_ATTR_FILE_SOURCE_INDEX: {
7d490872
RB
284 int error;
285 git_oid id;
286
823c0e9c
RB
287 if ((error = attr_file_oid_from_index(
288 &id, repo, file->entry->path)) < 0)
7d490872
RB
289 return error;
290
291 return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
292 }
293
c25aa7cd
PP
294 case GIT_ATTR_FILE_SOURCE_HEAD: {
295 git_tree *tree = NULL;
296 int error = git_repository_head_tree(&tree, repo);
297
298 if (error < 0)
299 return error;
300
301 error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0);
302
303 git_tree_free(tree);
304 return error;
305 }
306
307 case GIT_ATTR_FILE_SOURCE_COMMIT: {
308 git_commit *commit = NULL;
309 git_tree *tree = NULL;
22a2d3d5
UG
310 int error;
311
c25aa7cd
PP
312 if ((error = git_commit_lookup(&commit, repo, source->commit_id)) < 0)
313 return error;
314
315 error = git_commit_tree(&tree, commit);
316 git_commit_free(commit);
317
318 if (error < 0)
22a2d3d5
UG
319 return error;
320
c25aa7cd 321 error = (git_oid__cmp(&file->cache_data.oid, git_tree_id(tree)) != 0);
22a2d3d5
UG
322
323 git_tree_free(tree);
324 return error;
325 }
326
823c0e9c 327 default:
c25aa7cd 328 git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source.type);
823c0e9c
RB
329 return -1;
330 }
7d490872
RB
331}
332
333static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
334static void git_attr_rule__clear(git_attr_rule *rule);
335static bool parse_optimized_patterns(
336 git_attr_fnmatch *spec,
337 git_pool *pool,
338 const char *pattern);
339
f917481e 340int git_attr_file__parse_buffer(
22a2d3d5 341 git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros)
ee1f0b1a 342{
7d490872 343 const char *scan = data, *context = NULL;
ee1f0b1a 344 git_attr_rule *rule = NULL;
22a2d3d5 345 int error = 0;
ee1f0b1a 346
22a2d3d5 347 /* If subdir file path, convert context for file paths */
e579e0f7 348 if (attrs->entry && git_fs_path_root(attrs->entry->path) < 0 &&
22a2d3d5 349 !git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
823c0e9c 350 context = attrs->entry->path;
a51cd8e6 351
e6e8530a 352 if (git_mutex_lock(&attrs->lock) < 0) {
ac3d33df 353 git_error_set(GIT_ERROR_OS, "failed to lock attribute file");
e6e8530a
RB
354 return -1;
355 }
356
ae9e29fd 357 while (!error && *scan) {
22a2d3d5
UG
358 /* Allocate rule if needed, otherwise re-use previous rule */
359 if (!rule) {
360 rule = git__calloc(1, sizeof(*rule));
361 GIT_ERROR_CHECK_ALLOC(rule);
362 } else
363 git_attr_rule__clear(rule);
364
365 rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
366
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)
73b51450 370 {
22a2d3d5
UG
371 if (error != GIT_ENOTFOUND)
372 goto out;
373 error = 0;
374 continue;
73b51450 375 }
ee1f0b1a 376
22a2d3d5
UG
377 if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) {
378 /* TODO: warning if macro found in file below repo root */
379 if (!allow_macros)
380 continue;
381 if ((error = git_attr_cache__insert_macro(repo, rule)) < 0)
382 goto out;
383 } else if ((error = git_vector_insert(&attrs->rules, rule)) < 0)
384 goto out;
385
386 rule = NULL;
ee1f0b1a
RB
387 }
388
22a2d3d5 389out:
e6e8530a 390 git_mutex_unlock(&attrs->lock);
a51cd8e6 391 git_attr_rule__free(rule);
f917481e 392
ee1f0b1a
RB
393 return error;
394}
395
19fa2bc1 396uint32_t git_attr_file__name_hash(const char *name)
ee1f0b1a 397{
19fa2bc1 398 uint32_t h = 5381;
ee1f0b1a 399 int c;
c25aa7cd
PP
400
401 GIT_ASSERT_ARG(name);
402
ee1f0b1a
RB
403 while ((c = (int)*name++) != 0)
404 h = ((h << 5) + h) + c;
405 return h;
406}
407
ee1f0b1a
RB
408int git_attr_file__lookup_one(
409 git_attr_file *file,
f554611a 410 git_attr_path *path,
ee1f0b1a
RB
411 const char *attr,
412 const char **value)
413{
b8457baa 414 size_t i;
ee1f0b1a
RB
415 git_attr_name name;
416 git_attr_rule *rule;
417
418 *value = NULL;
419
420 name.name = attr;
421 name.name_hash = git_attr_file__name_hash(attr);
422
423 git_attr_file__foreach_matching_rule(file, path, i, rule) {
11d9f6b3 424 size_t pos;
ee1f0b1a 425
11d9f6b3 426 if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
ee1f0b1a
RB
427 *value = ((git_attr_assignment *)
428 git_vector_get(&rule->assigns, pos))->value;
429 break;
430 }
431 }
432
ab43ad2f 433 return 0;
ee1f0b1a
RB
434}
435
823c0e9c 436int git_attr_file__load_standalone(git_attr_file **out, const char *path)
7d490872 437{
e579e0f7 438 git_str content = GIT_STR_INIT;
c25aa7cd 439 git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE };
22a2d3d5
UG
440 git_attr_file *file = NULL;
441 int error;
7d490872 442
22a2d3d5
UG
443 if ((error = git_futils_readbuffer(&content, path)) < 0)
444 goto out;
7d490872 445
22a2d3d5
UG
446 /*
447 * Because the cache entry is allocated from the file's own pool, we
7d490872
RB
448 * don't have to free it - freeing file+pool will free cache entry, too.
449 */
450
c25aa7cd 451 if ((error = git_attr_file__new(&file, NULL, &source)) < 0 ||
22a2d3d5 452 (error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 ||
c25aa7cd 453 (error = git_attr_cache__alloc_file_entry(&file->entry, NULL, NULL, path, &file->pool)) < 0)
22a2d3d5 454 goto out;
7d490872 455
22a2d3d5
UG
456 *out = file;
457out:
7d490872
RB
458 if (error < 0)
459 git_attr_file__free(file);
e579e0f7 460 git_str_dispose(&content);
7d490872
RB
461
462 return error;
463}
ee1f0b1a 464
ab43ad2f 465bool git_attr_fnmatch__match(
df743c7d 466 git_attr_fnmatch *match,
f554611a 467 git_attr_path *path)
ee1f0b1a 468{
90997e40 469 const char *relpath = path->path;
7d490872
RB
470 const char *filename;
471 int flags = 0;
ee1f0b1a 472
6069042f
CMN
473 /*
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.
477 */
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))
481 return 0;
482 } else {
483 if (git__prefixcmp(path->path, match->containing_dir))
484 return 0;
485 }
90997e40
ET
486
487 relpath += match->containing_dir_length;
6069042f
CMN
488 }
489
7d490872 490 if (match->flags & GIT_ATTR_FNMATCH_ICASE)
22a2d3d5 491 flags |= WM_CASEFOLD;
7d490872
RB
492
493 if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
90997e40 494 filename = relpath;
22a2d3d5 495 flags |= WM_PATHNAME;
7d490872
RB
496 } else {
497 filename = path->basename;
7d490872 498 }
df743c7d 499
f554611a 500 if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
34593aae
ET
501 bool samename;
502
c3b8e8b3
RG
503 /*
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),
506 * do not match.
507 */
f554611a 508 if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
c3b8e8b3 509 path->basename == relpath)
f554611a
RB
510 return false;
511
ef6d0722 512 /* fail match if this is a file with same name as ignored folder */
34593aae 513 samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ?
90997e40
ET
514 !strcasecmp(match->pattern, relpath) :
515 !strcmp(match->pattern, relpath);
ef6d0722
ET
516
517 if (samename)
518 return false;
519
22a2d3d5 520 return (wildmatch(match->pattern, relpath, flags) == WM_MATCH);
f554611a
RB
521 }
522
22a2d3d5 523 return (wildmatch(match->pattern, filename, flags) == WM_MATCH);
df743c7d
RB
524}
525
ab43ad2f 526bool git_attr_rule__match(
df743c7d 527 git_attr_rule *rule,
f554611a 528 git_attr_path *path)
df743c7d 529{
ab43ad2f 530 bool matched = git_attr_fnmatch__match(&rule->match, path);
ee1f0b1a 531
73b51450 532 if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
ab43ad2f 533 matched = !matched;
ee1f0b1a
RB
534
535 return matched;
536}
537
538git_attr_assignment *git_attr_rule__lookup_assignment(
539 git_attr_rule *rule, const char *name)
540{
11d9f6b3 541 size_t pos;
ee1f0b1a
RB
542 git_attr_name key;
543 key.name = name;
544 key.name_hash = git_attr_file__name_hash(name);
545
11d9f6b3
PK
546 if (git_vector_bsearch(&pos, &rule->assigns, &key))
547 return NULL;
ee1f0b1a 548
11d9f6b3 549 return git_vector_get(&rule->assigns, pos);
ee1f0b1a
RB
550}
551
552int git_attr_path__init(
c25aa7cd
PP
553 git_attr_path *info,
554 const char *path,
555 const char *base,
556 git_dir_flag dir_flag)
ee1f0b1a 557{
ca1b6e54
RB
558 ssize_t root;
559
d58336dd 560 /* build full path as best we can */
e579e0f7 561 git_str_init(&info->full, 0);
adc9bdb3 562
e579e0f7 563 if (git_fs_path_join_unrooted(&info->full, path, base, &root) < 0)
ca1b6e54
RB
564 return -1;
565
566 info->path = info->full.ptr + root;
d58336dd
RB
567
568 /* remove trailing slashes */
569 while (info->full.size > 0) {
570 if (info->full.ptr[info->full.size - 1] != '/')
571 break;
572 info->full.size--;
573 }
574 info->full.ptr[info->full.size] = '\0';
575
576 /* skip leading slashes in path */
577 while (*info->path == '/')
578 info->path++;
579
580 /* find trailing basename component */
581 info->basename = strrchr(info->path, '/');
582 if (info->basename)
583 info->basename++;
584 if (!info->basename || !*info->basename)
585 info->basename = info->path;
586
4c09e19a
W
587 switch (dir_flag)
588 {
589 case GIT_DIR_FLAG_FALSE:
590 info->is_dir = 0;
591 break;
592
593 case GIT_DIR_FLAG_TRUE:
594 info->is_dir = 1;
595 break;
596
597 case GIT_DIR_FLAG_UNKNOWN:
598 default:
e579e0f7 599 info->is_dir = (int)git_fs_path_isdir(info->full.ptr);
4c09e19a
W
600 break;
601 }
adc9bdb3 602
ae9e29fd 603 return 0;
ee1f0b1a
RB
604}
605
d58336dd
RB
606void git_attr_path__free(git_attr_path *info)
607{
e579e0f7 608 git_str_dispose(&info->full);
d58336dd
RB
609 info->path = NULL;
610 info->basename = NULL;
611}
612
ee1f0b1a
RB
613/*
614 * From gitattributes(5):
615 *
616 * Patterns have the following format:
617 *
618 * - A blank line matches no files, so it can serve as a separator for
619 * readability.
620 *
621 * - A line starting with # serves as a comment.
622 *
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.
626 *
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).
632 *
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
635 * directories.
636 *
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".
643 */
644
22a2d3d5
UG
645/*
646 * Determine the length of trailing spaces. Escaped spaces do not count as
647 * trailing whitespace.
648 */
649static size_t trailing_space_length(const char *p, size_t len)
650{
651 size_t n, i;
652 for (n = len; n; n--) {
653 if (p[n-1] != ' ' && p[n-1] != '\t')
654 break;
655
656 /*
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.
662 */
663 i = n;
664 while (i > 1 && p[i-2] == '\\')
665 i--;
666 if ((n - i) % 2)
667 break;
668 }
669 return len - n;
670}
671
672static size_t unescape_spaces(char *str)
673{
674 char *scan, *pos = str;
675 bool escaped = false;
676
677 if (!str)
678 return 0;
679
680 for (scan = str; *scan; scan++) {
681 if (!escaped && *scan == '\\') {
682 escaped = true;
683 continue;
684 }
685
686 /* Only insert the escape character for escaped non-spaces */
687 if (escaped && !git__isspace(*scan))
688 *pos++ = '\\';
689
690 *pos++ = *scan;
691 escaped = false;
692 }
693
694 if (pos != scan)
695 *pos = '\0';
696
697 return (pos - str);
698}
699
ee1f0b1a 700/*
0d0fa7c3 701 * This will return 0 if the spec was filled out,
ee1f0b1a
RB
702 * GIT_ENOTFOUND if the fnmatch does not require matching, or
703 * another error code there was an actual problem.
704 */
4ba64794 705int git_attr_fnmatch__parse(
ee1f0b1a 706 git_attr_fnmatch *spec,
19fa2bc1 707 git_pool *pool,
7d490872 708 const char *context,
ee1f0b1a
RB
709 const char **base)
710{
4ba64794
RB
711 const char *pattern, *scan;
712 int slash_count, allow_space;
22a2d3d5 713 bool escaped;
ee1f0b1a 714
c25aa7cd
PP
715 GIT_ASSERT_ARG(spec);
716 GIT_ASSERT_ARG(base && *base);
ee1f0b1a 717
4ba64794
RB
718 if (parse_optimized_patterns(spec, pool, *base))
719 return 0;
720
721 spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
722 allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
723
ee1f0b1a
RB
724 pattern = *base;
725
22a2d3d5
UG
726 while (!allow_space && git__isspace(*pattern))
727 pattern++;
728
729 if (!*pattern || *pattern == '#' || *pattern == '\n' ||
730 (*pattern == '\r' && *(pattern + 1) == '\n')) {
df743c7d
RB
731 *base = git__next_line(pattern);
732 return GIT_ENOTFOUND;
ee1f0b1a
RB
733 }
734
4ba64794 735 if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
73b51450
RB
736 if (strncmp(pattern, "[attr]", 6) == 0) {
737 spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
738 pattern += 6;
73b51450 739 }
df743c7d 740 /* else a character range like [a-e]* which is accepted */
73b51450
RB
741 }
742
4ba64794 743 if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
ac3d33df 744 spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
ee1f0b1a 745 pattern++;
ee1f0b1a
RB
746 }
747
ee1f0b1a 748 slash_count = 0;
22a2d3d5
UG
749 escaped = false;
750 /* Scan until a non-escaped whitespace. */
ee1f0b1a 751 for (scan = pattern; *scan != '\0'; ++scan) {
22a2d3d5 752 char c = *scan;
ee1f0b1a 753
22a2d3d5
UG
754 if (c == '\\' && !escaped) {
755 escaped = true;
756 continue;
757 } else if (git__isspace(c) && !escaped) {
758 if (!allow_space || (c != ' ' && c != '\t' && c != '\r'))
759 break;
760 } else if (c == '/') {
73b51450 761 spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
ee1f0b1a 762 slash_count++;
22a2d3d5
UG
763
764 if (slash_count == 1 && pattern == scan)
d58336dd 765 pattern++;
22a2d3d5
UG
766 } else if (git__iswildcard(c) && !escaped) {
767 /* remember if we see an unescaped wildcard in pattern */
14a513e0 768 spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
22a2d3d5
UG
769 }
770
771 escaped = false;
ee1f0b1a
RB
772 }
773
774 *base = scan;
df743c7d 775
2d160ef7
ET
776 if ((spec->length = scan - pattern) == 0)
777 return GIT_ENOTFOUND;
ee1f0b1a 778
5c54e216
CMN
779 /*
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.
783 */
784 if (pattern[spec->length - 1] == '\r')
785 if (--spec->length == 0)
786 return GIT_ENOTFOUND;
787
4b3ec53c 788 /* Remove trailing spaces. */
22a2d3d5
UG
789 spec->length -= trailing_space_length(pattern, spec->length);
790
791 if (spec->length == 0)
792 return GIT_ENOTFOUND;
4b3ec53c 793
ee1f0b1a
RB
794 if (pattern[spec->length - 1] == '/') {
795 spec->length--;
73b51450 796 spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
ee1f0b1a 797 if (--slash_count <= 0)
73b51450 798 spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
ee1f0b1a
RB
799 }
800
6069042f 801 if (context) {
90997e40 802 char *slash = strrchr(context, '/');
6069042f
CMN
803 size_t len;
804 if (slash) {
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;
809 }
810 }
811
90997e40 812 spec->pattern = git_pool_strndup(pool, pattern, spec->length);
a51cd8e6
RB
813
814 if (!spec->pattern) {
815 *base = git__next_line(pattern);
ae9e29fd 816 return -1;
a51cd8e6 817 } else {
22a2d3d5
UG
818 /* strip '\' that might have been used for internal whitespace */
819 spec->length = unescape_spaces(spec->pattern);
a51cd8e6
RB
820 }
821
ae9e29fd 822 return 0;
ee1f0b1a
RB
823}
824
0d32f39e 825static bool parse_optimized_patterns(
826 git_attr_fnmatch *spec,
827 git_pool *pool,
828 const char *pattern)
829{
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);
833 spec->length = 1;
834
835 return true;
836 }
837
838 return false;
839}
840
ee1f0b1a
RB
841static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
842{
843 const git_attr_name *a = a_raw;
844 const git_attr_name *b = b_raw;
845
846 if (b->name_hash < a->name_hash)
847 return 1;
848 else if (b->name_hash > a->name_hash)
849 return -1;
850 else
851 return strcmp(b->name, a->name);
852}
853
bd370b14 854static void git_attr_assignment__free(git_attr_assignment *assign)
73b51450 855{
19fa2bc1
RB
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
858 */
73b51450 859 assign->name = NULL;
19fa2bc1 860 assign->value = NULL;
73b51450
RB
861 git__free(assign);
862}
863
bd370b14
RB
864static int merge_assignments(void **old_raw, void *new_raw)
865{
866 git_attr_assignment **old = (git_attr_assignment **)old_raw;
867 git_attr_assignment *new = (git_attr_assignment *)new_raw;
868
869 GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
870 *old = new;
871 return GIT_EEXISTS;
872}
873
73b51450
RB
874int git_attr_assignment__parse(
875 git_repository *repo,
19fa2bc1 876 git_pool *pool,
ee1f0b1a
RB
877 git_vector *assigns,
878 const char **base)
879{
ae9e29fd 880 int error;
ee1f0b1a
RB
881 const char *scan = *base;
882 git_attr_assignment *assign = NULL;
883
c25aa7cd 884 GIT_ASSERT_ARG(assigns && !assigns->length);
ee1f0b1a 885
22b6b82f 886 git_vector_set_cmp(assigns, sort_by_hash_and_name);
bd370b14 887
ae9e29fd 888 while (*scan && *scan != '\n') {
ee1f0b1a
RB
889 const char *name_start, *value_start;
890
891 /* skip leading blanks */
0f49200c 892 while (git__isspace(*scan) && *scan != '\n') scan++;
ee1f0b1a
RB
893
894 /* allocate assign if needed */
895 if (!assign) {
896 assign = git__calloc(1, sizeof(git_attr_assignment));
ac3d33df 897 GIT_ERROR_CHECK_ALLOC(assign);
bd370b14 898 GIT_REFCOUNT_INC(assign);
ee1f0b1a
RB
899 }
900
901 assign->name_hash = 5381;
0c9eacf3 902 assign->value = git_attr__true;
ee1f0b1a
RB
903
904 /* look for magic name prefixes */
905 if (*scan == '-') {
0c9eacf3 906 assign->value = git_attr__false;
ee1f0b1a
RB
907 scan++;
908 } else if (*scan == '!') {
0c9eacf3 909 assign->value = git_attr__unset; /* explicit unspecified state */
ee1f0b1a
RB
910 scan++;
911 } else if (*scan == '#') /* comment rest of line */
912 break;
913
914 /* find the name */
915 name_start = scan;
0f49200c 916 while (*scan && !git__isspace(*scan) && *scan != '=') {
ee1f0b1a
RB
917 assign->name_hash =
918 ((assign->name_hash << 5) + assign->name_hash) + *scan;
919 scan++;
920 }
73b51450 921 if (scan == name_start) {
ee1f0b1a
RB
922 /* must have found lone prefix (" - ") or leading = ("=foo")
923 * or end of buffer -- advance until whitespace and continue
924 */
0f49200c 925 while (*scan && !git__isspace(*scan)) scan++;
ee1f0b1a
RB
926 continue;
927 }
928
73b51450 929 /* allocate permanent storage for name */
19fa2bc1 930 assign->name = git_pool_strndup(pool, name_start, scan - name_start);
ac3d33df 931 GIT_ERROR_CHECK_ALLOC(assign->name);
73b51450 932
ee1f0b1a
RB
933 /* if there is an equals sign, find the value */
934 if (*scan == '=') {
0f49200c 935 for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan);
ee1f0b1a
RB
936
937 /* if we found a value, allocate permanent storage for it */
938 if (scan > value_start) {
19fa2bc1 939 assign->value = git_pool_strndup(pool, value_start, scan - value_start);
ac3d33df 940 GIT_ERROR_CHECK_ALLOC(assign->value);
ee1f0b1a
RB
941 }
942 }
943
bd370b14 944 /* expand macros (if given a repo with a macro cache) */
0c9eacf3 945 if (repo != NULL && assign->value == git_attr__true) {
01fed0a8
RB
946 git_attr_rule *macro =
947 git_attr_cache__lookup_macro(repo, assign->name);
73b51450
RB
948
949 if (macro != NULL) {
950 unsigned int i;
951 git_attr_assignment *massign;
952
bd370b14
RB
953 git_vector_foreach(&macro->assigns, i, massign) {
954 GIT_REFCOUNT_INC(massign);
73b51450 955
bd370b14
RB
956 error = git_vector_insert_sorted(
957 assigns, massign, &merge_assignments);
6f73e026
JG
958 if (error < 0 && error != GIT_EEXISTS) {
959 git_attr_assignment__free(assign);
ae9e29fd 960 return error;
6f73e026 961 }
73b51450 962 }
73b51450 963 }
ee1f0b1a
RB
964 }
965
966 /* insert allocated assign into vector */
bd370b14 967 error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
ae9e29fd
RB
968 if (error < 0 && error != GIT_EEXISTS)
969 return error;
ee1f0b1a
RB
970
971 /* clear assign since it is now "owned" by the vector */
972 assign = NULL;
973 }
974
73b51450 975 if (assign != NULL)
bd370b14 976 git_attr_assignment__free(assign);
ee1f0b1a 977
df743c7d 978 *base = git__next_line(scan);
ee1f0b1a 979
ae9e29fd 980 return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
ee1f0b1a
RB
981}
982
c6d2a2c0 983static void git_attr_rule__clear(git_attr_rule *rule)
ee1f0b1a
RB
984{
985 unsigned int i;
986 git_attr_assignment *assign;
987
988 if (!rule)
73b51450 989 return;
ee1f0b1a 990
df743c7d
RB
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);
995 }
996
19fa2bc1 997 /* match.pattern is stored in a git_pool, so no need to free */
ee1f0b1a
RB
998 rule->match.pattern = NULL;
999 rule->match.length = 0;
ee1f0b1a 1000}
c6d2a2c0
RB
1001
1002void git_attr_rule__free(git_attr_rule *rule)
1003{
1004 git_attr_rule__clear(rule);
1005 git__free(rule);
1006}
1007
9f779aac
ET
1008int git_attr_session__init(git_attr_session *session, git_repository *repo)
1009{
c25aa7cd 1010 GIT_ASSERT_ARG(repo);
9f779aac 1011
22a2d3d5 1012 memset(session, 0, sizeof(*session));
c25aa7cd 1013 session->key = git_atomic32_inc(&repo->attr_session_key);
9f779aac
ET
1014
1015 return 0;
1016}
d4b1b767
ET
1017
1018void git_attr_session__free(git_attr_session *session)
1019{
1020 if (!session)
1021 return;
1022
e579e0f7
MB
1023 git_str_dispose(&session->sysdir);
1024 git_str_dispose(&session->tmp);
d4b1b767
ET
1025
1026 memset(session, 0, sizeof(git_attr_session));
1027}