Commit 452bf57cbe665768810f2597aba50b9afc9509a7

Richard Ipsum 2016-08-27T13:42:53

Make symbolic ref target validation optional Introduce GIT_OPT_ENABLE_SYMBOLIC_REF_TARGET_VALIDATION option. Setting this option to 0 allows validation of a symbolic ref's target to be bypassed. This option is enabled by default. This mechanism is added primarily to address a discrepancy between git behaviour and libgit2 behaviour, whereby the former allows the symbolic ref target to carry an arbitrary string and the latter does not, so: $ git symbolic-ref refs/heads/foo bar $ cat .git/refs/heads/foo ref: bar where as attempting the same via libgit2 raises an error: The given reference name 'bar' is not valid this mechanism also allows those that might want to make use of git's more lenient treatment of symbolic ref targets to do so.

diff --git a/include/git2/common.h b/include/git2/common.h
index 18abe46..02d2630 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -157,6 +157,7 @@ typedef enum {
 	GIT_OPT_SET_SSL_CERT_LOCATIONS,
 	GIT_OPT_SET_USER_AGENT,
 	GIT_OPT_ENABLE_STRICT_OBJECT_CREATION,
+	GIT_OPT_ENABLE_SYMBOLIC_REF_TARGET_VALIDATION,
 	GIT_OPT_SET_SSL_CIPHERS,
 	GIT_OPT_GET_USER_AGENT,
 } git_libgit2_opt_t;
@@ -270,6 +271,18 @@ typedef enum {
  *		> example, when this is enabled, the parent(s) and tree inputs
  *		> will be validated when creating a new commit.  This defaults
  *		> to disabled.
+ *
+ *	* opts(GIT_OPT_ENABLE_SYMBOLIC_REF_TARGET_VALIDATION, 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,
+ *		> where as '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 bff443a..c77653d 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
@@ -175,10 +177,11 @@ int git_reference_name_to_id(
 	return 0;
 }
 
-static int reference_normalize_for_repo(
+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,9 +190,29 @@ static int reference_normalize_for_repo(
 		precompose)
 		flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE;
 
+	if (!validate) {
+		flags |= GIT_REF_VALIDATION_DISABLE;
+	}
+
 	return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags);
 }
 
+static int reference_normalize_for_repo(
+	git_refname_t out,
+	git_repository *repo,
+	const char *name)
+{
+	return reference__normalize_for_repo(out, repo, name, true);
+}
+
+static int reference_normalize_for_repo_without_validation(
+	git_refname_t out,
+	git_repository *repo,
+	const char *name)
+{
+	return reference__normalize_for_repo(out, repo, name, false);
+}
+
 int git_reference_lookup_resolved(
 	git_reference **ref_out,
 	git_repository *repo,
@@ -404,7 +427,13 @@ static int reference__create(
 	} else {
 		git_refname_t normalized_target;
 
-		if ((error = reference_normalize_for_repo(normalized_target, repo, symbolic)) < 0)
+		if (git_reference__enable_symbolic_ref_target_validation) {
+			error = reference_normalize_for_repo(normalized_target, repo, symbolic);
+		} else {
+			error = reference_normalize_for_repo_without_validation(normalized_target, repo, symbolic);
+		}
+
+		if (error < 0)
 			return error;
 
 		ref = git_reference__alloc_symbolic(normalized, normalized_target);
@@ -876,6 +905,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_VALIDATION_DISABLE) == 0;
 
 #ifdef GIT_USE_ICONV
 	git_path_iconv_t ic = GIT_PATH_ICONV_INIT;
@@ -886,7 +916,7 @@ int git_reference__normalize_name(
 	process_flags = flags;
 	current = (char *)name;
 
-	if (*current == '/')
+	if (validate && *current == '/')
 		goto cleanup;
 
 	if (normalize)
@@ -902,6 +932,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..c4786dd 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_VALIDATION_DISABLE	(1u << 15)
 
 #define GIT_REFNAME_MAX 1024
 
diff --git a/src/settings.c b/src/settings.c
index cb2317f..1fcdce2 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)
 {
@@ -191,6 +192,10 @@ int git_libgit2_opts(int key, ...)
 		git_object__strict_input_validation = (va_arg(ap, int) != 0);
 		break;
 
+	case GIT_OPT_ENABLE_SYMBOLIC_REF_TARGET_VALIDATION:
+		git_reference__enable_symbolic_ref_target_validation = (va_arg(ap, int) != 0);
+		break;
+
 	case GIT_OPT_SET_SSL_CIPHERS:
 #ifdef GIT_OPENSSL
 		{