Commit db2d4061 by Vicent Martí

Merge pull request #814 from nulltoken/topic/revparse-refac

Revparse refactoring: a start
parents 48bcf81d 12595ab8
...@@ -179,6 +179,25 @@ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsig ...@@ -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); 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` * Create a new commit in the repository using `git_object`
* instances as parameters. * instances as parameters.
* *
......
...@@ -363,6 +363,27 @@ GIT_EXTERN(int) git_reference_foreach_glob( ...@@ -363,6 +363,27 @@ GIT_EXTERN(int) git_reference_foreach_glob(
*/ */
GIT_EXTERN(int) git_reference_has_log(git_reference *ref); 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 GIT_END_DECL
#endif #endif
...@@ -229,19 +229,25 @@ GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) ...@@ -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(unsigned int, parentcount, commit->parent_oids.length)
GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid); GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid);
int git_commit_tree(git_tree **tree_out, git_commit *commit) int git_commit_tree(git_tree **tree_out, git_commit *commit)
{ {
assert(commit); assert(commit);
return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_oid); 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) int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n)
{ {
git_oid *parent_oid; const git_oid *parent_oid;
assert(commit); assert(commit);
parent_oid = git_vector_get(&commit->parent_oids, n); parent_oid = git_commit_parent_oid(commit, n);
if (parent_oid == NULL) { if (parent_oid == NULL) {
giterr_set(GITERR_INVALID, "Parent %u does not exist", n); giterr_set(GITERR_INVALID, "Parent %u does not exist", n);
return GIT_ENOTFOUND; return GIT_ENOTFOUND;
...@@ -250,9 +256,36 @@ int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) ...@@ -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); 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;
} }
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "fileops.h" #include "fileops.h"
#include "pack.h" #include "pack.h"
#include "reflog.h" #include "reflog.h"
#include "config.h"
#include <git2/tag.h> #include <git2/tag.h>
#include <git2/object.h> #include <git2/object.h>
...@@ -1811,3 +1812,76 @@ int git_reference_has_log( ...@@ -1811,3 +1812,76 @@ int git_reference_has_log(
return result; 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;
}
...@@ -104,7 +104,16 @@ cleanup: ...@@ -104,7 +104,16 @@ cleanup:
return error; 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) 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 ...@@ -123,21 +132,10 @@ static int maybe_describe(git_object**out, git_repository *repo, const char *spe
if (!match) if (!match)
return GIT_ENOTFOUND; 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) static int revparse_lookup_object(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)
{ {
int error; int error;
git_reference *ref; git_reference *ref;
...@@ -251,32 +249,12 @@ static int walk_ref_history(git_object **out, git_repository *repo, const char * ...@@ -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)); 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. */ /* @{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) && if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) {
(!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}"))) { git_reference *tracking;
git_config *cfg;
if (!git_repository_config(&cfg, repo)) { if (!(retcode = git_reference_remote_tracking_from_branch(&tracking, disambiguated))) {
/* Is the ref a tracking branch? */ retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_reference_name(tracking));
const char *remote; git_reference_free(tracking);
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);
} }
} }
...@@ -524,8 +502,7 @@ static int handle_caret_syntax(git_object **out, git_repository *repo, git_objec ...@@ -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) static int handle_linear_syntax(git_object **out, git_object *obj, const char *movement)
{ {
git_commit *commit1, *commit2; int n;
int i, n;
/* Dereference until we reach a commit. */ /* Dereference until we reach a commit. */
if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { 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 ...@@ -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) { } else if (git__strtol32(&n, movement, NULL, 0) < 0) {
return GIT_ERROR; return GIT_ERROR;
} }
commit1 = (git_commit*)obj;
/* "~0" just returns the input */ return git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)obj, n);
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;
} }
static int oid_for_tree_path(git_oid *out, git_tree *tree, git_repository *repo, const char *path) static int oid_for_tree_path(git_oid *out, git_tree *tree, git_repository *repo, const char *path)
......
#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);
}
...@@ -16,12 +16,12 @@ static git_repository *g_repo; ...@@ -16,12 +16,12 @@ static git_repository *g_repo;
void test_refs_read__initialize(void) 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) void test_refs_read__cleanup(void)
{ {
cl_git_sandbox_cleanup(); git_repository_free(g_repo);
} }
void test_refs_read__loose_tag(void) void test_refs_read__loose_tag(void)
......
#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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment