summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Kelley <phkelley@hotmail.com>2013-02-11 09:12:39 -0800
committerPhilip Kelley <phkelley@hotmail.com>2013-02-11 09:12:39 -0800
commite026cfee003e103d79e56983d68a206ae907eada (patch)
tree16720580285b40a2208d07cd06d1abd893493991
parent8c29dca6c372cae1c6a97d78252ee1d7ac10673b (diff)
parentdb4bb4158f1005267e8d0e5785cb75487440580a (diff)
downloadlibgit2-e026cfee003e103d79e56983d68a206ae907eada.tar.gz
Merge pull request #1323 from jamill/resolve_remote
Resolve a remote branch's remote
-rw-r--r--include/git2/branch.h25
-rw-r--r--include/git2/refspec.h20
-rw-r--r--src/branch.c81
-rw-r--r--src/refspec.c30
-rw-r--r--tests-clar/network/remotes.c11
-rw-r--r--tests-clar/refs/branches/remote.c119
6 files changed, 278 insertions, 8 deletions
diff --git a/include/git2/branch.h b/include/git2/branch.h
index 54a1ab118..3c7fb443c 100644
--- a/include/git2/branch.h
+++ b/include/git2/branch.h
@@ -210,6 +210,31 @@ GIT_EXTERN(int) git_branch_tracking_name(
GIT_EXTERN(int) git_branch_is_head(
git_reference *branch);
+/**
+ * Return the name of remote that the remote tracking branch belongs to.
+ *
+ * @param remote_name_out The user-allocated buffer which will be
+ * filled with the name of the remote. Pass NULL if you just want to
+ * get the needed size of the name of the remote as the output value.
+ *
+ * @param buffer_size Size of the `out` buffer in bytes.
+ *
+ * @param repo The repository where the branch lives.
+ *
+ * @param branch The reference to the remote tracking branch.
+ *
+ * @return Number of characters in the reference name
+ * including the trailing NUL byte; GIT_ENOTFOUND
+ * when no remote matching remote was gound,
+ * GIT_EAMBIGUOUS when the branch maps to several remotes,
+ * otherwise an error code.
+ */
+GIT_EXTERN(int) git_branch_remote_name(
+ char *remote_name_out,
+ size_t buffer_size,
+ git_repository *repo,
+ git_reference *branch);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/refspec.h b/include/git2/refspec.h
index ee06f8eca..ec7830b7c 100644
--- a/include/git2/refspec.h
+++ b/include/git2/refspec.h
@@ -53,6 +53,15 @@ 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
+ *
+ * @param refspec the refspec
+ * @param refname the name of the reference to check
+ * @return 1 if the refspec matches, 0 otherwise
+ */
+GIT_EXTERN(int) git_refspec_dst_matches(const git_refspec *refspec, const char *refname);
+
+/**
* Transform a reference to its target following the refspec's rules
*
* @param out where to store the target name
@@ -63,6 +72,17 @@ GIT_EXTERN(int) git_refspec_src_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/branch.c b/src/branch.c
index 3959409c5..936947a73 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -319,6 +319,87 @@ cleanup:
return error;
}
+int git_branch_remote_name(
+ char *remote_name_out,
+ size_t buffer_size,
+ git_repository *repo,
+ git_reference *branch)
+{
+ git_strarray remote_list = {0};
+ size_t i, remote_name_size;
+ git_remote *remote;
+ const git_refspec *fetchspec;
+ int error = 0;
+ char *remote_name = NULL;
+
+ assert(branch);
+
+ if (remote_name_out && buffer_size)
+ *remote_name_out = '\0';
+
+ /* Verify that this is a remote branch */
+ if (!git_reference_is_remote(branch)) {
+ giterr_set(GITERR_INVALID,
+ "Reference '%s' is not a remote branch.", branch->name);
+ error = GIT_ERROR;
+ goto cleanup;
+ }
+
+ /* Get the remotes */
+ if ((error = git_remote_list(&remote_list, repo)) < 0)
+ goto cleanup;
+
+ /* Find matching remotes */
+ for (i = 0; i < remote_list.count; i++) {
+ if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0)
+ goto cleanup;
+
+ fetchspec = git_remote_fetchspec(remote);
+
+ /* Defensivly check that we have a fetchspec */
+ if (fetchspec &&
+ git_refspec_dst_matches(fetchspec, branch->name)) {
+ /* If we have not already set out yet, then set
+ * it to the matching remote name. Otherwise
+ * multiple remotes match this reference, and it
+ * is ambiguous. */
+ if (!remote_name) {
+ remote_name = remote_list.strings[i];
+ } else {
+ git_remote_free(remote);
+ error = GIT_EAMBIGUOUS;
+ goto cleanup;
+ }
+ }
+
+ git_remote_free(remote);
+ }
+
+ if (remote_name) {
+ remote_name_size = strlen(remote_name) + 1;
+ error = (int) remote_name_size;
+
+ if (remote_name_out) {
+ if(remote_name_size > buffer_size) {
+ giterr_set(
+ GITERR_INVALID,
+ "Buffer too short to hold the remote name.");
+ error = GIT_ERROR;
+ goto cleanup;
+ }
+
+ memcpy(remote_name_out, remote_name, remote_name_size);
+ }
+ } else {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+cleanup:
+ git_strarray_free(&remote_list);
+ return error;
+}
+
int git_branch_tracking_name(
char *tracking_branch_name_out,
size_t buffer_size,
diff --git a/src/refspec.c b/src/refspec.c
index bd69f58ae..a51b0cfab 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -159,11 +159,19 @@ int git_refspec_src_matches(const git_refspec *refspec, const char *refname)
return (p_fnmatch(refspec->src, refname, 0) == 0);
}
-int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name)
+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);
+}
+
+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;
@@ -173,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;
}
@@ -182,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);
@@ -191,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 b138d8c10..51d6c946f 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;
diff --git a/tests-clar/refs/branches/remote.c b/tests-clar/refs/branches/remote.c
new file mode 100644
index 000000000..be355af46
--- /dev/null
+++ b/tests-clar/refs/branches/remote.c
@@ -0,0 +1,119 @@
+#include "clar_libgit2.h"
+#include "branch.h"
+#include "remote.h"
+
+static git_repository *g_repo;
+
+static const char *current_master_tip = "099fabac3a9ea935598528c27f866e34089c2eff";
+
+void test_refs_branches_remote__initialize(void)
+{
+ git_oid id;
+
+ g_repo = cl_git_sandbox_init("testrepo");
+ git_oid_fromstr(&id, current_master_tip);
+
+ /* Create test/master */
+ git_reference_create(NULL, g_repo, "refs/remotes/test/master", &id, 1);
+}
+
+void test_refs_branches_remote__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+void test_refs_branches_remote__can_get_remote_for_branch(void)
+{
+ git_reference *ref;
+ const char *name;
+ char *expectedRemoteName = "test";
+ int expectedRemoteNameLength = strlen(expectedRemoteName) + 1;
+ char remotename[1024] = {0};
+
+ cl_git_pass(git_branch_lookup(&ref, g_repo, "test/master", GIT_BRANCH_REMOTE));
+ cl_git_pass(git_branch_name(&name, ref));
+ cl_assert_equal_s("test/master", name);
+
+ cl_assert_equal_i(expectedRemoteNameLength,
+ git_branch_remote_name(NULL, 0, g_repo, ref));
+ cl_assert_equal_i(expectedRemoteNameLength,
+ git_branch_remote_name(remotename, expectedRemoteNameLength, g_repo, ref));
+ cl_assert_equal_s("test", remotename);
+
+ git_reference_free(ref);
+}
+
+void test_refs_branches_remote__insufficient_buffer_returns_error(void)
+{
+ git_reference *ref;
+ const char *name;
+ char *expectedRemoteName = "test";
+ int expectedRemoteNameLength = strlen(expectedRemoteName) + 1;
+ char remotename[1024] = {0};
+
+ cl_git_pass(git_branch_lookup(&ref, g_repo, "test/master", GIT_BRANCH_REMOTE));
+ cl_git_pass(git_branch_name(&name, ref));
+ cl_assert_equal_s("test/master", name);
+
+ cl_assert_equal_i(expectedRemoteNameLength,
+ git_branch_remote_name(NULL, 0, g_repo, ref));
+ cl_git_fail_with(GIT_ERROR,
+ git_branch_remote_name(remotename, expectedRemoteNameLength - 1, g_repo, ref));
+
+ git_reference_free(ref);
+}
+
+void test_refs_branches_remote__no_matching_remote_returns_error(void)
+{
+ git_reference *ref;
+ const char *name;
+ git_oid id;
+
+ git_oid_fromstr(&id, current_master_tip);
+
+ /* Create nonexistent/master */
+ git_reference_create(NULL, g_repo, "refs/remotes/nonexistent/master", &id, 1);
+
+ cl_git_pass(git_branch_lookup(&ref, g_repo,"nonexistent/master", GIT_BRANCH_REMOTE));
+ cl_git_pass(git_branch_name(&name, ref));
+ cl_assert_equal_s("nonexistent/master", name);
+
+ cl_git_fail_with(git_branch_remote_name(NULL, 0, g_repo, ref), GIT_ENOTFOUND);
+ git_reference_free(ref);
+}
+
+void test_refs_branches_remote__local_remote_returns_error(void)
+{
+ git_reference *ref;
+ const char *name;
+
+ cl_git_pass(git_branch_lookup(&ref,g_repo, "master", GIT_BRANCH_LOCAL));
+ cl_git_pass(git_branch_name(&name, ref));
+ cl_assert_equal_s("master",name);
+
+ cl_git_fail_with(git_branch_remote_name(NULL, 0, g_repo, ref), GIT_ERROR);
+ git_reference_free(ref);
+}
+
+void test_refs_branches_remote__ambiguous_remote_returns_error(void)
+{
+ git_reference *ref;
+ const char *name;
+ git_remote *remote;
+
+ /* Create the remote */
+ cl_git_pass(git_remote_create(&remote, g_repo, "addtest", "http://github.com/libgit2/libgit2"));
+
+ /* Update the remote fetch spec */
+ cl_git_pass(git_remote_set_fetchspec(remote, "refs/heads/*:refs/remotes/test/*"));
+ cl_git_pass(git_remote_save(remote));
+
+ git_remote_free(remote);
+
+ cl_git_pass(git_branch_lookup(&ref,g_repo, "test/master", GIT_BRANCH_REMOTE));
+ cl_git_pass(git_branch_name(&name, ref));
+ cl_assert_equal_s("test/master", name);
+
+ cl_git_fail_with(git_branch_remote_name(NULL, 0, g_repo, ref), GIT_EAMBIGUOUS);
+ git_reference_free(ref);
+}