Commit 0b6ed4f96c7253b97a86f7e88ba4d69888c72382

Edward Thomson 2015-06-22T11:24:20

Merge pull request #3240 from libgit2/cmn/commit-header-field commit: allow retrieving an arbitrary header field

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a8e3e18..eb7ae84 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -140,6 +140,9 @@ support for HTTPS connections insead of OpenSSL.
 * `git_filter_list_contains` will indicate whether a particular
   filter will be run in the given filter list.
 
+* `git_commit_header_field()` has been added, which allows retrieving
+  the contents of an arbitrary header field.
+
 ### API removals
 
 * `git_remote_save()` and `git_remote_clear_refspecs()` have been
diff --git a/include/git2/commit.h b/include/git2/commit.h
index fb53a70..04711c1 100644
--- a/include/git2/commit.h
+++ b/include/git2/commit.h
@@ -240,6 +240,17 @@ GIT_EXTERN(int) git_commit_nth_gen_ancestor(
 	unsigned int n);
 
 /**
+ * Get an arbitrary header field
+ *
+ * @param out the buffer to fill
+ * @param commit the commit to look in
+ * @param field the header field to return
+ * @return 0 on succeess, GIT_ENOTFOUND if the field does not exist,
+ * or an error code
+ */
+GIT_EXTERN(int) git_commit_header_field(git_buf *out, const git_commit *commit, const char *field);
+
+/**
  * Create new commit in the repository from a list of `git_object` pointers
  *
  * The message will **not** be cleaned up automatically. You can do that
diff --git a/src/commit.c b/src/commit.c
index ce13bdb..616f947 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -518,3 +518,58 @@ int git_commit_nth_gen_ancestor(
 	*ancestor = parent;
 	return 0;
 }
+
+int git_commit_header_field(git_buf *out, const git_commit *commit, const char *field)
+{
+	const char *buf = commit->raw_header;
+	const char *h, *eol;
+
+	git_buf_sanitize(out);
+	while ((h = strchr(buf, '\n')) && h[1] != '\0' && h[1] != '\n') {
+		h++;
+		if (git__prefixcmp(h, field)) {
+			buf = h;
+			continue;
+		}
+
+		h += strlen(field);
+		eol = strchr(h, '\n');
+		if (h[0] != ' ') {
+			buf = h;
+			continue;
+		}
+		if (!eol)
+			goto malformed;
+
+		h++; /* skip the SP */
+
+		git_buf_put(out, h, eol - h);
+		if (git_buf_oom(out))
+			goto oom;
+
+		/* If the next line starts with SP, it's multi-line, we must continue */
+		while (eol[1] == ' ') {
+			git_buf_putc(out, '\n');
+			h = eol + 2;
+			eol = strchr(h, '\n');
+			if (!eol)
+				goto malformed;
+
+			git_buf_put(out, h, eol - h);
+		}
+
+		if (git_buf_oom(out))
+			goto oom;
+
+		return 0;
+	}
+
+	return GIT_ENOTFOUND;
+
+malformed:
+	giterr_set(GITERR_OBJECT, "malformed header");
+	return -1;
+oom:
+	giterr_set_oom();
+	return -1;
+}
diff --git a/tests/commit/parse.c b/tests/commit/parse.c
index fa079f4..388da07 100644
--- a/tests/commit/parse.c
+++ b/tests/commit/parse.c
@@ -418,3 +418,41 @@ committer Vicent Marti <tanoku@gmail.com> 1273848544 +0200\n\
 	cl_assert_equal_s(raw_message, git_commit_message_raw(commit));
 	git_commit__free(commit);
 }
+
+void test_commit_parse__arbitrary_field(void)
+{
+	git_commit *commit;
+	git_buf buf = GIT_BUF_INIT;
+	const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\
+Version: GnuPG v1.4.12 (Darwin)\n\
+\n\
+iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\
+o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\
+JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\
+AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\
+SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\
+who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\
+6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\
+cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\
+c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\
+ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\
+7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\
+cpxtDQQMGYFpXK/71stq\n\
+=ozeK\n\
+-----END PGP SIGNATURE-----";
+
+	cl_git_pass(parse_commit(&commit, passing_commit_cases[4]));
+
+	cl_git_pass(git_commit_header_field(&buf, commit, "parent"));
+	cl_assert_equal_s("34734e478d6cf50c27c9d69026d93974d052c454", buf.ptr);
+	git_buf_clear(&buf);
+
+	cl_git_pass(git_commit_header_field(&buf, commit, "gpgsig"));
+	cl_assert_equal_s(gpgsig, buf.ptr);
+
+	cl_git_fail_with(GIT_ENOTFOUND, git_commit_header_field(&buf, commit, "awesomeness"));
+	cl_git_fail_with(GIT_ENOTFOUND, git_commit_header_field(&buf, commit, "par"));
+
+	git_buf_free(&buf);
+	git_commit__free(commit);
+}