Commit 1439b9ff05524949b6b3fa6cad716a9bb3cbc249

Edward Thomson 2021-07-22T15:29:54

filter: introduce GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT Provide a mechanism to filter using attribute data from a specific commit (making use of `GIT_ATTR_CHECK_INCLUDE_COMMIT`).

diff --git a/include/git2/blob.h b/include/git2/blob.h
index 3f67386..fceb5c7 100644
--- a/include/git2/blob.h
+++ b/include/git2/blob.h
@@ -114,6 +114,12 @@ typedef enum {
 	 * in the HEAD commit.
 	 */
 	GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD = (1 << 2),
+
+	/**
+	 * When set, filters will be loaded from a `.gitattributes` file
+	 * in the specified commit.
+	 */
+	GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT = (1 << 3),
 } git_blob_filter_flag_t;
 
 /**
@@ -128,6 +134,12 @@ typedef struct {
 
 	/** Flags to control the filtering process, see `git_blob_filter_flag_t` above */
 	uint32_t flags;
+
+	/**
+	 * The commit to load attributes from, when
+	 * `GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT` is specified.
+	 */
+	git_oid *commit_id;
 } git_blob_filter_options;
 
 #define GIT_BLOB_FILTER_OPTIONS_VERSION 1
diff --git a/include/git2/filter.h b/include/git2/filter.h
index 5458151..044c3b8 100644
--- a/include/git2/filter.h
+++ b/include/git2/filter.h
@@ -49,6 +49,12 @@ typedef enum {
 
 	/** Load attributes from `.gitattributes` in the root of HEAD */
 	GIT_FILTER_ATTRIBUTES_FROM_HEAD = (1u << 2),
+
+	/**
+	 * Load attributes from `.gitattributes` in a given commit.
+	 * This can only be specified in a `git_filter_options`.
+	 */
+	GIT_FILTER_ATTRIBUTES_FROM_COMMIT = (1u << 3),
 } git_filter_flag_t;
 
 /**
@@ -59,6 +65,12 @@ typedef struct {
 
 	/** See `git_filter_flag_t` above */
 	uint32_t flags;
+
+	/**
+	 * The commit to load attributes from, when
+	 * `GIT_FILTER_ATTRIBUTES_FROM_COMMIT` is specified.
+	 */
+	git_oid *commit_id;
 } git_filter_options;
 
  #define GIT_FILTER_OPTIONS_VERSION 1
diff --git a/src/blob.c b/src/blob.c
index 169e345..01ebf07 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -421,7 +421,7 @@ int git_blob_filter(
 	int error = 0;
 	git_filter_list *fl = NULL;
 	git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
-	git_filter_flag_t flags = GIT_FILTER_DEFAULT;
+	git_filter_options filter_opts = GIT_FILTER_OPTIONS_INIT;
 
 	GIT_ASSERT_ARG(blob);
 	GIT_ASSERT_ARG(path);
@@ -441,14 +441,19 @@ int git_blob_filter(
 		return 0;
 
 	if ((opts.flags & GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES) != 0)
-		flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES;
+		filter_opts.flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES;
 
 	if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_HEAD) != 0)
-		flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD;
+		filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD;
 
-	if (!(error = git_filter_list_load(
+	if ((opts.flags & GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) {
+		filter_opts.flags |= GIT_FILTER_ATTRIBUTES_FROM_COMMIT;
+		filter_opts.commit_id = opts.commit_id;
+	}
+
+	if (!(error = git_filter_list_load_ext(
 			&fl, git_blob_owner(blob), blob, path,
-			GIT_FILTER_TO_WORKTREE, flags))) {
+			GIT_FILTER_TO_WORKTREE, &filter_opts))) {
 
 		error = git_filter_list_apply_to_blob(out, fl, blob);
 
diff --git a/src/filter.c b/src/filter.c
index 3309ab7..eed175e 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -443,6 +443,11 @@ static int filter_list_check_attributes(
 	if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_HEAD) != 0)
 		attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_HEAD;
 
+	if ((src->options.flags & GIT_FILTER_ATTRIBUTES_FROM_COMMIT) != 0) {
+		attr_opts.flags |= GIT_ATTR_CHECK_INCLUDE_COMMIT;
+		attr_opts.commit_id = src->options.commit_id;
+	}
+
 	error = git_attr_get_many_with_session(
 		strs, repo, filter_session->attr_session, &attr_opts, src->path, fdef->nattrs, fdef->attrs);
 
diff --git a/tests/filter/bare.c b/tests/filter/bare.c
index 7319b52..f8e3423 100644
--- a/tests/filter/bare.c
+++ b/tests/filter/bare.c
@@ -132,3 +132,63 @@ void test_filter_bare__sanitizes(void)
 	git_blob_free(blob);
 }
 
+void test_filter_bare__from_specific_commit_one(void)
+{
+	git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
+	git_blob *blob;
+	git_buf buf = { 0 };
+	git_oid commit_id;
+
+	cl_git_pass(git_oid_fromstr(&commit_id, "b8986fec0f7bde90f78ac72706e782d82f24f2f0"));
+
+	opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES;
+	opts.flags |= GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT;
+	opts.commit_id = &commit_id;
+
+	cl_git_pass(git_revparse_single(
+		(git_object **)&blob, g_repo, "055c872")); /* ident */
+
+	cl_assert_equal_s("$Id$\n", git_blob_rawcontent(blob));
+
+	cl_git_pass(git_blob_filter(&buf, blob, "ident.bin", &opts));
+	cl_assert_equal_s("$Id$\n", buf.ptr);
+
+	cl_git_pass(git_blob_filter(&buf, blob, "ident.identlf", &opts));
+	cl_assert_equal_s("$Id: 055c8729cdcc372500a08db659c045e16c4409fb $\n", buf.ptr);
+
+	git_buf_dispose(&buf);
+	git_blob_free(blob);
+}
+
+void test_filter_bare__from_specific_commit_with_no_attributes_file(void)
+{
+	git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
+	git_blob *blob;
+	git_buf buf = { 0 };
+	git_oid commit_id;
+
+	cl_git_pass(git_oid_fromstr(&commit_id, "5afb6a14a864e30787857dd92af837e8cdd2cb1b"));
+
+	opts.flags |= GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES;
+	opts.flags |= GIT_BLOB_FILTER_ATTRIBUTES_FROM_COMMIT;
+	opts.commit_id = &commit_id;
+
+	cl_git_pass(git_revparse_single(
+		(git_object **)&blob, g_repo, "799770d")); /* all-lf */
+
+	cl_assert_equal_s(ALL_LF_TEXT_RAW, git_blob_rawcontent(blob));
+
+	cl_git_pass(git_blob_filter(&buf, blob, "file.bin", &opts));
+	cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr);
+
+	/* we never convert CRLF -> LF on platforms that have LF */
+	cl_git_pass(git_blob_filter(&buf, blob, "file.lf", &opts));
+	cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr);
+
+	/* we never convert CRLF -> LF on platforms that have LF */
+	cl_git_pass(git_blob_filter(&buf, blob, "file.crlf", &opts));
+	cl_assert_equal_s(ALL_LF_TEXT_RAW, buf.ptr);
+
+	git_buf_dispose(&buf);
+	git_blob_free(blob);
+}
diff --git a/tests/resources/crlf.git/objects/05/5c8729cdcc372500a08db659c045e16c4409fb b/tests/resources/crlf.git/objects/05/5c8729cdcc372500a08db659c045e16c4409fb
new file mode 100644
index 0000000..44076ca
Binary files /dev/null and b/tests/resources/crlf.git/objects/05/5c8729cdcc372500a08db659c045e16c4409fb differ
diff --git a/tests/resources/crlf.git/objects/1e/c507638b806aba45d6142082885f2a9e88322d b/tests/resources/crlf.git/objects/1e/c507638b806aba45d6142082885f2a9e88322d
new file mode 100644
index 0000000..ca97967
Binary files /dev/null and b/tests/resources/crlf.git/objects/1e/c507638b806aba45d6142082885f2a9e88322d differ
diff --git a/tests/resources/crlf.git/objects/44/b0be18671a284f1156117b6338edac2663341c b/tests/resources/crlf.git/objects/44/b0be18671a284f1156117b6338edac2663341c
new file mode 100644
index 0000000..0576e62
Binary files /dev/null and b/tests/resources/crlf.git/objects/44/b0be18671a284f1156117b6338edac2663341c differ
diff --git a/tests/resources/crlf.git/objects/55/1b8fce462bba005ab6d34a2244d8a3f6b03dd0 b/tests/resources/crlf.git/objects/55/1b8fce462bba005ab6d34a2244d8a3f6b03dd0
new file mode 100644
index 0000000..7501c88
Binary files /dev/null and b/tests/resources/crlf.git/objects/55/1b8fce462bba005ab6d34a2244d8a3f6b03dd0 differ
diff --git a/tests/resources/crlf.git/objects/b8/986fec0f7bde90f78ac72706e782d82f24f2f0 b/tests/resources/crlf.git/objects/b8/986fec0f7bde90f78ac72706e782d82f24f2f0
new file mode 100644
index 0000000..d745d20
--- /dev/null
+++ b/tests/resources/crlf.git/objects/b8/986fec0f7bde90f78ac72706e782d82f24f2f0
@@ -0,0 +1,3 @@
+xM
+0]s%3Dt
$b6FQ2McnSWU`6*CĽGw,|S,\#Y8t);
qȳ
+5VG-~ۥ2=`ֲml?13cn
H]aj@U
\ No newline at end of file
diff --git a/tests/resources/crlf.git/refs/heads/ident b/tests/resources/crlf.git/refs/heads/ident
new file mode 100644
index 0000000..8732f0c
--- /dev/null
+++ b/tests/resources/crlf.git/refs/heads/ident
@@ -0,0 +1 @@
+b8986fec0f7bde90f78ac72706e782d82f24f2f0
diff --git a/tests/resources/crlf.git/refs/heads/no-ident b/tests/resources/crlf.git/refs/heads/no-ident
new file mode 100644
index 0000000..fa9a673
--- /dev/null
+++ b/tests/resources/crlf.git/refs/heads/no-ident
@@ -0,0 +1 @@
+1ec507638b806aba45d6142082885f2a9e88322d