summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVicent Martí <vicent@github.com>2012-07-12 09:35:35 -0700
committerVicent Martí <vicent@github.com>2012-07-12 09:35:35 -0700
commitdb2d4061f60f9390b9d37100b15ba5e819927aa4 (patch)
treebe93e45ba71395254565a1117a142510d2df30f8
parent48bcf81dd2584d91e5922dd1458dab6c4173bdcf (diff)
parent12595ab8f91a71e5a596a883b31789d5317e9ec2 (diff)
downloadlibgit2-db2d4061f60f9390b9d37100b15ba5e819927aa4.tar.gz
Merge pull request #814 from nulltoken/topic/revparse-refac
Revparse refactoring: a start
-rw-r--r--include/git2/commit.h19
-rw-r--r--include/git2/refs.h21
-rw-r--r--src/commit.c45
-rw-r--r--src/refs.c74
-rw-r--r--src/revparse.c81
-rw-r--r--tests-clar/commit/parent.c57
-rw-r--r--tests-clar/refs/read.c4
-rw-r--r--tests-clar/refs/remotetracking.c49
8 files changed, 281 insertions, 69 deletions
diff --git a/include/git2/commit.h b/include/git2/commit.h
index 640adf5c..5b6da520 100644
--- a/include/git2/commit.h
+++ b/include/git2/commit.h
@@ -179,6 +179,25 @@ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsig
GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned int n);
/**
+ * Get the commit object that is the <n>th generation ancestor
+ * of the named commit object, following only the first parents.
+ * The returned commit has to be freed by the caller.
+ *
+ * Passing `0` as the generation number returns another instance of the
+ * base commit itself.
+ *
+ * @param ancestor Pointer where to store the ancestor commit
+ * @param commit a previously loaded commit.
+ * @param n the requested generation
+ * @return 0 on success; GIT_ENOTFOUND if no matching ancestor exists
+ * or an error code
+ */
+int git_commit_nth_gen_ancestor(
+ git_commit **ancestor,
+ const git_commit *commit,
+ unsigned int n);
+
+/**
* Create a new commit in the repository using `git_object`
* instances as parameters.
*
diff --git a/include/git2/refs.h b/include/git2/refs.h
index 7f6eb0e9..b119e90b 100644
--- a/include/git2/refs.h
+++ b/include/git2/refs.h
@@ -363,6 +363,27 @@ GIT_EXTERN(int) git_reference_foreach_glob(
*/
GIT_EXTERN(int) git_reference_has_log(git_reference *ref);
+
+/**
+ * Return the reference supporting the remote tracking branch,
+ * given a reference branch.
+ *
+ * The input reference has to be located in the `refs/heads`
+ * namespace.
+ *
+ * @param tracking_ref Pointer where to store the retrieved
+ * reference.
+ *
+ * @param branch_ref A git local branch reference.
+ *
+ * @return 0 on success; GIT_ENOTFOUND when no remote tracking
+ * reference exists, otherwise an error code.
+ */
+GIT_EXTERN(int) git_reference_remote_tracking_from_branch(
+ git_reference **tracking_ref,
+ git_reference *branch_ref
+);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/commit.c b/src/commit.c
index a3baf9d4..32c47944 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -229,19 +229,25 @@ GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length)
GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid);
-
int git_commit_tree(git_tree **tree_out, git_commit *commit)
{
assert(commit);
return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_oid);
}
+const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n)
+{
+ assert(commit);
+
+ return git_vector_get(&commit->parent_oids, n);
+}
+
int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
{
- git_oid *parent_oid;
+ const git_oid *parent_oid;
assert(commit);
- parent_oid = git_vector_get(&commit->parent_oids, n);
+ parent_oid = git_commit_parent_oid(commit, n);
if (parent_oid == NULL) {
giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
return GIT_ENOTFOUND;
@@ -250,9 +256,36 @@ int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
return git_commit_lookup(parent, commit->object.repo, parent_oid);
}
-const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n)
+int git_commit_nth_gen_ancestor(
+ git_commit **ancestor,
+ const git_commit *commit,
+ unsigned int n)
{
- assert(commit);
+ git_commit *current, *parent;
+ int error;
- return git_vector_get(&commit->parent_oids, n);
+ assert(ancestor && commit);
+
+ current = (git_commit *)commit;
+
+ if (n == 0)
+ return git_commit_lookup(
+ ancestor,
+ commit->object.repo,
+ git_object_id((const git_object *)commit));
+
+ while (n--) {
+ error = git_commit_parent(&parent, (git_commit *)current, 0);
+
+ if (current != commit)
+ git_commit_free(current);
+
+ if (error < 0)
+ return error;
+
+ current = parent;
+ }
+
+ *ancestor = parent;
+ return 0;
}
diff --git a/src/refs.c b/src/refs.c
index e8f9fc8d..13022c7a 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -11,6 +11,7 @@
#include "fileops.h"
#include "pack.h"
#include "reflog.h"
+#include "config.h"
#include <git2/tag.h>
#include <git2/object.h>
@@ -1811,3 +1812,76 @@ int git_reference_has_log(
return result;
}
+
+//TODO: How about also taking care of local tracking branches?
+//cf. http://alblue.bandlem.com/2011/07/git-tip-of-week-tracking-branches.html
+int git_reference_remote_tracking_from_branch(
+ git_reference **tracking_ref,
+ git_reference *branch_ref)
+{
+ git_config *config = NULL;
+ const char *name, *remote, *merge;
+ git_buf buf = GIT_BUF_INIT;
+ int error = -1;
+
+ assert(tracking_ref && branch_ref);
+
+ name = git_reference_name(branch_ref);
+
+ if (git__prefixcmp(name, GIT_REFS_HEADS_DIR)) {
+ giterr_set(
+ GITERR_INVALID,
+ "Failed to retrieve tracking reference - '%s' is not a branch.",
+ name);
+ return -1;
+ }
+
+ if (git_repository_config(&config, branch_ref->owner) < 0)
+ return -1;
+
+ if (git_buf_printf(
+ &buf,
+ "branch.%s.remote",
+ name + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto cleanup;
+
+ if ((error = git_config_get_string(&remote, config, git_buf_cstr(&buf))) < 0)
+ goto cleanup;
+
+ error = -1;
+
+ git_buf_clear(&buf);
+
+ //TODO: Is it ok to fail when no merge target is found?
+ if (git_buf_printf(
+ &buf,
+ "branch.%s.merge",
+ name + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto cleanup;
+
+ if (git_config_get_string(&merge, config, git_buf_cstr(&buf)) < 0)
+ goto cleanup;
+
+ //TODO: Should we test this?
+ if (git__prefixcmp(merge, GIT_REFS_HEADS_DIR))
+ goto cleanup;
+
+ git_buf_clear(&buf);
+
+ if (git_buf_printf(
+ &buf,
+ "refs/remotes/%s/%s",
+ remote,
+ merge + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto cleanup;
+
+ error = git_reference_lookup(
+ tracking_ref,
+ branch_ref->owner,
+ git_buf_cstr(&buf));
+
+cleanup:
+ git_config_free(config);
+ git_buf_free(&buf);
+ return error;
+}
diff --git a/src/revparse.c b/src/revparse.c
index f9859b18..2b03c86b 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -104,7 +104,16 @@ cleanup:
return error;
}
-extern int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec);
+static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char *spec)
+{
+ git_oid oid;
+ size_t speclen = strlen(spec);
+
+ if (git_oid_fromstrn(&oid, spec, speclen) < 0)
+ return GIT_ENOTFOUND;
+
+ return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);
+}
static int maybe_describe(git_object**out, git_repository *repo, const char *spec)
{
@@ -123,21 +132,10 @@ static int maybe_describe(git_object**out, git_repository *repo, const char *spe
if (!match)
return GIT_ENOTFOUND;
- return revparse_lookup_object(out, repo, substr+2);
+ return maybe_sha_or_abbrev(out, repo, substr+2);
}
-static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char *spec)
-{
- git_oid oid;
- size_t speclen = strlen(spec);
-
- if (git_oid_fromstrn(&oid, spec, speclen) < 0)
- return GIT_ENOTFOUND;
-
- return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);
-}
-
-int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec)
+static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec)
{
int error;
git_reference *ref;
@@ -251,32 +249,12 @@ static int walk_ref_history(git_object **out, git_repository *repo, const char *
date_error = git__date_parse(&timestamp, git_buf_cstr(&datebuf));
/* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */
- if (!git__prefixcmp(git_reference_name(disambiguated), GIT_REFS_HEADS_DIR) &&
- (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}"))) {
- git_config *cfg;
- if (!git_repository_config(&cfg, repo)) {
- /* Is the ref a tracking branch? */
- const char *remote;
- git_buf_clear(&buf);
- git_buf_printf(&buf, "branch.%s.remote",
- git_reference_name(disambiguated) + strlen(GIT_REFS_HEADS_DIR));
-
- if (!git_config_get_string(&remote, cfg, git_buf_cstr(&buf))) {
- /* Yes. Find the first merge target name. */
- const char *mergetarget;
- git_buf_clear(&buf);
- git_buf_printf(&buf, "branch.%s.merge",
- git_reference_name(disambiguated) + strlen(GIT_REFS_HEADS_DIR));
-
- if (!git_config_get_string(&mergetarget, cfg, git_buf_cstr(&buf)) &&
- !git__prefixcmp(mergetarget, "refs/heads/")) {
- /* Success. Look up the target and fetch the object. */
- git_buf_clear(&buf);
- git_buf_printf(&buf, "refs/remotes/%s/%s", remote, mergetarget+11);
- retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf));
- }
- }
- git_config_free(cfg);
+ if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) {
+ git_reference *tracking;
+
+ if (!(retcode = git_reference_remote_tracking_from_branch(&tracking, disambiguated))) {
+ retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_reference_name(tracking));
+ git_reference_free(tracking);
}
}
@@ -524,8 +502,7 @@ static int handle_caret_syntax(git_object **out, git_repository *repo, git_objec
static int handle_linear_syntax(git_object **out, git_object *obj, const char *movement)
{
- git_commit *commit1, *commit2;
- int i, n;
+ int n;
/* Dereference until we reach a commit. */
if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) {
@@ -539,26 +516,8 @@ static int handle_linear_syntax(git_object **out, git_object *obj, const char *m
} else if (git__strtol32(&n, movement, NULL, 0) < 0) {
return GIT_ERROR;
}
- commit1 = (git_commit*)obj;
- /* "~0" just returns the input */
- if (n == 0) {
- *out = obj;
- return 0;
- }
-
- for (i=0; i<n; i++) {
- if (git_commit_parent(&commit2, commit1, 0) < 0) {
- return GIT_ERROR;
- }
- if (commit1 != (git_commit*)obj) {
- git_commit_free(commit1);
- }
- commit1 = commit2;
- }
-
- *out = (git_object*)commit1;
- return 0;
+ return git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)obj, n);
}
static int oid_for_tree_path(git_oid *out, git_tree *tree, git_repository *repo, const char *path)
diff --git a/tests-clar/commit/parent.c b/tests-clar/commit/parent.c
new file mode 100644
index 00000000..a0075773
--- /dev/null
+++ b/tests-clar/commit/parent.c
@@ -0,0 +1,57 @@
+#include "clar_libgit2.h"
+
+static git_repository *_repo;
+static git_commit *commit;
+
+void test_commit_parent__initialize(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git")));
+
+ git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ cl_git_pass(git_commit_lookup(&commit, _repo, &oid));
+}
+
+void test_commit_parent__cleanup(void)
+{
+ git_commit_free(commit);
+ git_repository_free(_repo);
+}
+
+static void assert_nth_gen_parent(unsigned int gen, const char *expected_oid)
+{
+ git_commit *parent = NULL;
+ int error;
+
+ error = git_commit_nth_gen_ancestor(&parent, commit, gen);
+
+ if (expected_oid != NULL) {
+ cl_assert_equal_i(0, error);
+ cl_assert_equal_i(0, git_oid_streq(git_commit_id(parent), expected_oid));
+ } else
+ cl_assert_equal_i(GIT_ENOTFOUND, error);
+
+ git_commit_free(parent);
+}
+
+/*
+ * $ git show be35~0
+ * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644
+ *
+ * $ git show be35~1
+ * commit 9fd738e8f7967c078dceed8190330fc8648ee56a
+ *
+ * $ git show be35~3
+ * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644
+ *
+ * $ git show be35~42
+ * fatal: ambiguous argument 'be35~42': unknown revision or path not in the working tree.
+ */
+void test_commit_parent__can_retrieve_nth_generation_parent(void)
+{
+ assert_nth_gen_parent(0, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644");
+ assert_nth_gen_parent(1, "9fd738e8f7967c078dceed8190330fc8648ee56a");
+ assert_nth_gen_parent(3, "5b5b025afb0b4c913b4c338a42934a3863bf3644");
+ assert_nth_gen_parent(42, NULL);
+}
diff --git a/tests-clar/refs/read.c b/tests-clar/refs/read.c
index 6838ead7..ce4eefeb 100644
--- a/tests-clar/refs/read.c
+++ b/tests-clar/refs/read.c
@@ -16,12 +16,12 @@ static git_repository *g_repo;
void test_refs_read__initialize(void)
{
- g_repo = cl_git_sandbox_init("testrepo");
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
}
void test_refs_read__cleanup(void)
{
- cl_git_sandbox_cleanup();
+ git_repository_free(g_repo);
}
void test_refs_read__loose_tag(void)
diff --git a/tests-clar/refs/remotetracking.c b/tests-clar/refs/remotetracking.c
new file mode 100644
index 00000000..c4ec588e
--- /dev/null
+++ b/tests-clar/refs/remotetracking.c
@@ -0,0 +1,49 @@
+#include "clar_libgit2.h"
+
+static git_repository *g_repo;
+
+void test_refs_remotetracking__initialize(void)
+{
+ cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git")));
+}
+
+void test_refs_remotetracking__cleanup(void)
+{
+ git_repository_free(g_repo);
+}
+
+void test_refs_remotetracking__unfound_returns_GIT_ENOTFOUND(void)
+{
+ git_reference *branch, *tracking;
+
+ cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/subtrees"));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_reference_remote_tracking_from_branch(&tracking, branch));
+
+ git_reference_free(branch);
+}
+
+void test_refs_remotetracking__retrieving_from_a_non_head_fails(void)
+{
+ git_reference *branch, *tracking;
+
+ cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/tags/e90810b"));
+
+ cl_git_fail(git_reference_remote_tracking_from_branch(&tracking, branch));
+
+ git_reference_free(branch);
+}
+
+void test_refs_remotetracking__can_retrieve_a_remote_tracking_branch_reference(void)
+{
+ git_reference *branch, *tracking;
+
+ cl_git_pass(git_reference_lookup(&branch, g_repo, "refs/heads/master"));
+
+ cl_git_pass(git_reference_remote_tracking_from_branch(&tracking, branch));
+
+ cl_assert_equal_s("refs/remotes/test/master", git_reference_name(tracking));
+
+ git_reference_free(branch);
+ git_reference_free(tracking);
+}