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