The gitattributes machinery is being taught to work better in a
multi-threaded environment.

* bw/attr: (27 commits)
  attr: reformat git_attr_set_direction() function
  attr: push the bare repo check into read_attr()
  attr: store attribute stack in attr_check structure
  attr: tighten const correctness with git_attr and match_attr
  attr: remove maybe-real, maybe-macro from git_attr
  attr: eliminate global check_all_attr array
  attr: use hashmap for attribute dictionary
  attr: change validity check for attribute names to use positive logic
  attr: pass struct attr_check to collect_some_attrs
  attr: retire git_check_attrs() API
  attr: convert git_check_attrs() callers to use the new API
  attr: convert git_all_attrs() to use "struct attr_check"
  attr: (re)introduce git_check_attr() and struct attr_check
  attr: rename function and struct related to checking attributes
  attr.c: outline the future plans by heavily commenting
  Documentation: fix a typo
  attr.c: add push_stack() helper
  attr: support quoting pathname patterns in C style
  attr.c: plug small leak in parse_attr_line()
  attr.c: tighten constness around "git_attr" structure
  ...
This commit is contained in:
Junio C Hamano 2017-02-27 13:57:14 -08:00
Родитель fdeb89fdeb f0dd042148
Коммит a04855bae8
14 изменённых файлов: 830 добавлений и 458 удалений

Просмотреть файл

@ -21,9 +21,11 @@ Each line in `gitattributes` file is of form:
pattern attr1 attr2 ...
That is, a pattern followed by an attributes list,
separated by whitespaces. When the pattern matches the
path in question, the attributes listed on the line are given to
the path.
separated by whitespaces. Leading and trailing whitespaces are
ignored. Lines that begin with '#' are ignored. Patterns
that begin with a double quote are quoted in C style.
When the pattern matches the path in question, the attributes
listed on the line are given to the path.
Each attribute can be in one of these states for a given path:
@ -86,7 +88,7 @@ is either not set or empty, $HOME/.config/git/attributes is used instead.
Attributes for all users on a system should be placed in the
`$(prefix)/etc/gitattributes` file.
Sometimes you would need to override an setting of an attribute
Sometimes you would need to override a setting of an attribute
for a path to `Unspecified` state. This can be done by listing
the name of the attribute prefixed with an exclamation point `!`.

Просмотреть файл

@ -16,10 +16,15 @@ Data Structure
of no interest to the calling programs. The name of the
attribute can be retrieved by calling `git_attr_name()`.
`struct git_attr_check`::
`struct attr_check_item`::
This structure represents a set of attributes to check in a call
to `git_check_attr()` function, and receives the results.
This structure represents one attribute and its value.
`struct attr_check`::
This structure represents a collection of `attr_check_item`.
It is passed to `git_check_attr()` function, specifying the
attributes to check, and receives their values.
Attribute Values
@ -27,7 +32,7 @@ Attribute Values
An attribute for a path can be in one of four states: Set, Unset,
Unspecified or set to a string, and `.value` member of `struct
git_attr_check` records it. There are three macros to check these:
attr_check_item` records it. There are three macros to check these:
`ATTR_TRUE()`::
@ -48,49 +53,51 @@ value of the attribute for the path.
Querying Specific Attributes
----------------------------
* Prepare an array of `struct git_attr_check` to define the list of
attributes you would want to check. To populate this array, you would
need to define necessary attributes by calling `git_attr()` function.
* Prepare `struct attr_check` using attr_check_initl()
function, enumerating the names of attributes whose values you are
interested in, terminated with a NULL pointer. Alternatively, an
empty `struct attr_check` can be prepared by calling
`attr_check_alloc()` function and then attributes you want to
ask about can be added to it with `attr_check_append()`
function.
* Call `git_check_attr()` to check the attributes for the path.
* Inspect `git_attr_check` structure to see how each of the attribute in
the array is defined for the path.
* Inspect `attr_check` structure to see how each of the
attribute in the array is defined for the path.
Example
-------
To see how attributes "crlf" and "indent" are set for different paths.
To see how attributes "crlf" and "ident" are set for different paths.
. Prepare an array of `struct git_attr_check` with two elements (because
we are checking two attributes). Initialize their `attr` member with
pointers to `struct git_attr` obtained by calling `git_attr()`:
. Prepare a `struct attr_check` with two elements (because
we are checking two attributes):
------------
static struct git_attr_check check[2];
static struct attr_check *check;
static void setup_check(void)
{
if (check[0].attr)
if (check)
return; /* already done */
check[0].attr = git_attr("crlf");
check[1].attr = git_attr("ident");
check = attr_check_initl("crlf", "ident", NULL);
}
------------
. Call `git_check_attr()` with the prepared array of `struct git_attr_check`:
. Call `git_check_attr()` with the prepared `struct attr_check`:
------------
const char *path;
setup_check();
git_check_attr(path, ARRAY_SIZE(check), check);
git_check_attr(path, check);
------------
. Act on `.value` member of the result, left in `check[]`:
. Act on `.value` member of the result, left in `check->items[]`:
------------
const char *value = check[0].value;
const char *value = check->items[0].value;
if (ATTR_TRUE(value)) {
The attribute is Set, by listing only the name of the
@ -109,20 +116,39 @@ static void setup_check(void)
}
------------
To see how attributes in argv[] are set for different paths, only
the first step in the above would be different.
------------
static struct attr_check *check;
static void setup_check(const char **argv)
{
check = attr_check_alloc();
while (*argv) {
struct git_attr *attr = git_attr(*argv);
attr_check_append(check, attr);
argv++;
}
}
------------
Querying All Attributes
-----------------------
To get the values of all attributes associated with a file:
* Call `git_all_attrs()`, which returns an array of `git_attr_check`
structures.
* Prepare an empty `attr_check` structure by calling
`attr_check_alloc()`.
* Iterate over the `git_attr_check` array to examine the attribute
names and values. The name of the attribute described by a
`git_attr_check` object can be retrieved via
`git_attr_name(check[i].attr)`. (Please note that no items will be
returned for unset attributes, so `ATTR_UNSET()` will return false
for all returned `git_array_check` objects.)
* Call `git_all_attrs()`, which populates the `attr_check`
with the attributes attached to the path.
* Free the `git_array_check` array.
* Iterate over the `attr_check.items[]` array to examine
the attribute names and values. The name of the attribute
described by a `attr_check.items[]` object can be retrieved via
`git_attr_name(check->items[i].attr)`. (Please note that no items
will be returned for unset attributes, so `ATTR_UNSET()` will return
false for all returned `attr_check.items[]` objects.)
* Free the `attr_check` struct by calling `attr_check_free()`.

Просмотреть файл

@ -87,19 +87,6 @@ void *sha1_file_to_archive(const struct archiver_args *args,
return buffer;
}
static void setup_archive_check(struct git_attr_check *check)
{
static struct git_attr *attr_export_ignore;
static struct git_attr *attr_export_subst;
if (!attr_export_ignore) {
attr_export_ignore = git_attr("export-ignore");
attr_export_subst = git_attr("export-subst");
}
check[0].attr = attr_export_ignore;
check[1].attr = attr_export_subst;
}
struct directory {
struct directory *up;
struct object_id oid;
@ -120,10 +107,10 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
void *context)
{
static struct strbuf path = STRBUF_INIT;
static struct attr_check *check;
struct archiver_context *c = context;
struct archiver_args *args = c->args;
write_archive_entry_fn_t write_entry = c->write_entry;
struct git_attr_check check[2];
const char *path_without_prefix;
int err;
@ -137,11 +124,12 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
strbuf_addch(&path, '/');
path_without_prefix = path.buf + args->baselen;
setup_archive_check(check);
if (!git_check_attr(path_without_prefix, ARRAY_SIZE(check), check)) {
if (ATTR_TRUE(check[0].value))
if (!check)
check = attr_check_initl("export-ignore", "export-subst", NULL);
if (!git_check_attr(path_without_prefix, check)) {
if (ATTR_TRUE(check->items[0].value))
return 0;
args->convert = ATTR_TRUE(check[1].value);
args->convert = ATTR_TRUE(check->items[1].value);
}
if (S_ISDIR(mode) || S_ISGITLINK(mode)) {

906
attr.c

Разница между файлами не показана из-за своего большого размера Загрузить разницу

49
attr.h
Просмотреть файл

@ -4,11 +4,15 @@
/* An attribute is a pointer to this opaque structure */
struct git_attr;
/* opaque structures used internally for attribute collection */
struct all_attrs_item;
struct attr_stack;
/*
* Given a string, return the gitattribute object that
* corresponds to it.
*/
struct git_attr *git_attr(const char *);
const struct git_attr *git_attr(const char *);
/* Internal use */
extern const char git_attr__true[];
@ -20,38 +24,57 @@ extern const char git_attr__false[];
#define ATTR_UNSET(v) ((v) == NULL)
/*
* Send one or more git_attr_check to git_check_attr(), and
* Send one or more git_attr_check to git_check_attrs(), and
* each 'value' member tells what its value is.
* Unset one is returned as NULL.
*/
struct git_attr_check {
struct git_attr *attr;
struct attr_check_item {
const struct git_attr *attr;
const char *value;
};
struct attr_check {
int nr;
int alloc;
struct attr_check_item *items;
int all_attrs_nr;
struct all_attrs_item *all_attrs;
struct attr_stack *stack;
};
extern struct attr_check *attr_check_alloc(void);
extern struct attr_check *attr_check_initl(const char *, ...);
extern struct attr_check_item *attr_check_append(struct attr_check *check,
const struct git_attr *attr);
extern void attr_check_reset(struct attr_check *check);
extern void attr_check_clear(struct attr_check *check);
extern void attr_check_free(struct attr_check *check);
/*
* Return the name of the attribute represented by the argument. The
* return value is a pointer to a null-delimited string that is part
* of the internal data structure; it should not be modified or freed.
*/
char *git_attr_name(struct git_attr *);
extern const char *git_attr_name(const struct git_attr *);
int git_check_attr(const char *path, int, struct git_attr_check *);
extern int git_check_attr(const char *path, struct attr_check *check);
/*
* Retrieve all attributes that apply to the specified path. *num
* will be set to the number of attributes on the path; **check will
* be set to point at a newly-allocated array of git_attr_check
* objects describing the attributes and their values. *check must be
* free()ed by the caller.
* Retrieve all attributes that apply to the specified path.
* check holds the attributes and their values.
*/
int git_all_attrs(const char *path, int *num, struct git_attr_check **check);
extern void git_all_attrs(const char *path, struct attr_check *check);
enum git_attr_direction {
GIT_ATTR_CHECKIN,
GIT_ATTR_CHECKOUT,
GIT_ATTR_INDEX
};
void git_attr_set_direction(enum git_attr_direction, struct index_state *);
void git_attr_set_direction(enum git_attr_direction new_direction,
struct index_state *istate);
extern void attr_start(void);
#endif /* ATTR_H */

Просмотреть файл

@ -24,12 +24,13 @@ static const struct option check_attr_options[] = {
OPT_END()
};
static void output_attr(int cnt, struct git_attr_check *check,
const char *file)
static void output_attr(struct attr_check *check, const char *file)
{
int j;
int cnt = check->nr;
for (j = 0; j < cnt; j++) {
const char *value = check[j].value;
const char *value = check->items[j].value;
if (ATTR_TRUE(value))
value = "set";
@ -42,35 +43,38 @@ static void output_attr(int cnt, struct git_attr_check *check,
printf("%s%c" /* path */
"%s%c" /* attrname */
"%s%c" /* attrvalue */,
file, 0, git_attr_name(check[j].attr), 0, value, 0);
file, 0,
git_attr_name(check->items[j].attr), 0, value, 0);
} else {
quote_c_style(file, NULL, stdout, 0);
printf(": %s: %s\n", git_attr_name(check[j].attr), value);
printf(": %s: %s\n",
git_attr_name(check->items[j].attr), value);
}
}
}
static void check_attr(const char *prefix, int cnt,
struct git_attr_check *check, const char *file)
static void check_attr(const char *prefix,
struct attr_check *check,
int collect_all,
const char *file)
{
char *full_path =
prefix_path(prefix, prefix ? strlen(prefix) : 0, file);
if (check != NULL) {
if (git_check_attr(full_path, cnt, check))
die("git_check_attr died");
output_attr(cnt, check, file);
if (collect_all) {
git_all_attrs(full_path, check);
} else {
if (git_all_attrs(full_path, &cnt, &check))
die("git_all_attrs died");
output_attr(cnt, check, file);
free(check);
if (git_check_attr(full_path, check))
die("git_check_attr died");
}
output_attr(check, file);
free(full_path);
}
static void check_attr_stdin_paths(const char *prefix, int cnt,
struct git_attr_check *check)
static void check_attr_stdin_paths(const char *prefix,
struct attr_check *check,
int collect_all)
{
struct strbuf buf = STRBUF_INIT;
struct strbuf unquoted = STRBUF_INIT;
@ -84,7 +88,7 @@ static void check_attr_stdin_paths(const char *prefix, int cnt,
die("line is badly quoted");
strbuf_swap(&buf, &unquoted);
}
check_attr(prefix, cnt, check, buf.buf);
check_attr(prefix, check, collect_all, buf.buf);
maybe_flush_or_die(stdout, "attribute to stdout");
}
strbuf_release(&buf);
@ -99,7 +103,7 @@ static NORETURN void error_with_usage(const char *msg)
int cmd_check_attr(int argc, const char **argv, const char *prefix)
{
struct git_attr_check *check;
struct attr_check *check;
int cnt, i, doubledash, filei;
if (!is_bare_repository())
@ -159,28 +163,26 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
error_with_usage("No file specified");
}
if (all_attrs) {
check = NULL;
} else {
check = xcalloc(cnt, sizeof(*check));
check = attr_check_alloc();
if (!all_attrs) {
for (i = 0; i < cnt; i++) {
const char *name;
struct git_attr *a;
name = argv[i];
a = git_attr(name);
const struct git_attr *a = git_attr(argv[i]);
if (!a)
return error("%s: not a valid attribute name",
name);
check[i].attr = a;
argv[i]);
attr_check_append(check, a);
}
}
if (stdin_paths)
check_attr_stdin_paths(prefix, cnt, check);
check_attr_stdin_paths(prefix, check, all_attrs);
else {
for (i = filei; i < argc; i++)
check_attr(prefix, cnt, check, argv[i]);
check_attr(prefix, check, all_attrs, argv[i]);
maybe_flush_or_die(stdout, "attribute to stdout");
}
attr_check_free(check);
return 0;
}

Просмотреть файл

@ -894,24 +894,15 @@ static void write_pack_file(void)
written, nr_result);
}
static void setup_delta_attr_check(struct git_attr_check *check)
{
static struct git_attr *attr_delta;
if (!attr_delta)
attr_delta = git_attr("delta");
check[0].attr = attr_delta;
}
static int no_try_delta(const char *path)
{
struct git_attr_check check[1];
static struct attr_check *check;
setup_delta_attr_check(check);
if (git_check_attr(path, ARRAY_SIZE(check), check))
if (!check)
check = attr_check_initl("delta", NULL);
if (git_check_attr(path, check))
return 0;
if (ATTR_FALSE(check->value))
if (ATTR_FALSE(check->items[0].value))
return 1;
return 0;
}

Просмотреть файл

@ -415,8 +415,7 @@ int find_commit_subject(const char *commit_buffer, const char **subject)
p++;
if (*p) {
p = skip_blank_lines(p + 2);
for (eol = p; *eol && *eol != '\n'; eol++)
; /* do nothing */
eol = strchrnul(p, '\n');
} else
eol = p;

Просмотреть файл

@ -1,5 +1,6 @@
#include "cache.h"
#include "exec_cmd.h"
#include "attr.h"
/*
* Many parts of Git have subprograms communicate via pipe, expect the
@ -33,6 +34,8 @@ int main(int argc, const char **argv)
git_setup_gettext();
attr_start();
git_extract_argv0_path(argv[0]);
restore_sigpipe_to_default();

Просмотреть файл

@ -1028,7 +1028,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
return 1;
}
static enum crlf_action git_path_check_crlf(struct git_attr_check *check)
static enum crlf_action git_path_check_crlf(struct attr_check_item *check)
{
const char *value = check->value;
@ -1045,7 +1045,7 @@ static enum crlf_action git_path_check_crlf(struct git_attr_check *check)
return CRLF_UNDEFINED;
}
static enum eol git_path_check_eol(struct git_attr_check *check)
static enum eol git_path_check_eol(struct attr_check_item *check)
{
const char *value = check->value;
@ -1058,7 +1058,7 @@ static enum eol git_path_check_eol(struct git_attr_check *check)
return EOL_UNSET;
}
static struct convert_driver *git_path_check_convert(struct git_attr_check *check)
static struct convert_driver *git_path_check_convert(struct attr_check_item *check)
{
const char *value = check->value;
struct convert_driver *drv;
@ -1071,7 +1071,7 @@ static struct convert_driver *git_path_check_convert(struct git_attr_check *chec
return NULL;
}
static int git_path_check_ident(struct git_attr_check *check)
static int git_path_check_ident(struct attr_check_item *check)
{
const char *value = check->value;
@ -1085,24 +1085,19 @@ struct conv_attrs {
int ident;
};
static const char *conv_attr_name[] = {
"crlf", "ident", "filter", "eol", "text",
};
#define NUM_CONV_ATTRS ARRAY_SIZE(conv_attr_name)
static void convert_attrs(struct conv_attrs *ca, const char *path)
{
int i;
static struct git_attr_check ccheck[NUM_CONV_ATTRS];
static struct attr_check *check;
if (!ccheck[0].attr) {
for (i = 0; i < NUM_CONV_ATTRS; i++)
ccheck[i].attr = git_attr(conv_attr_name[i]);
if (!check) {
check = attr_check_initl("crlf", "ident", "filter",
"eol", "text", NULL);
user_convert_tail = &user_convert;
git_config(read_convert_config, NULL);
}
if (!git_check_attr(path, NUM_CONV_ATTRS, ccheck)) {
if (!git_check_attr(path, check)) {
struct attr_check_item *ccheck = check->items;
ca->crlf_action = git_path_check_crlf(ccheck + 4);
if (ca->crlf_action == CRLF_UNDEFINED)
ca->crlf_action = git_path_check_crlf(ccheck + 0);

Просмотреть файл

@ -336,15 +336,6 @@ static const struct ll_merge_driver *find_ll_merge_driver(const char *merge_attr
return &ll_merge_drv[LL_TEXT_MERGE];
}
static int git_path_check_merge(const char *path, struct git_attr_check check[2])
{
if (!check[0].attr) {
check[0].attr = git_attr("merge");
check[1].attr = git_attr("conflict-marker-size");
}
return git_check_attr(path, 2, check);
}
static void normalize_file(mmfile_t *mm, const char *path)
{
struct strbuf strbuf = STRBUF_INIT;
@ -362,7 +353,7 @@ int ll_merge(mmbuffer_t *result_buf,
mmfile_t *theirs, const char *their_label,
const struct ll_merge_options *opts)
{
static struct git_attr_check check[2];
static struct attr_check *check;
static const struct ll_merge_options default_opts;
const char *ll_driver_name = NULL;
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
@ -376,10 +367,14 @@ int ll_merge(mmbuffer_t *result_buf,
normalize_file(ours, path);
normalize_file(theirs, path);
}
if (!git_path_check_merge(path, check)) {
ll_driver_name = check[0].value;
if (check[1].value) {
marker_size = atoi(check[1].value);
if (!check)
check = attr_check_initl("merge", "conflict-marker-size", NULL);
if (!git_check_attr(path, check)) {
ll_driver_name = check->items[0].value;
if (check->items[1].value) {
marker_size = atoi(check->items[1].value);
if (marker_size <= 0)
marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
}
@ -398,13 +393,13 @@ int ll_merge(mmbuffer_t *result_buf,
int ll_merge_marker_size(const char *path)
{
static struct git_attr_check check;
static struct attr_check *check;
int marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
if (!check.attr)
check.attr = git_attr("conflict-marker-size");
if (!git_check_attr(path, 1, &check) && check.value) {
marker_size = atoi(check.value);
if (!check)
check = attr_check_initl("conflict-marker-size", NULL);
if (!git_check_attr(path, check) && check->items[0].value) {
marker_size = atoi(check->items[0].value);
if (marker_size <= 0)
marker_size = DEFAULT_CONFLICT_MARKER_SIZE;
}

Просмотреть файл

@ -13,10 +13,31 @@ attr_check () {
test_line_count = 0 err
}
attr_check_quote () {
path="$1"
quoted_path="$2"
expect="$3"
git check-attr test -- "$path" >actual &&
echo "\"$quoted_path\": test: $expect" >expect &&
test_cmp expect actual
}
test_expect_success 'open-quoted pathname' '
echo "\"a test=a" >.gitattributes &&
test_must_fail attr_check a a
'
test_expect_success 'setup' '
mkdir -p a/b/d a/c b &&
(
echo "[attr]notest !test"
echo "\" d \" test=d"
echo " e test=e"
echo " e\" test=e"
echo "f test=f"
echo "a/i test=a/i"
echo "onoff test -test"
@ -69,6 +90,11 @@ test_expect_success 'command line checks' '
'
test_expect_success 'attribute test' '
attr_check " d " d &&
attr_check e e &&
attr_check_quote e\" e\\\" e &&
attr_check f f &&
attr_check a/f f &&
attr_check a/c/f f &&

Просмотреть файл

@ -262,25 +262,22 @@ struct userdiff_driver *userdiff_find_by_name(const char *name) {
struct userdiff_driver *userdiff_find_by_path(const char *path)
{
static struct git_attr *attr;
struct git_attr_check check;
if (!attr)
attr = git_attr("diff");
check.attr = attr;
static struct attr_check *check;
if (!check)
check = attr_check_initl("diff", NULL);
if (!path)
return NULL;
if (git_check_attr(path, 1, &check))
if (git_check_attr(path, check))
return NULL;
if (ATTR_TRUE(check.value))
if (ATTR_TRUE(check->items[0].value))
return &driver_true;
if (ATTR_FALSE(check.value))
if (ATTR_FALSE(check->items[0].value))
return &driver_false;
if (ATTR_UNSET(check.value))
if (ATTR_UNSET(check->items[0].value))
return NULL;
return userdiff_find_by_name(check.value);
return userdiff_find_by_name(check->items[0].value);
}
struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver)

19
ws.c
Просмотреть файл

@ -71,24 +71,17 @@ unsigned parse_whitespace_rule(const char *string)
return rule;
}
static void setup_whitespace_attr_check(struct git_attr_check *check)
{
static struct git_attr *attr_whitespace;
if (!attr_whitespace)
attr_whitespace = git_attr("whitespace");
check[0].attr = attr_whitespace;
}
unsigned whitespace_rule(const char *pathname)
{
struct git_attr_check attr_whitespace_rule;
static struct attr_check *attr_whitespace_rule;
setup_whitespace_attr_check(&attr_whitespace_rule);
if (!git_check_attr(pathname, 1, &attr_whitespace_rule)) {
if (!attr_whitespace_rule)
attr_whitespace_rule = attr_check_initl("whitespace", NULL);
if (!git_check_attr(pathname, attr_whitespace_rule)) {
const char *value;
value = attr_whitespace_rule.value;
value = attr_whitespace_rule->items[0].value;
if (ATTR_TRUE(value)) {
/* true (whitespace) */
unsigned all_rule = ws_tab_width(whitespace_rule_cfg);