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