Commit db4bb4158f1005267e8d0e5785cb75487440580a

Jameson Miller 2013-02-07T14:53:52

Teach refspec to transform destination reference to source reference

diff --git a/include/git2/refspec.h b/include/git2/refspec.h
index 3e83e41..ec7830b 100644
--- a/include/git2/refspec.h
+++ b/include/git2/refspec.h
@@ -53,7 +53,7 @@ GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec);
 GIT_EXTERN(int) git_refspec_src_matches(const git_refspec *refspec, const char *refname);
 
 /**
- * Check if a refspec's destination descriptor matches a reference 
+ * Check if a refspec's destination descriptor matches a reference
  *
  * @param refspec the refspec
  * @param refname the name of the reference to check
@@ -72,6 +72,17 @@ GIT_EXTERN(int) git_refspec_dst_matches(const git_refspec *refspec, const char *
  */
 GIT_EXTERN(int) git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name);
 
+/**
+ * Transform a target reference to its source reference following the refspec's rules
+ *
+ * @param out where to store the source reference name
+ * @param outlen the size of the `out` buffer
+ * @param spec the refspec
+ * @param name the name of the reference to transform
+ * @return 0, GIT_EBUFS or another error
+ */
+GIT_EXTERN(int) git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name);
+
 GIT_END_DECL
 
 #endif
diff --git a/src/refspec.c b/src/refspec.c
index 4f324d3..a51b0cf 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -163,15 +163,15 @@ int git_refspec_dst_matches(const git_refspec *refspec, const char *refname)
 {
 	if (refspec == NULL || refspec->dst == NULL)
 		return false;
-    
+
 	return (p_fnmatch(refspec->dst, refname, 0) == 0);
 }
 
-int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name)
+static int refspec_transform_internal(char *out, size_t outlen, const char *from, const char *to, const char *name)
 {
 	size_t baselen, namelen;
 
-	baselen = strlen(spec->dst);
+	baselen = strlen(to);
 	if (outlen <= baselen) {
 		giterr_set(GITERR_INVALID, "Reference name too long");
 		return GIT_EBUFS;
@@ -181,8 +181,8 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con
 	 * No '*' at the end means that it's mapped to one specific local
 	 * branch, so no actual transformation is needed.
 	 */
-	if (spec->dst[baselen - 1] != '*') {
-		memcpy(out, spec->dst, baselen + 1); /* include '\0' */
+	if (to[baselen - 1] != '*') {
+		memcpy(out, to, baselen + 1); /* include '\0' */
 		return 0;
 	}
 
@@ -190,7 +190,7 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con
 	baselen--;
 
 	/* skip the prefix, -1 is for the '*' */
-	name += strlen(spec->src) - 1;
+	name += strlen(from) - 1;
 
 	namelen = strlen(name);
 
@@ -199,12 +199,22 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con
 		return GIT_EBUFS;
 	}
 
-	memcpy(out, spec->dst, baselen);
+	memcpy(out, to, baselen);
 	memcpy(out + baselen, name, namelen + 1);
 
 	return 0;
 }
 
+int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name)
+{
+	return refspec_transform_internal(out, outlen, spec->src, spec->dst, name);
+}
+
+int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name)
+{
+	return refspec_transform_internal(out, outlen, spec->dst, spec->src, name);
+}
+
 static int refspec_transform(git_buf *out, const char *from, const char *to, const char *name)
 {
 	if (git_buf_sets(out, to) < 0)
diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c
index b138d8c..51d6c94 100644
--- a/tests-clar/network/remotes.c
+++ b/tests-clar/network/remotes.c
@@ -173,13 +173,20 @@ void test_network_remotes__fnmatch(void)
 
 void test_network_remotes__transform(void)
 {
-	char ref[1024];
+	char ref[1024] = {0};
 
-	memset(ref, 0x0, sizeof(ref));
 	cl_git_pass(git_refspec_transform(ref, sizeof(ref), _refspec, "refs/heads/master"));
 	cl_assert_equal_s(ref, "refs/remotes/test/master");
 }
 
+void test_network_remotes__transform_destination_to_source(void)
+{
+	char ref[1024] = {0};
+
+	cl_git_pass(git_refspec_rtransform(ref, sizeof(ref), _refspec, "refs/remotes/test/master"));
+	cl_assert_equal_s(ref, "refs/heads/master");
+}
+
 void test_network_remotes__transform_r(void)
 {
 	git_buf buf = GIT_BUF_INIT;