Commit 8d3b39a6ad325fe1d08e17281803e26359756fc6

Edward Thomson 2017-01-21T23:50:38

Merge branch 'pr/3912'

diff --git a/include/git2/common.h b/include/git2/common.h
index 99c9981..5be7fb6 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -175,6 +175,7 @@ typedef enum {
 	GIT_OPT_SET_SSL_CERT_LOCATIONS,
 	GIT_OPT_SET_USER_AGENT,
 	GIT_OPT_ENABLE_STRICT_OBJECT_CREATION,
+	GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION,
 	GIT_OPT_SET_SSL_CIPHERS,
 	GIT_OPT_GET_USER_AGENT,
 } git_libgit2_opt_t;
@@ -289,6 +290,15 @@ typedef enum {
  *		> will be validated when creating a new commit.  This defaults
  *		> to enabled.
  *
+ *	* opts(GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, int enabled)
+ *
+ *		> Validate the target of a symbolic ref when creating it.  For
+ *		> example, `foobar` is not a valid ref, therefore `foobar` is
+ *		> not a valid target for a symbolic ref by default, whereas
+ *		> `refs/heads/foobar` is.  Disabling this bypasses validation
+ *		> so that an arbitrary strings such as `foobar` can be used
+ *		> for a symbolic ref target.  This defaults to enabled.
+ *
  *	* opts(GIT_OPT_SET_SSL_CIPHERS, const char *ciphers)
  *
  *		> Set the SSL ciphers use for HTTPS connections.
diff --git a/src/refs.c b/src/refs.c
index dbc7e5e..140b175 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -24,6 +24,8 @@
 #include <git2/signature.h>
 #include <git2/commit.h>
 
+bool git_reference__enable_symbolic_ref_target_validation = true;
+
 GIT__USE_STRMAP
 
 #define DEFAULT_NESTING_LEVEL	5
@@ -178,7 +180,8 @@ int git_reference_name_to_id(
 static int reference_normalize_for_repo(
 	git_refname_t out,
 	git_repository *repo,
-	const char *name)
+	const char *name,
+	bool validate)
 {
 	int precompose;
 	unsigned int flags = GIT_REF_FORMAT_ALLOW_ONELEVEL;
@@ -187,6 +190,9 @@ static int reference_normalize_for_repo(
 		precompose)
 		flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE;
 
+	if (!validate)
+		flags |= GIT_REF_FORMAT__VALIDATION_DISABLE;
+
 	return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags);
 }
 
@@ -213,7 +219,7 @@ int git_reference_lookup_resolved(
 
 	scan_type = GIT_REF_SYMBOLIC;
 
-	if ((error = reference_normalize_for_repo(scan_name, repo, name)) < 0)
+	if ((error = reference_normalize_for_repo(scan_name, repo, name, true)) < 0)
 		return error;
 
 	if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)
@@ -383,7 +389,7 @@ static int reference__create(
 	if (ref_out)
 		*ref_out = NULL;
 
-	error = reference_normalize_for_repo(normalized, repo, name);
+	error = reference_normalize_for_repo(normalized, repo, name, true);
 	if (error < 0)
 		return error;
 
@@ -404,7 +410,10 @@ static int reference__create(
 	} else {
 		git_refname_t normalized_target;
 
-		if ((error = reference_normalize_for_repo(normalized_target, repo, symbolic)) < 0)
+		error = reference_normalize_for_repo(normalized_target, repo,
+			symbolic, git_reference__enable_symbolic_ref_target_validation);
+
+		if (error < 0)
 			return error;
 
 		ref = git_reference__alloc_symbolic(normalized, normalized_target);
@@ -583,7 +592,7 @@ static int reference__rename(git_reference **out, git_reference *ref, const char
 	assert(ref && new_name && signature);
 
 	if ((error = reference_normalize_for_repo(
-			normalized, git_reference_owner(ref), new_name)) < 0)
+		normalized, git_reference_owner(ref), new_name, true)) < 0)
 		return error;
 
 
@@ -876,6 +885,7 @@ int git_reference__normalize_name(
 	int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;
 	unsigned int process_flags;
 	bool normalize = (buf != NULL);
+	bool validate = (flags & GIT_REF_FORMAT__VALIDATION_DISABLE) == 0;
 
 #ifdef GIT_USE_ICONV
 	git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
@@ -886,7 +896,7 @@ int git_reference__normalize_name(
 	process_flags = flags;
 	current = (char *)name;
 
-	if (*current == '/')
+	if (validate && *current == '/')
 		goto cleanup;
 
 	if (normalize)
@@ -902,6 +912,13 @@ int git_reference__normalize_name(
 	}
 #endif
 
+	if (!validate) {
+		git_buf_sets(buf, current);
+
+		error = git_buf_oom(buf) ? -1 : 0;
+		goto cleanup;
+	}
+
 	while (true) {
 		segment_len = ensure_segment_validity(current);
 		if (segment_len < 0) {
diff --git a/src/refs.h b/src/refs.h
index fda9532..80e655a 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -15,6 +15,8 @@
 #include "buffer.h"
 #include "oid.h"
 
+extern bool git_reference__enable_symbolic_ref_target_validation;
+
 #define GIT_REFS_DIR "refs/"
 #define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/"
 #define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/"
@@ -53,6 +55,7 @@
 #define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE
 
 #define GIT_REF_FORMAT__PRECOMPOSE_UNICODE	(1u << 16)
+#define GIT_REF_FORMAT__VALIDATION_DISABLE	(1u << 15)
 
 #define GIT_REFNAME_MAX 1024
 
diff --git a/src/settings.c b/src/settings.c
index 980233d..222bd6b 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -15,6 +15,7 @@
 #include "cache.h"
 #include "global.h"
 #include "object.h"
+#include "refs.h"
 
 void git_libgit2_version(int *major, int *minor, int *rev)
 {
@@ -193,6 +194,10 @@ int git_libgit2_opts(int key, ...)
 		git_object__strict_input_validation = (va_arg(ap, int) != 0);
 		break;
 
+	case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION:
+		git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0);
+		break;
+
 	case GIT_OPT_SET_SSL_CIPHERS:
 #ifdef GIT_OPENSSL
 		{