Skip to content

Commit 515106f

Browse files
author
Junio C Hamano
committed
Allow more than true/false to attributes.
This allows you to define three values (and possibly more) to each attribute: true, false, and unset. Typically the handlers that notice and act on attribute values treat "unset" attribute to mean "do your default thing" (e.g. crlf that is unset would trigger "guess from contents"), so being able to override a setting to an unset state is actually useful. - If you want to set the attribute value to true, have an entry in .gitattributes file that mentions the attribute name; e.g. *.o binary - If you want to set the attribute value explicitly to false, use '-'; e.g. *.a -diff - If you want to make the attribute value _unset_, perhaps to override an earlier entry, use '!'; e.g. *.a -diff c.i.a !diff This also allows string values to attributes, with the natural syntax: attrname=attrvalue but you cannot use it, as nobody takes notice and acts on it yet. Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent b568a50 commit 515106f

File tree

5 files changed

+169
-82
lines changed

5 files changed

+169
-82
lines changed

attr.c

Lines changed: 123 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "cache.h"
22
#include "attr.h"
33

4+
#define ATTR__UNKNOWN ((void *) -2)
5+
46
/*
57
* The basic design decision here is that we are not going to have
68
* insanely large number of attributes.
@@ -83,6 +85,7 @@ struct git_attr *git_attr(const char *name, int len)
8385
check_all_attr = xrealloc(check_all_attr,
8486
sizeof(*check_all_attr) * attr_nr);
8587
check_all_attr[a->attr_nr].attr = a;
88+
check_all_attr[a->attr_nr].value = ATTR__UNKNOWN;
8689
return a;
8790
}
8891

@@ -92,12 +95,14 @@ struct git_attr *git_attr(const char *name, int len)
9295
* (1) glob pattern.
9396
* (2) whitespace
9497
* (3) whitespace separated list of attribute names, each of which
95-
* could be prefixed with '-' to mean "not set".
98+
* could be prefixed with '-' to mean "set to false", '!' to mean
99+
* "unset".
96100
*/
97101

102+
/* What does a matched pattern decide? */
98103
struct attr_state {
99-
int unset;
100104
struct git_attr *attr;
105+
void *setto;
101106
};
102107

103108
struct match_attr {
@@ -112,13 +117,63 @@ struct match_attr {
112117

113118
static const char blank[] = " \t\r\n";
114119

120+
static const char *parse_attr(const char *src, int lineno, const char *cp,
121+
int *num_attr, struct match_attr *res)
122+
{
123+
const char *ep, *equals;
124+
int len;
125+
126+
ep = cp + strcspn(cp, blank);
127+
equals = strchr(cp, '=');
128+
if (equals && ep < equals)
129+
equals = NULL;
130+
if (equals)
131+
len = equals - cp;
132+
else
133+
len = ep - cp;
134+
if (!res) {
135+
if (*cp == '-' || *cp == '!') {
136+
cp++;
137+
len--;
138+
}
139+
if (invalid_attr_name(cp, len)) {
140+
fprintf(stderr,
141+
"%.*s is not a valid attribute name: %s:%d\n",
142+
len, cp, src, lineno);
143+
return NULL;
144+
}
145+
} else {
146+
struct attr_state *e;
147+
148+
e = &(res->state[*num_attr]);
149+
if (*cp == '-' || *cp == '!') {
150+
e->setto = (*cp == '-') ? ATTR__FALSE : ATTR__UNSET;
151+
cp++;
152+
len--;
153+
}
154+
else if (!equals)
155+
e->setto = ATTR__TRUE;
156+
else {
157+
char *value;
158+
int vallen = ep - equals;
159+
value = xmalloc(vallen);
160+
memcpy(value, equals+1, vallen-1);
161+
value[vallen-1] = 0;
162+
e->setto = value;
163+
}
164+
e->attr = git_attr(cp, len);
165+
}
166+
(*num_attr)++;
167+
return ep + strspn(ep, blank);
168+
}
169+
115170
static struct match_attr *parse_attr_line(const char *line, const char *src,
116171
int lineno, int macro_ok)
117172
{
118173
int namelen;
119174
int num_attr;
120175
const char *cp, *name;
121-
struct match_attr *res = res;
176+
struct match_attr *res = NULL;
122177
int pass;
123178
int is_macro;
124179

@@ -153,42 +208,16 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
153208
num_attr = 0;
154209
cp = name + namelen;
155210
cp = cp + strspn(cp, blank);
156-
while (*cp) {
157-
const char *ep;
158-
ep = cp + strcspn(cp, blank);
159-
if (!pass) {
160-
if (*cp == '-')
161-
cp++;
162-
if (invalid_attr_name(cp, ep - cp)) {
163-
fprintf(stderr,
164-
"%.*s is not a valid attribute name: %s:%d\n",
165-
(int)(ep - cp), cp,
166-
src, lineno);
167-
return NULL;
168-
}
169-
} else {
170-
struct attr_state *e;
171-
172-
e = &(res->state[num_attr]);
173-
if (*cp == '-') {
174-
e->unset = 1;
175-
cp++;
176-
}
177-
e->attr = git_attr(cp, ep - cp);
178-
}
179-
num_attr++;
180-
cp = ep + strspn(ep, blank);
181-
}
211+
while (*cp)
212+
cp = parse_attr(src, lineno, cp, &num_attr, res);
182213
if (pass)
183214
break;
184-
185215
res = xcalloc(1,
186216
sizeof(*res) +
187217
sizeof(struct attr_state) * num_attr +
188218
(is_macro ? 0 : namelen + 1));
189-
if (is_macro) {
219+
if (is_macro)
190220
res->u.attr = git_attr(name, namelen);
191-
}
192221
else {
193222
res->u.pattern = (char*)&(res->state[num_attr]);
194223
memcpy(res->u.pattern, name, namelen);
@@ -205,9 +234,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
205234
* come from many places.
206235
*
207236
* (1) .gitattribute file of the same directory;
208-
* (2) .gitattribute file of the parent directory if (1) does not have any match;
209-
* this goes recursively upwards, just like .gitignore
210-
* (3) perhaps $GIT_DIR/info/attributes, as the final fallback.
237+
* (2) .gitattribute file of the parent directory if (1) does not have
238+
* any match; this goes recursively upwards, just like .gitignore.
239+
* (3) $GIT_DIR/info/attributes, which overrides both of the above.
211240
*
212241
* In the same file, later entries override the earlier match, so in the
213242
* global list, we would have entries from info/attributes the earliest
@@ -229,8 +258,21 @@ static void free_attr_elem(struct attr_stack *e)
229258
{
230259
int i;
231260
free(e->origin);
232-
for (i = 0; i < e->num_matches; i++)
233-
free(e->attrs[i]);
261+
for (i = 0; i < e->num_matches; i++) {
262+
struct match_attr *a = e->attrs[i];
263+
int j;
264+
for (j = 0; j < a->num_attr; j++) {
265+
void *setto = a->state[j].setto;
266+
if (setto == ATTR__TRUE ||
267+
setto == ATTR__FALSE ||
268+
setto == ATTR__UNSET ||
269+
setto == ATTR__UNKNOWN)
270+
;
271+
else
272+
free(setto);
273+
}
274+
free(a);
275+
}
234276
free(e);
235277
}
236278

@@ -288,10 +330,19 @@ static void debug_info(const char *what, struct attr_stack *elem)
288330
{
289331
fprintf(stderr, "%s: %s\n", what, elem->origin ? elem->origin : "()");
290332
}
291-
static void debug_set(const char *what, const char *match, struct git_attr *attr, int set)
333+
static void debug_set(const char *what, const char *match, struct git_attr *attr, void *v)
292334
{
293-
fprintf(stderr, "%s: %s => %d (%s)\n",
294-
what, attr->name, set, match);
335+
const char *value = v;
336+
337+
if (ATTR_TRUE(value))
338+
value = "set";
339+
else if (ATTR_FALSE(value))
340+
value = "unset";
341+
else if (ATTR_UNSET(value))
342+
value = "unspecified";
343+
344+
fprintf(stderr, "%s: %s => %s (%s)\n",
345+
what, attr->name, (char *) value, match);
295346
}
296347
#define debug_push(a) debug_info("push", (a))
297348
#define debug_pop(a) debug_info("pop", (a))
@@ -420,56 +471,53 @@ static int path_matches(const char *pathname, int pathlen,
420471
return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
421472
}
422473

474+
static int fill_one(const char *what, struct match_attr *a, int rem)
475+
{
476+
struct git_attr_check *check = check_all_attr;
477+
int i;
478+
479+
for (i = 0; 0 < rem && i < a->num_attr; i++) {
480+
struct git_attr *attr = a->state[i].attr;
481+
void **n = &(check[attr->attr_nr].value);
482+
void *v = a->state[i].setto;
483+
484+
if (*n == ATTR__UNKNOWN) {
485+
debug_set(what, a->u.pattern, attr, v);
486+
*n = v;
487+
rem--;
488+
}
489+
}
490+
return rem;
491+
}
492+
423493
static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem)
424494
{
495+
int i;
425496
const char *base = stk->origin ? stk->origin : "";
426-
int i, j;
427-
struct git_attr_check *check = check_all_attr;
428497

429498
for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
430499
struct match_attr *a = stk->attrs[i];
431500
if (a->is_macro)
432501
continue;
433502
if (path_matches(path, pathlen,
434-
a->u.pattern, base, strlen(base))) {
435-
for (j = 0; 0 < rem && j < a->num_attr; j++) {
436-
struct git_attr *attr = a->state[j].attr;
437-
int set = !a->state[j].unset;
438-
int *n = &(check[attr->attr_nr].isset);
439-
440-
if (*n < 0) {
441-
debug_set("fill", a->u.pattern, attr, set);
442-
*n = set;
443-
rem--;
444-
}
445-
}
446-
}
503+
a->u.pattern, base, strlen(base)))
504+
rem = fill_one("fill", a, rem);
447505
}
448506
return rem;
449507
}
450508

451509
static int macroexpand(struct attr_stack *stk, int rem)
452510
{
453-
int i, j;
511+
int i;
454512
struct git_attr_check *check = check_all_attr;
455513

456514
for (i = stk->num_matches - 1; 0 < rem && 0 <= i; i--) {
457515
struct match_attr *a = stk->attrs[i];
458516
if (!a->is_macro)
459517
continue;
460-
if (check[a->u.attr->attr_nr].isset < 0)
518+
if (check[a->u.attr->attr_nr].value != ATTR__TRUE)
461519
continue;
462-
for (j = 0; 0 < rem && j < a->num_attr; j++) {
463-
struct git_attr *attr = a->state[j].attr;
464-
int set = !a->state[j].unset;
465-
int *n = &(check[attr->attr_nr].isset);
466-
467-
if (*n < 0) {
468-
debug_set("expand", a->u.attr->name, attr, set);
469-
*n = set;
470-
rem--;
471-
}
472-
}
520+
rem = fill_one("expand", a, rem);
473521
}
474522
return rem;
475523
}
@@ -482,7 +530,7 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check)
482530

483531
bootstrap_attr_stack();
484532
for (i = 0; i < attr_nr; i++)
485-
check_all_attr[i].isset = -1;
533+
check_all_attr[i].value = ATTR__UNKNOWN;
486534

487535
pathlen = strlen(path);
488536
cp = strrchr(path, '/');
@@ -498,8 +546,12 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check)
498546
for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
499547
rem = macroexpand(stk, rem);
500548

501-
for (i = 0; i < num; i++)
502-
check[i].isset = check_all_attr[check[i].attr->attr_nr].isset;
549+
for (i = 0; i < num; i++) {
550+
void *value = check_all_attr[check[i].attr->attr_nr].value;
551+
if (value == ATTR__UNKNOWN)
552+
value = ATTR__UNSET;
553+
check[i].value = value;
554+
}
503555

504556
return 0;
505557
}

attr.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,19 @@ struct git_attr;
66

77
struct git_attr *git_attr(const char *, int);
88

9+
/* Internal use */
10+
#define ATTR__TRUE ((void *) 1)
11+
#define ATTR__FALSE ((void *) 0)
12+
#define ATTR__UNSET ((void *) -1)
13+
14+
/* For public to check git_attr_check results */
15+
#define ATTR_TRUE(v) ((v) == ATTR__TRUE)
16+
#define ATTR_FALSE(v) ((v) == ATTR__FALSE)
17+
#define ATTR_UNSET(v) ((v) == ATTR__UNSET)
18+
919
struct git_attr_check {
1020
struct git_attr *attr;
11-
int isset;
21+
void *value;
1222
};
1323

1424
int git_checkattr(const char *path, int, struct git_attr_check *);

builtin-check-attr.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,17 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
4242
if (git_checkattr(argv[i], cnt, check))
4343
die("git_checkattr died");
4444
for (j = 0; j < cnt; j++) {
45+
void *value = check[j].value;
46+
47+
if (ATTR_TRUE(value))
48+
value = "set";
49+
else if (ATTR_FALSE(value))
50+
value = "unset";
51+
else if (ATTR_UNSET(value))
52+
value = "unspecified";
53+
4554
write_name_quoted("", 0, argv[i], 1, stdout);
46-
printf(": %s: %s\n", argv[j+1],
47-
(check[j].isset < 0) ? "unspecified" :
48-
(check[j].isset == 0) ? "unset" :
49-
"set");
55+
printf(": %s: %s\n", argv[j+1], (char *) value);
5056
}
5157
}
5258
return 0;

convert.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,19 @@ static int git_path_check_crlf(const char *path)
225225

226226
setup_crlf_check(&attr_crlf_check);
227227

228-
if (git_checkattr(path, 1, &attr_crlf_check))
229-
return -1;
230-
return attr_crlf_check.isset;
228+
if (!git_checkattr(path, 1, &attr_crlf_check)) {
229+
void *value = attr_crlf_check.value;
230+
if (ATTR_TRUE(value))
231+
return 1;
232+
else if (ATTR_FALSE(value))
233+
return 0;
234+
else if (ATTR_UNSET(value))
235+
;
236+
else
237+
die("unknown value %s given to 'crlf' attribute",
238+
(char *)value);
239+
}
240+
return -1;
231241
}
232242

233243
int convert_to_git(const char *path, char **bufp, unsigned long *sizep)

diff.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,9 +1068,18 @@ static int file_is_binary(struct diff_filespec *one)
10681068
struct git_attr_check attr_diff_check;
10691069

10701070
setup_diff_attr_check(&attr_diff_check);
1071-
if (!git_checkattr(one->path, 1, &attr_diff_check) &&
1072-
(0 <= attr_diff_check.isset))
1073-
return !attr_diff_check.isset;
1071+
if (!git_checkattr(one->path, 1, &attr_diff_check)) {
1072+
void *value = attr_diff_check.value;
1073+
if (ATTR_TRUE(value))
1074+
return 0;
1075+
else if (ATTR_FALSE(value))
1076+
return 1;
1077+
else if (ATTR_UNSET(value))
1078+
;
1079+
else
1080+
die("unknown value %s given to 'diff' attribute",
1081+
(char *)value);
1082+
}
10741083

10751084
if (!one->data) {
10761085
if (!DIFF_FILE_VALID(one))

0 commit comments

Comments
 (0)