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