Commit 86219f40689c85ec4418575223f4376beffa45af

Edward Thomson 2017-11-30T15:40:13

util: introduce `git__prefixncmp` and consolidate implementations Introduce `git_prefixncmp` that will search up to the first `n` characters of a string to see if it is prefixed by another string. This is useful for examining if a non-null terminated character array is prefixed by a particular substring. Consolidate the various implementations of `git__prefixcmp` around a single core implementation and add some test cases to validate its behavior.

diff --git a/src/util.c b/src/util.c
index 6ae5cda..1760a31 100644
--- a/src/util.c
+++ b/src/util.c
@@ -252,35 +252,47 @@ void git__strtolower(char *str)
 	git__strntolower(str, strlen(str));
 }
 
-int git__prefixcmp(const char *str, const char *prefix)
+GIT_INLINE(int) prefixcmp(const char *str, size_t str_n, const char *prefix, bool icase)
 {
-	for (;;) {
-		unsigned char p = *(prefix++), s;
+	int s, p;
+
+	while (str_n--) {
+		s = (unsigned char)*str++;
+		p = (unsigned char)*prefix++;
+
+		if (icase) {
+			s = git__tolower(s);
+			p = git__tolower(p);
+		}
+
 		if (!p)
 			return 0;
-		if ((s = *(str++)) != p)
+
+		if (s != p)
 			return s - p;
 	}
+
+	return (0 - *prefix);
 }
 
-int git__prefixcmp_icase(const char *str, const char *prefix)
+int git__prefixcmp(const char *str, const char *prefix)
 {
-	return strncasecmp(str, prefix, strlen(prefix));
+	return prefixcmp(str, SIZE_MAX, prefix, false);
 }
 
-int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix)
+int git__prefixncmp(const char *str, size_t str_n, const char *prefix)
 {
-	int s, p;
-
-	while(str_n--) {
-		s = (unsigned char)git__tolower(*str++);
-		p = (unsigned char)git__tolower(*prefix++);
+	return prefixcmp(str, str_n, prefix, false);
+}
 
-		if (s != p)
-			return s - p;
-	}
+int git__prefixcmp_icase(const char *str, const char *prefix)
+{
+	return prefixcmp(str, SIZE_MAX, prefix, true);
+}
 
-	return (0 - *prefix);
+int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix)
+{
+	return prefixcmp(str, str_n, prefix, true);
 }
 
 int git__suffixcmp(const char *str, const char *suffix)
diff --git a/src/util.h b/src/util.h
index 7c9a54f..80ee8e6 100644
--- a/src/util.h
+++ b/src/util.h
@@ -180,6 +180,7 @@ GIT_INLINE(void) git__free(void *ptr)
 
 extern int git__prefixcmp(const char *str, const char *prefix);
 extern int git__prefixcmp_icase(const char *str, const char *prefix);
+extern int git__prefixncmp(const char *str, size_t str_n, const char *prefix);
 extern int git__prefixncmp_icase(const char *str, size_t str_n, const char *prefix);
 extern int git__suffixcmp(const char *str, const char *suffix);
 
diff --git a/tests/core/string.c b/tests/core/string.c
index 90e8fa0..85db0c6 100644
--- a/tests/core/string.c
+++ b/tests/core/string.c
@@ -40,6 +40,48 @@ void test_core_string__2(void)
 	cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0);
 }
 
+/* compare prefixes with len */
+void test_core_string__prefixncmp(void)
+{
+	cl_assert(git__prefixncmp("", 0, "") == 0);
+	cl_assert(git__prefixncmp("a", 1, "") == 0);
+	cl_assert(git__prefixncmp("", 0, "a") < 0);
+	cl_assert(git__prefixncmp("a", 1, "b") < 0);
+	cl_assert(git__prefixncmp("b", 1, "a") > 0);
+	cl_assert(git__prefixncmp("ab", 2, "a") == 0);
+	cl_assert(git__prefixncmp("ab", 1, "a") == 0);
+	cl_assert(git__prefixncmp("ab", 2, "ac") < 0);
+	cl_assert(git__prefixncmp("a", 1, "ac") < 0);
+	cl_assert(git__prefixncmp("ab", 1, "ac") < 0);
+	cl_assert(git__prefixncmp("ab", 2, "aa") > 0);
+	cl_assert(git__prefixncmp("ab", 1, "aa") < 0);
+}
+
+/* compare prefixes with len */
+void test_core_string__prefixncmp_icase(void)
+{
+	cl_assert(git__prefixncmp_icase("", 0, "") == 0);
+	cl_assert(git__prefixncmp_icase("a", 1, "") == 0);
+	cl_assert(git__prefixncmp_icase("", 0, "a") < 0);
+	cl_assert(git__prefixncmp_icase("a", 1, "b") < 0);
+	cl_assert(git__prefixncmp_icase("A", 1, "b") < 0);
+	cl_assert(git__prefixncmp_icase("a", 1, "B") < 0);
+	cl_assert(git__prefixncmp_icase("b", 1, "a") > 0);
+	cl_assert(git__prefixncmp_icase("B", 1, "a") > 0);
+	cl_assert(git__prefixncmp_icase("b", 1, "A") > 0);
+	cl_assert(git__prefixncmp_icase("ab", 2, "a") == 0);
+	cl_assert(git__prefixncmp_icase("Ab", 2, "a") == 0);
+	cl_assert(git__prefixncmp_icase("ab", 2, "A") == 0);
+	cl_assert(git__prefixncmp_icase("ab", 1, "a") == 0);
+	cl_assert(git__prefixncmp_icase("ab", 2, "ac") < 0);
+	cl_assert(git__prefixncmp_icase("Ab", 2, "ac") < 0);
+	cl_assert(git__prefixncmp_icase("ab", 2, "Ac") < 0);
+	cl_assert(git__prefixncmp_icase("a", 1, "ac") < 0);
+	cl_assert(git__prefixncmp_icase("ab", 1, "ac") < 0);
+	cl_assert(git__prefixncmp_icase("ab", 2, "aa") > 0);
+	cl_assert(git__prefixncmp_icase("ab", 1, "aa") < 0);
+}
+
 void test_core_string__strcmp(void)
 {
 	cl_assert(git__strcmp("", "") == 0);