Commit 3779a04794333a0152a1d7ae39117278508c4680

Edward Thomson 2021-05-27T18:47:22

attr: introduce `git_attr_options` for extended queries Allow more advanced attribute queries using a `git_attr_options`, and extended functions to use it. Presently there is no additional configuration in a `git_attr_options` beyond the flags, but this is for future growth.

diff --git a/include/git2/attr.h b/include/git2/attr.h
index a3ab5a7..306893c 100644
--- a/include/git2/attr.h
+++ b/include/git2/attr.h
@@ -135,6 +135,19 @@ GIT_EXTERN(git_attr_value_t) git_attr_value(const char *attr);
 #define GIT_ATTR_CHECK_INCLUDE_HEAD     (1 << 3)
 
 /**
+* An options structure for querying attributes.
+*/
+typedef struct {
+	unsigned int version;
+
+	/** A combination of GIT_ATTR_CHECK flags */
+	unsigned int flags;
+} git_attr_options;
+
+#define GIT_ATTR_OPTIONS_VERSION 1
+#define GIT_ATTR_OPTIONS_INIT {GIT_ATTR_OPTIONS_VERSION}
+
+/**
  * Look up the value of one git attribute for path.
  *
  * @param value_out Output of the value of the attribute.  Use the GIT_ATTR_...
@@ -157,6 +170,28 @@ GIT_EXTERN(int) git_attr_get(
 	const char *name);
 
 /**
+ * Look up the value of one git attribute for path with extended options.
+ *
+ * @param value_out Output of the value of the attribute.  Use the GIT_ATTR_...
+ *             macros to test for TRUE, FALSE, UNSPECIFIED, etc. or just
+ *             use the string value for attributes set to a value.  You
+ *             should NOT modify or free this value.
+ * @param repo The repository containing the path.
+ * @param opts The `git_attr_options` to use when querying these attributes.
+ * @param path The path to check for attributes.  Relative paths are
+ *             interpreted relative to the repo root.  The file does
+ *             not have to exist, but if it does not, then it will be
+ *             treated as a plain file (not a directory).
+ * @param name The name of the attribute to look up.
+ */
+GIT_EXTERN(int) git_attr_get_ext(
+	const char **value_out,
+	git_repository *repo,
+	git_attr_options *opts,
+	const char *path,
+	const char *name);
+
+/**
  * Look up a list of git attributes for path.
  *
  * Use this if you have a known list of attributes that you want to
@@ -194,6 +229,30 @@ GIT_EXTERN(int) git_attr_get_many(
 	const char **names);
 
 /**
+ * Look up a list of git attributes for path with extended options.
+ *
+ * @param values_out An array of num_attr entries that will have string
+ *             pointers written into it for the values of the attributes.
+ *             You should not modify or free the values that are written
+ *             into this array (although of course, you should free the
+ *             array itself if you allocated it).
+ * @param repo The repository containing the path.
+ * @param opts The `git_attr_options` to use when querying these attributes.
+ * @param path The path inside the repo to check attributes.  This
+ *             does not have to exist, but if it does not, then
+ *             it will be treated as a plain file (i.e. not a directory).
+ * @param num_attr The number of attributes being looked up
+ * @param names An array of num_attr strings containing attribute names.
+ */
+GIT_EXTERN(int) git_attr_get_many_ext(
+	const char **values_out,
+	git_repository *repo,
+	git_attr_options *opts,
+	const char *path,
+	size_t num_attr,
+	const char **names);
+
+/**
  * The callback used with git_attr_foreach.
  *
  * This callback will be invoked only once per attribute name, even if there
@@ -232,6 +291,26 @@ GIT_EXTERN(int) git_attr_foreach(
 	void *payload);
 
 /**
+ * Loop over all the git attributes for a path with extended options.
+ *
+ * @param repo The repository containing the path.
+ * @param opts The `git_attr_options` to use when querying these attributes.
+ * @param path Path inside the repo to check attributes.  This does not have
+ *             to exist, but if it does not, then it will be treated as a
+ *             plain file (i.e. not a directory).
+ * @param callback Function to invoke on each attribute name and value.
+ *                 See git_attr_foreach_cb.
+ * @param payload Passed on as extra parameter to callback function.
+ * @return 0 on success, non-zero callback return value, or error code
+ */
+GIT_EXTERN(int) git_attr_foreach_ext(
+	git_repository *repo,
+	git_attr_options *opts,
+	const char *path,
+	git_attr_foreach_cb callback,
+	void *payload);
+
+/**
  * Flush the gitattributes cache.
  *
  * Call this if you have reason to believe that the attributes files on
diff --git a/src/attr.c b/src/attr.c
index 86be084..a2d78e6 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -36,16 +36,16 @@ git_attr_value_t git_attr_value(const char *attr)
 static int collect_attr_files(
 	git_repository *repo,
 	git_attr_session *attr_session,
-	uint32_t flags,
+	git_attr_options *opts,
 	const char *path,
 	git_vector *files);
 
 static void release_attr_files(git_vector *files);
 
-int git_attr_get(
+int git_attr_get_ext(
 	const char **value,
 	git_repository *repo,
-	uint32_t flags,
+	git_attr_options *opts,
 	const char *pathname,
 	const char *name)
 {
@@ -61,6 +61,7 @@ int git_attr_get(
 	GIT_ASSERT_ARG(value);
 	GIT_ASSERT_ARG(repo);
 	GIT_ASSERT_ARG(name);
+	GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options");
 
 	*value = NULL;
 
@@ -70,7 +71,7 @@ int git_attr_get(
 	if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
 		return -1;
 
-	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
+	if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0)
 		goto cleanup;
 
 	memset(&attr, 0, sizeof(attr));
@@ -97,6 +98,20 @@ cleanup:
 	return error;
 }
 
+int git_attr_get(
+	const char **value,
+	git_repository *repo,
+	uint32_t flags,
+	const char *pathname,
+	const char *name)
+{
+	git_attr_options opts = GIT_ATTR_OPTIONS_INIT;
+
+	opts.flags = flags;
+
+	return git_attr_get_ext(value, repo, &opts, pathname, name);
+}
+
 
 typedef struct {
 	git_attr_name name;
@@ -107,7 +122,7 @@ int git_attr_get_many_with_session(
 	const char **values,
 	git_repository *repo,
 	git_attr_session *attr_session,
-	uint32_t flags,
+	git_attr_options *opts,
 	const char *pathname,
 	size_t num_attr,
 	const char **names)
@@ -129,6 +144,7 @@ int git_attr_get_many_with_session(
 	GIT_ASSERT_ARG(repo);
 	GIT_ASSERT_ARG(pathname);
 	GIT_ASSERT_ARG(names);
+	GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options");
 
 	if (git_repository_is_bare(repo))
 		dir_flag = GIT_DIR_FLAG_FALSE;
@@ -136,7 +152,7 @@ int git_attr_get_many_with_session(
 	if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
 		return -1;
 
-	if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
+	if ((error = collect_attr_files(repo, attr_session, opts, pathname, &files)) < 0)
 		goto cleanup;
 
 	info = git__calloc(num_attr, sizeof(attr_get_many_info));
@@ -190,8 +206,24 @@ int git_attr_get_many(
 	size_t num_attr,
 	const char **names)
 {
+	git_attr_options opts = GIT_ATTR_OPTIONS_INIT;
+
+	opts.flags = flags;
+
+	return git_attr_get_many_with_session(
+		values, repo, NULL, &opts, pathname, num_attr, names);
+}
+
+int git_attr_get_many_ext(
+	const char **values,
+	git_repository *repo,
+	git_attr_options *opts,
+	const char *pathname,
+	size_t num_attr,
+	const char **names)
+{
 	return git_attr_get_many_with_session(
-		values, repo, NULL, flags, pathname, num_attr, names);
+		values, repo, NULL, opts, pathname, num_attr, names);
 }
 
 int git_attr_foreach(
@@ -201,6 +233,20 @@ int git_attr_foreach(
 	int (*callback)(const char *name, const char *value, void *payload),
 	void *payload)
 {
+	git_attr_options opts = GIT_ATTR_OPTIONS_INIT;
+
+	opts.flags = flags;
+
+	return git_attr_foreach_ext(repo, &opts, pathname, callback, payload);
+}
+
+int git_attr_foreach_ext(
+	git_repository *repo,
+	git_attr_options *opts,
+	const char *pathname,
+	int (*callback)(const char *name, const char *value, void *payload),
+	void *payload)
+{
 	int error;
 	git_attr_path path;
 	git_vector files = GIT_VECTOR_INIT;
@@ -213,6 +259,7 @@ int git_attr_foreach(
 
 	GIT_ASSERT_ARG(repo);
 	GIT_ASSERT_ARG(callback);
+	GIT_ERROR_CHECK_VERSION(opts, GIT_ATTR_OPTIONS_VERSION, "git_attr_options");
 
 	if (git_repository_is_bare(repo))
 		dir_flag = GIT_DIR_FLAG_FALSE;
@@ -220,7 +267,7 @@ int git_attr_foreach(
 	if (git_attr_path__init(&path, repo, pathname, git_repository_workdir(repo), dir_flag) < 0)
 		return -1;
 
-	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
+	if ((error = collect_attr_files(repo, NULL, opts, pathname, &files)) < 0 ||
 	    (error = git_strmap_new(&seen)) < 0)
 		goto cleanup;
 
@@ -331,7 +378,7 @@ static int system_attr_file(
 static int attr_setup(
 	git_repository *repo,
 	git_attr_session *attr_session,
-	uint32_t flags)
+	git_attr_options *opts)
 {
 	git_buf system = GIT_BUF_INIT, info = GIT_BUF_INIT;
 	git_attr_file_source index_source = { GIT_ATTR_FILE_SOURCE_INDEX, NULL, GIT_ATTR_FILE };
@@ -379,7 +426,7 @@ static int attr_setup(
 	    (error = preload_attr_source(repo, attr_session, &index_source)) < 0)
 			goto out;
 
-	if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0 &&
+	if ((opts && (opts->flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0) &&
 	    (error = preload_attr_source(repo, attr_session, &head_source)) < 0)
 		goto out;
 
@@ -545,7 +592,7 @@ static void release_attr_files(git_vector *files)
 static int collect_attr_files(
 	git_repository *repo,
 	git_attr_session *attr_session,
-	uint32_t flags,
+	git_attr_options *opts,
 	const char *path,
 	git_vector *files)
 {
@@ -554,7 +601,7 @@ static int collect_attr_files(
 	const char *workdir = git_repository_workdir(repo);
 	attr_walk_up_info info = { NULL };
 
-	if ((error = attr_setup(repo, attr_session, flags)) < 0)
+	if ((error = attr_setup(repo, attr_session, opts)) < 0)
 		return error;
 
 	/* Resolve path in a non-bare repo */
@@ -584,7 +631,7 @@ static int collect_attr_files(
 
 	info.repo = repo;
 	info.attr_session = attr_session;
-	info.flags = flags;
+	info.flags = opts ? opts->flags : 0;
 	info.workdir = workdir;
 	if (git_repository_index__weakptr(&info.index, repo) < 0)
 		git_error_clear(); /* no error even if there is no index */
@@ -604,7 +651,7 @@ static int collect_attr_files(
 			goto cleanup;
 	}
 
-	if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
+	if (!opts || (opts->flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
 		error = system_attr_file(&dir, attr_session);
 
 		if (!error)
diff --git a/src/attr_file.h b/src/attr_file.h
index 691dbda..5c8a412 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -136,7 +136,7 @@ extern int git_attr_get_many_with_session(
 	const char **values_out,
 	git_repository *repo,
 	git_attr_session *attr_session,
-	uint32_t flags,
+	git_attr_options *opts,
 	const char *path,
 	size_t num_attr,
 	const char **names);
diff --git a/src/filter.c b/src/filter.c
index 6c09a6a..d139a59 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -430,20 +430,20 @@ static int filter_list_check_attributes(
 	const git_filter_source *src)
 {
 	const char **strs = git__calloc(fdef->nattrs, sizeof(const char *));
-	uint32_t flags = 0;
+	git_attr_options attr_opts = GIT_ATTR_OPTIONS_INIT;
 	size_t i;
 	int error;
 
 	GIT_ERROR_CHECK_ALLOC(strs);
 
 	if ((src->flags & GIT_FILTER_NO_SYSTEM_ATTRIBUTES) != 0)
-		flags |= GIT_ATTR_CHECK_NO_SYSTEM;
+		attr_opts.flags |= GIT_ATTR_CHECK_NO_SYSTEM;
 
 	if ((src->flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0)
-		flags |= GIT_ATTR_CHECK_INCLUDE_HEAD;
+		attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_HEAD;
 
 	error = git_attr_get_many_with_session(
-		strs, repo, attr_session, flags, src->path, fdef->nattrs, fdef->attrs);
+		strs, repo, attr_session, &attr_opts, src->path, fdef->nattrs, fdef->attrs);
 
 	/* if no values were found but no matches are needed, it's okay! */
 	if (error == GIT_ENOTFOUND && !fdef->nmatches) {
diff --git a/tests/attr/repo.c b/tests/attr/repo.c
index 8224e5c..eabc033 100644
--- a/tests/attr/repo.c
+++ b/tests/attr/repo.c
@@ -341,7 +341,7 @@ void test_attr_repo__sysdir_with_session(void)
 	g_repo = cl_git_sandbox_reopen();
 
 	cl_git_pass(git_attr_session__init(&session, g_repo));
-	cl_git_pass(git_attr_get_many_with_session(values, g_repo, &session, 0, "file", ARRAY_SIZE(attrs), attrs));
+	cl_git_pass(git_attr_get_many_with_session(values, g_repo, &session, NULL, "file", ARRAY_SIZE(attrs), attrs));
 
 	cl_assert_equal_s(values[0], "1");
 	cl_assert_equal_s(values[1], "2");