Commit 4fd5748c4bbc0fba3db9fb2fb20e01261339b9d9

Edward Thomson 2019-07-21T14:11:03

attr: optionally read attributes from repository When `GIT_ATTR_CHECK_INCLUDE_HEAD` is specified, read `gitattribute` files that are checked into the repository at the HEAD revision.

diff --git a/include/git2/attr.h b/include/git2/attr.h
index cab9758..72e4380 100644
--- a/include/git2/attr.h
+++ b/include/git2/attr.h
@@ -119,13 +119,20 @@ GIT_EXTERN(git_attr_value_t) git_attr_value(const char *attr);
 #define GIT_ATTR_CHECK_INDEX_ONLY		2
 
 /**
- * Check attribute flags: Using the system attributes file.
+ * Check attribute flags: controlling extended attribute behavior.
  *
  * Normally, attribute checks include looking in the /etc (or system
  * equivalent) directory for a `gitattributes` file.  Passing this
  * flag will cause attribute checks to ignore that file.
+ * equivalent) directory for a `gitattributes` file.  Passing the
+ * `GIT_ATTR_CHECK_NO_SYSTEM` flag will cause attribute checks to
+ * ignore that file.
+ *
+ * Passing the `GIT_ATTR_CHECK_INCLUDE_HEAD` flag will use attributes
+ * from a `.gitattributes` file in the repository at the HEAD revision.
  */
-#define GIT_ATTR_CHECK_NO_SYSTEM		(1 << 2)
+#define GIT_ATTR_CHECK_NO_SYSTEM        (1 << 2)
+#define GIT_ATTR_CHECK_INCLUDE_HEAD     (1 << 3)
 
 /**
  * Look up the value of one git attribute for path.
diff --git a/src/attr.c b/src/attr.c
index 02a7148..bd517cd 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -305,7 +305,10 @@ static int system_attr_file(
 	return 0;
 }
 
-static int attr_setup(git_repository *repo, git_attr_session *attr_session)
+static int attr_setup(
+	git_repository *repo,
+	git_attr_session *attr_session,
+	uint32_t flags)
 {
 	git_buf path = GIT_BUF_INIT;
 	git_index *idx = NULL;
@@ -352,6 +355,11 @@ static int attr_setup(git_repository *repo, git_attr_session *attr_session)
 				       NULL, GIT_ATTR_FILE, true)) < 0)
 			goto out;
 
+	if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0 &&
+	    (error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_HEAD,
+				       NULL, GIT_ATTR_FILE, true)) < 0)
+		goto out;
+
 	if (attr_session)
 		attr_session->init_setup = 1;
 
@@ -428,6 +436,9 @@ static int attr_decide_sources(
 		break;
 	}
 
+	if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0)
+		srcs[count++] = GIT_ATTR_FILE__FROM_HEAD;
+
 	return count;
 }
 
@@ -460,7 +471,7 @@ static int push_attr_file(
 static int push_one_attr(void *ref, const char *path)
 {
 	attr_walk_up_info *info = (attr_walk_up_info *)ref;
-	git_attr_file_source src[2];
+	git_attr_file_source src[GIT_ATTR_FILE_NUM_SOURCES];
 	int error = 0, n_src, i;
 	bool allow_macros;
 
@@ -499,7 +510,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)) < 0)
+	if ((error = attr_setup(repo, attr_session, flags)) < 0)
 		return error;
 
 	/* Resolve path in a non-bare repo */
diff --git a/src/attr_file.c b/src/attr_file.c
index f8769c6..a1b4c73 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -109,6 +109,8 @@ int git_attr_file__load(
 	bool allow_macros)
 {
 	int error = 0;
+	git_tree *tree = NULL;
+	git_tree_entry *tree_entry = NULL;
 	git_blob *blob = NULL;
 	git_buf content = GIT_BUF_INIT;
 	const char *content_str;
@@ -117,6 +119,8 @@ int git_attr_file__load(
 	bool nonexistent = false;
 	int bom_offset;
 	git_bom_t bom;
+	git_oid id;
+	git_off_t blobsize;
 
 	*out = NULL;
 
@@ -125,9 +129,6 @@ int git_attr_file__load(
 		/* in-memory attribute file doesn't need data */
 		break;
 	case GIT_ATTR_FILE__FROM_INDEX: {
-		git_oid id;
-		git_off_t blobsize;
-
 		if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
 			(error = git_blob_lookup(&blob, repo, &id)) < 0)
 			return error;
@@ -157,6 +158,25 @@ int git_attr_file__load(
 
 		break;
 	}
+	case GIT_ATTR_FILE__FROM_HEAD: {
+		if ((error = git_repository_head_tree(&tree, repo)) < 0 ||
+		    (error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0 ||
+		    (error = git_blob_lookup(&blob, repo, git_tree_entry_id(tree_entry))) < 0)
+			goto cleanup;
+
+		/*
+		 * Do not assume that data straight from the ODB is NULL-terminated;
+		 * copy the contents of a file to a buffer to work on.
+		 */
+		blobsize = git_blob_rawsize(blob);
+
+		GIT_ERROR_CHECK_BLOBSIZE(blobsize);
+		if ((error = git_buf_put(&content,
+			git_blob_rawcontent(blob), (size_t)blobsize)) < 0)
+			goto cleanup;
+
+		break;
+	}
 	default:
 		git_error_set(GIT_ERROR_INVALID, "unknown file source %d", source);
 		return -1;
@@ -188,6 +208,8 @@ int git_attr_file__load(
 		file->nonexistent = 1;
 	else if (source == GIT_ATTR_FILE__FROM_INDEX)
 		git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
+	else if (source == GIT_ATTR_FILE__FROM_HEAD)
+		git_oid_cpy(&file->cache_data.oid, git_tree_id(tree));
 	else if (source == GIT_ATTR_FILE__FROM_FILE)
 		git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
 	/* else always cacheable */
@@ -196,6 +218,8 @@ int git_attr_file__load(
 
 cleanup:
 	git_blob_free(blob);
+	git_tree_entry_free(tree_entry);
+	git_tree_free(tree);
 	git_buf_dispose(&content);
 
 	return error;
@@ -236,6 +260,19 @@ int git_attr_file__out_of_date(
 		return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
 	}
 
+	case GIT_ATTR_FILE__FROM_HEAD: {
+		git_tree *tree;
+		int error;
+
+		if ((error = git_repository_head_tree(&tree, repo)) < 0)
+			return error;
+
+		error = git_oid__cmp(&file->cache_data.oid, git_tree_id(tree));
+
+		git_tree_free(tree);
+		return error;
+	}
+
 	default:
 		git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source);
 		return -1;
diff --git a/src/attr_file.h b/src/attr_file.h
index f4f9a09..2b6b1d6 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -40,8 +40,9 @@ typedef enum {
 	GIT_ATTR_FILE__IN_MEMORY   = 0,
 	GIT_ATTR_FILE__FROM_FILE   = 1,
 	GIT_ATTR_FILE__FROM_INDEX  = 2,
+	GIT_ATTR_FILE__FROM_HEAD   = 3,
 
-	GIT_ATTR_FILE_NUM_SOURCES  = 3
+	GIT_ATTR_FILE_NUM_SOURCES  = 4
 } git_attr_file_source;
 
 extern const char *git_attr__true;