Commit f063dafb1da60625da76a1ea6975a121969cb630

Henry Kleynhans 2017-11-12T10:56:50

signature: distinguish +0000 and -0000 UTC offsets Git considers '-0000' a valid offset for signature lines. They need to be treated as _not_ equal to a '+0000' signature offset. Parsing a signature line stores the offset in a signed integer which does not distinguish between `+0` and `-0`. This patch adds an additional flag `sign` to the `git_time` in the `signature` object which is populated with the sign of the offset. In addition to exposing this information to the user, this information is also used to compare signatures. /cc @pks-t @ethomson

diff --git a/include/git2/types.h b/include/git2/types.h
index dfdaa29..8d9a947 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -159,6 +159,7 @@ typedef struct git_packbuilder git_packbuilder;
 typedef struct git_time {
 	git_time_t time; /**< time in seconds from epoch */
 	int offset; /**< timezone offset, in minutes */
+	char sign; /**< indicator for questionable '-0000' offsets in signature */
 } git_time;
 
 /** An action signature (e.g. for committers, taggers, etc) */
diff --git a/src/signature.c b/src/signature.c
index bc3cf33..cd68523 100644
--- a/src/signature.c
+++ b/src/signature.c
@@ -90,6 +90,7 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema
 
 	p->when.time = time;
 	p->when.offset = offset;
+	p->when.sign = (offset < 0) ? '-' : '+';
 
 	*sig_out = p;
 	return 0;
@@ -113,6 +114,7 @@ int git_signature_dup(git_signature **dest, const git_signature *source)
 
 	signature->when.time = source->when.time;
 	signature->when.offset = source->when.offset;
+	signature->when.sign = source->when.sign;
 
 	*dest = signature;
 
@@ -137,6 +139,7 @@ int git_signature__pdup(git_signature **dest, const git_signature *source, git_p
 
 	signature->when.time = source->when.time;
 	signature->when.offset = source->when.offset;
+	signature->when.sign = source->when.sign;
 
 	*dest = signature;
 
@@ -257,6 +260,7 @@ int git_signature__parse(git_signature *sig, const char **buffer_out,
 			 */
 			if (hours <= 14 && mins <= 59) {
 				sig->when.offset = (hours * 60) + mins;
+				sig->when.sign = tz_start[0];
 				if (tz_start[0] == '-')
 					sig->when.offset = -sig->when.offset;
 			}
@@ -299,7 +303,7 @@ void git_signature__writebuf(git_buf *buf, const char *header, const git_signatu
 	assert(buf && sig);
 
 	offset = sig->when.offset;
-	sign = (sig->when.offset < 0) ? '-' : '+';
+	sign = (sig->when.offset < 0 || sig->when.sign == '-') ? '-' : '+';
 
 	if (offset < 0)
 		offset = -offset;
@@ -320,6 +324,7 @@ bool git_signature__equal(const git_signature *one, const git_signature *two)
 		git__strcmp(one->name, two->name) == 0 &&
 		git__strcmp(one->email, two->email) == 0 &&
 		one->when.time == two->when.time &&
-		one->when.offset == two->when.offset;
+		one->when.offset == two->when.offset &&
+		one->when.sign == two->when.sign;
 }
 
diff --git a/tests/commit/signature.c b/tests/commit/signature.c
index 30bc929..286079f 100644
--- a/tests/commit/signature.c
+++ b/tests/commit/signature.c
@@ -1,4 +1,5 @@
 #include "clar_libgit2.h"
+#include "signature.h"
 
 static int try_build_signature(const char *name, const char *email, git_time_t time, int offset)
 {
@@ -99,3 +100,29 @@ void test_commit_signature__from_buf(void)
 	git_signature_free(sign);
 }
 
+void test_commit_signature__from_buf_with_neg_zero_offset(void)
+{
+	git_signature *sign;
+
+	cl_git_pass(git_signature_from_buffer(&sign, "Test User <test@test.tt> 1461698487 -0000"));
+	cl_assert_equal_s("Test User", sign->name);
+	cl_assert_equal_s("test@test.tt", sign->email);
+	cl_assert_equal_i(1461698487, sign->when.time);
+	cl_assert_equal_i(0, sign->when.offset);
+	cl_assert_equal_i('-', sign->when.sign);
+	git_signature_free(sign);
+}
+
+void test_commit_signature__pos_and_neg_zero_offsets_dont_match(void)
+{
+	git_signature *with_neg_zero;
+	git_signature *with_pos_zero;
+
+	cl_git_pass(git_signature_from_buffer(&with_neg_zero, "Test User <test@test.tt> 1461698487 -0000"));
+	cl_git_pass(git_signature_from_buffer(&with_pos_zero, "Test User <test@test.tt> 1461698487 +0000"));
+
+	cl_assert(!git_signature__equal(with_neg_zero, with_pos_zero));
+
+	git_signature_free((git_signature *)with_neg_zero);
+	git_signature_free((git_signature *)with_pos_zero);
+}