Skip to content

Commit f834690

Browse files
committed
attr_file: ignore macros defined in subdirectories
Right now, we are unconditionally applying all macros found in a gitatttributes file. But quoting gitattributes(5): Custom macro attributes can be defined only in top-level gitattributes files ($GIT_DIR/info/attributes, the .gitattributes file at the top level of the working tree, or the global or system-wide gitattributes files), not in .gitattributes files in working tree subdirectories. The built-in macro attribute "binary" is equivalent to: So gitattribute files in subdirectories of the working tree may explicitly _not_ contain macro definitions, but we do not currently enforce this limitation. This patch introduces a new parameter to the gitattributes parser that tells whether macros are allowed in the current file or not. If set to `false`, we will still parse macros, but silently ignore them instead of adding them to the list of defined macros. Update all callers to correctly determine whether the to-be-parsed file may contain macros or not. Most importantly, when walking up the directory hierarchy, we will only set it to `true` once it reaches the root directory of the repo itself. Add a test that verifies that we are indeed not applying macros from subdirectories. Previous to these changes, the test would've failed.
1 parent 9796852 commit f834690

File tree

8 files changed

+66
-40
lines changed

8 files changed

+66
-40
lines changed

src/attr.c

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -252,15 +252,16 @@ static int preload_attr_file(
252252
git_attr_session *attr_session,
253253
git_attr_file_source source,
254254
const char *base,
255-
const char *file)
255+
const char *file,
256+
bool allow_macros)
256257
{
257258
int error;
258259
git_attr_file *preload = NULL;
259260

260261
if (!file)
261262
return 0;
262-
if (!(error = git_attr_cache__get(
263-
&preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer)))
263+
if (!(error = git_attr_cache__get(&preload, repo, attr_session, source, base, file,
264+
git_attr_file__parse_buffer, allow_macros)))
264265
git_attr_file__free(preload);
265266

266267
return error;
@@ -324,31 +325,31 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session)
324325

325326
if ((error = system_attr_file(&path, attr_session)) < 0 ||
326327
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
327-
NULL, path.ptr)) < 0) {
328+
NULL, path.ptr, true)) < 0) {
328329
if (error != GIT_ENOTFOUND)
329330
goto out;
330331
}
331332

332333
if ((error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
333-
NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
334+
NULL, git_repository_attr_cache(repo)->cfg_attr_file, true)) < 0)
334335
goto out;
335336

336337
git_buf_clear(&path); /* git_repository_item_path expects an empty buffer, because it uses git_buf_set */
337338
if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
338339
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
339-
path.ptr, GIT_ATTR_FILE_INREPO)) < 0) {
340+
path.ptr, GIT_ATTR_FILE_INREPO, true)) < 0) {
340341
if (error != GIT_ENOTFOUND)
341342
goto out;
342343
}
343344

344345
if ((workdir = git_repository_workdir(repo)) != NULL &&
345346
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
346-
workdir, GIT_ATTR_FILE)) < 0)
347+
workdir, GIT_ATTR_FILE, true)) < 0)
347348
goto out;
348349

349350
if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
350351
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_INDEX,
351-
NULL, GIT_ATTR_FILE)) < 0)
352+
NULL, GIT_ATTR_FILE, true)) < 0)
352353
goto out;
353354

354355
if (attr_session)
@@ -436,13 +437,14 @@ static int push_attr_file(
436437
git_vector *list,
437438
git_attr_file_source source,
438439
const char *base,
439-
const char *filename)
440+
const char *filename,
441+
bool allow_macros)
440442
{
441443
int error = 0;
442444
git_attr_file *file = NULL;
443445

444446
error = git_attr_cache__get(&file, repo, attr_session,
445-
source, base, filename, git_attr_file__parse_buffer);
447+
source, base, filename, git_attr_file__parse_buffer, allow_macros);
446448

447449
if (error < 0)
448450
return error;
@@ -457,16 +459,18 @@ static int push_attr_file(
457459

458460
static int push_one_attr(void *ref, const char *path)
459461
{
460-
int error = 0, n_src, i;
461462
attr_walk_up_info *info = (attr_walk_up_info *)ref;
462463
git_attr_file_source src[2];
464+
int error = 0, n_src, i;
465+
bool allow_macros;
463466

464467
n_src = attr_decide_sources(
465468
info->flags, info->workdir != NULL, info->index != NULL, src);
469+
allow_macros = info->workdir ? !strcmp(info->workdir, path) : false;
466470

467471
for (i = 0; !error && i < n_src; ++i)
468-
error = push_attr_file(info->repo, info->attr_session,
469-
info->files, src[i], path, GIT_ATTR_FILE);
472+
error = push_attr_file(info->repo, info->attr_session, info->files,
473+
src[i], path, GIT_ATTR_FILE, allow_macros);
470474

471475
return error;
472476
}
@@ -515,7 +519,7 @@ static int collect_attr_files(
515519

516520
if ((error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
517521
(error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
518-
attrfile.ptr, GIT_ATTR_FILE_INREPO)) < 0) {
522+
attrfile.ptr, GIT_ATTR_FILE_INREPO, true)) < 0) {
519523
if (error != GIT_ENOTFOUND)
520524
goto cleanup;
521525
}
@@ -537,9 +541,8 @@ static int collect_attr_files(
537541
goto cleanup;
538542

539543
if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
540-
error = push_attr_file(
541-
repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
542-
NULL, git_repository_attr_cache(repo)->cfg_attr_file);
544+
error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
545+
NULL, git_repository_attr_cache(repo)->cfg_attr_file, true);
543546
if (error < 0)
544547
goto cleanup;
545548
}
@@ -548,9 +551,8 @@ static int collect_attr_files(
548551
error = system_attr_file(&dir, attr_session);
549552

550553
if (!error)
551-
error = push_attr_file(
552-
repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
553-
NULL, dir.ptr);
554+
error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
555+
NULL, dir.ptr, true);
554556
else if (error == GIT_ENOTFOUND)
555557
error = 0;
556558
}

src/attr_file.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ int git_attr_file__load(
105105
git_attr_session *attr_session,
106106
git_attr_file_entry *entry,
107107
git_attr_file_source source,
108-
git_attr_file_parser parser)
108+
git_attr_file_parser parser,
109+
bool allow_macros)
109110
{
110111
int error = 0;
111112
git_blob *blob = NULL;
@@ -177,7 +178,7 @@ int git_attr_file__load(
177178
if (attr_session)
178179
file->session_key = attr_session->key;
179180

180-
if (parser && (error = parser(repo, file, content_str)) < 0) {
181+
if (parser && (error = parser(repo, file, content_str, allow_macros)) < 0) {
181182
git_attr_file__free(file);
182183
goto cleanup;
183184
}
@@ -249,7 +250,7 @@ static bool parse_optimized_patterns(
249250
const char *pattern);
250251

251252
int git_attr_file__parse_buffer(
252-
git_repository *repo, git_attr_file *attrs, const char *data)
253+
git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros)
253254
{
254255
const char *scan = data, *context = NULL;
255256
git_attr_rule *rule = NULL;
@@ -287,6 +288,8 @@ int git_attr_file__parse_buffer(
287288

288289
if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) {
289290
/* TODO: warning if macro found in file below repo root */
291+
if (!allow_macros)
292+
continue;
290293
if ((error = git_attr_cache__insert_macro(repo, rule)) < 0)
291294
goto out;
292295
} else if ((error = git_vector_insert(&attrs->rules, rule)) < 0)
@@ -355,7 +358,7 @@ int git_attr_file__load_standalone(git_attr_file **out, const char *path)
355358
*/
356359

357360
if ((error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE)) < 0 ||
358-
(error = git_attr_file__parse_buffer(NULL, file, content.ptr)) < 0 ||
361+
(error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 ||
359362
(error = git_attr_cache__alloc_file_entry(&file->entry, NULL, path, &file->pool)) < 0)
360363
goto out;
361364

src/attr_file.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ extern int git_attr_get_many_with_session(
131131
typedef int (*git_attr_file_parser)(
132132
git_repository *repo,
133133
git_attr_file *file,
134-
const char *data);
134+
const char *data,
135+
bool allow_macros);
135136

136137
/*
137138
* git_attr_file API
@@ -150,7 +151,8 @@ int git_attr_file__load(
150151
git_attr_session *attr_session,
151152
git_attr_file_entry *ce,
152153
git_attr_file_source source,
153-
git_attr_file_parser parser);
154+
git_attr_file_parser parser,
155+
bool allow_macros);
154156

155157
int git_attr_file__load_standalone(
156158
git_attr_file **out, const char *path);
@@ -159,7 +161,7 @@ int git_attr_file__out_of_date(
159161
git_repository *repo, git_attr_session *session, git_attr_file *file);
160162

161163
int git_attr_file__parse_buffer(
162-
git_repository *repo, git_attr_file *attrs, const char *data);
164+
git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros);
163165

164166
int git_attr_file__clear_rules(
165167
git_attr_file *file, bool need_lock);

src/attrcache.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ int git_attr_cache__get(
208208
git_attr_file_source source,
209209
const char *base,
210210
const char *filename,
211-
git_attr_file_parser parser)
211+
git_attr_file_parser parser,
212+
bool allow_macros)
212213
{
213214
int error = 0;
214215
git_attr_cache *cache = git_repository_attr_cache(repo);
@@ -221,7 +222,7 @@ int git_attr_cache__get(
221222

222223
/* load file if we don't have one or if existing one is out of date */
223224
if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0)
224-
error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser);
225+
error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser, allow_macros);
225226

226227
/* if we loaded the file, insert into and/or update cache */
227228
if (updated) {

src/attrcache.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ extern int git_attr_cache__get(
3434
git_attr_file_source source,
3535
const char *base,
3636
const char *filename,
37-
git_attr_file_parser parser);
37+
git_attr_file_parser parser,
38+
bool allow_macros);
3839

3940
extern bool git_attr_cache__is_cached(
4041
git_repository *repo,

src/ignore.c

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,15 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
163163
}
164164

165165
static int parse_ignore_file(
166-
git_repository *repo, git_attr_file *attrs, const char *data)
166+
git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros)
167167
{
168168
int error = 0;
169169
int ignore_case = false;
170170
const char *scan = data, *context = NULL;
171171
git_attr_fnmatch *match = NULL;
172172

173+
GIT_UNUSED(allow_macros);
174+
173175
if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
174176
git_error_clear();
175177

@@ -244,9 +246,8 @@ static int push_ignore_file(
244246
int error = 0;
245247
git_attr_file *file = NULL;
246248

247-
error = git_attr_cache__get(
248-
&file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE,
249-
base, filename, parse_ignore_file);
249+
error = git_attr_cache__get(&file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE,
250+
base, filename, parse_ignore_file, false);
250251
if (error < 0)
251252
return error;
252253

@@ -272,12 +273,12 @@ static int get_internal_ignores(git_attr_file **out, git_repository *repo)
272273
if ((error = git_attr_cache__init(repo)) < 0)
273274
return error;
274275

275-
error = git_attr_cache__get(
276-
out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL);
276+
error = git_attr_cache__get(out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL,
277+
GIT_IGNORE_INTERNAL, NULL, false);
277278

278279
/* if internal rules list is empty, insert default rules */
279280
if (!error && !(*out)->rules.length)
280-
error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES);
281+
error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, false);
281282

282283
return error;
283284
}
@@ -487,7 +488,7 @@ int git_ignore_add_rule(git_repository *repo, const char *rules)
487488
if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
488489
return error;
489490

490-
error = parse_ignore_file(repo, ign_internal, rules);
491+
error = parse_ignore_file(repo, ign_internal, rules, false);
491492
git_attr_file__free(ign_internal);
492493

493494
return error;
@@ -503,7 +504,7 @@ int git_ignore_clear_internal_rules(git_repository *repo)
503504

504505
if (!(error = git_attr_file__clear_rules(ign_internal, true)))
505506
error = parse_ignore_file(
506-
repo, ign_internal, GIT_IGNORE_DEFAULT_RULES);
507+
repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false);
507508

508509
git_attr_file__free(ign_internal);
509510
return error;

tests/attr/lookup.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ void test_attr_lookup__from_buffer(void)
252252

253253
cl_git_pass(git_attr_file__new(&file, NULL, 0));
254254

255-
cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz"));
255+
cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz", true));
256256

257257
cl_assert(file->rules.length == 3);
258258

tests/attr/macro.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,22 @@ void test_attr_macro__changing_macro_in_root_wd_updates_attributes(void)
125125
cl_assert_equal_s(value, "second");
126126
}
127127

128+
void test_attr_macro__macros_in_subdir_do_not_apply(void)
129+
{
130+
const char *value;
131+
132+
g_repo = cl_git_sandbox_init("empty_standard_repo");
133+
134+
cl_git_pass(p_mkdir("empty_standard_repo/dir", 0777));
135+
cl_git_rewritefile("empty_standard_repo/dir/.gitattributes",
136+
"[attr]customattr key=value\n"
137+
"file customattr\n");
138+
139+
/* This should _not_ pass, as macros in subdirectories shall be ignored */
140+
cl_git_pass(git_attr_get(&value, g_repo, 0, "dir/file", "key"));
141+
cl_assert_equal_p(value, NULL);
142+
}
143+
128144
void test_attr_macro__adding_macro_succeeds(void)
129145
{
130146
const char *value;

0 commit comments

Comments
 (0)