Commit 11995603330208ca0b2abf919ffc862d461bfc60

Stefan Sperling 2017-11-05T17:26:31

resolve symbolic refs

diff --git a/include/got_object.h b/include/got_object.h
index 3be1c83..eeca74e 100644
--- a/include/got_object.h
+++ b/include/got_object.h
@@ -14,6 +14,6 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-struct got_object {
+struct got_object_id {
 	u_int8_t sha1[SHA1_DIGEST_LENGTH];
 };
diff --git a/include/got_refs.h b/include/got_refs.h
index 874712f..4c5b34a 100644
--- a/include/got_refs.h
+++ b/include/got_refs.h
@@ -43,8 +43,12 @@ struct got_reference {
 #define GOT_REF_MERGE_HEAD	"MERGE_HEAD"
 #define GOT_REF_FETCH_HEAD	"FETCH_HEAD"
 
-const struct got_error *
-got_ref_open(struct got_reference **, const char *, const char *);
+struct got_repository;
+struct got_object_id;
 
+const struct got_error * got_ref_open(struct got_reference **,
+    struct got_repository *, const char *);
 void got_ref_close(struct got_reference *);
-
+struct got_reference *got_ref_dup(struct got_reference *);
+const struct got_error *got_ref_resolve(struct got_object_id **,
+    struct got_repository *, struct got_reference *);
diff --git a/include/got_repository.h b/include/got_repository.h
index 89c3353..7d9c9c2 100644
--- a/include/got_repository.h
+++ b/include/got_repository.h
@@ -25,6 +25,12 @@ void got_repo_close(struct got_repository*);
 /* Get the absolute path to the top-level directory of a repository. */
 const char *got_repo_get_path(struct got_repository *);
 
+char *got_repo_get_path_git_dir(struct got_repository *);
+char *got_repo_get_path_objects(struct got_repository *);
+char *got_repo_get_path_refs(struct got_repository *);
+
+struct got_reference;
+
 /* Get a reference, by name, from a repository. */
 const struct got_error *got_repo_get_reference(struct got_reference **,
     struct got_repository *, const char *);
diff --git a/lib/refs.c b/lib/refs.c
index c520fc2..a51fefd 100644
--- a/lib/refs.c
+++ b/lib/refs.c
@@ -20,12 +20,17 @@
 #include <stdlib.h>
 #include <string.h>
 #include <util.h>
+#include <limits.h>
+#include <errno.h>
 
 #include "got_error.h"
+#include "got_object.h"
+#include "got_repository.h"
 #include "got_refs.h"
 
 #include "path.h"
 
+
 static const struct got_error *
 parse_symref(struct got_reference **ref, const char *name, const char *line)
 {
@@ -54,18 +59,39 @@ parse_symref(struct got_reference **ref, const char *name, const char *line)
 }
 
 static int
+parse_xdigit(uint8_t *val, const char *hex)
+{
+	char *ep;
+	long lval;
+
+	errno = 0;
+	lval = strtol(hex, &ep, 16);
+	if (hex[0] == '\0' || *ep != '\0')
+		return 0;
+	if (errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN))
+		return 0;
+
+	*val = (uint8_t)lval;
+	return 1;
+}
+
+static int
 parse_sha1_digest(uint8_t *digest, const char *line)
 {
-	uint8_t b;
-	int i, n;
+	uint8_t b = 0;
+	char hex[3] = {'\0', '\0', '\0'};
+	int i, j;
 
-	memset(digest, 0, SHA1_DIGEST_LENGTH);
 	for (i = 0; i < SHA1_DIGEST_LENGTH; i++) {
-		n = sscanf(line, "%hhx", &b);
-		if (n == 1)
-			digest[i] = b;
-		else
+		if (line[0] == '\0' || line[1] == '\0')
+			return 0;
+		for (j = 0; j < 2; j++) {
+			hex[j] = *line;
+			line++;
+		}
+		if (!parse_xdigit(&b, hex))
 			return 0;
+		digest[i] = b;
 	}
 
 	return 1;
@@ -121,14 +147,37 @@ done:
 	return err;
 }
 
+static char *
+get_refs_dir_path(struct got_repository *repo, const char *refname)
+{
+	/* Some refs live in the .git directory. */
+	if (strcmp(refname, GOT_REF_HEAD) == 0 ||
+	    strcmp(refname, GOT_REF_ORIG_HEAD) == 0 ||
+	    strcmp(refname, GOT_REF_MERGE_HEAD) == 0 ||
+	    strcmp(refname, GOT_REF_FETCH_HEAD) == 0)
+		return got_repo_get_path_git_dir(repo);
+
+	/* Is the ref name relative to the .git directory? */
+	if (strncmp(refname, "refs/", 5) == 0)
+		return got_repo_get_path_git_dir(repo);
+
+	return got_repo_get_path_refs(repo);
+}
+
 const struct got_error *
-got_ref_open(struct got_reference **ref, const char *path_refs,
+got_ref_open(struct got_reference **ref, struct got_repository *repo,
    const char *refname)
 {
 	const struct got_error *err = NULL;
 	char *path_ref = NULL;
 	char *normpath = NULL;
 	const char *parent_dir;
+	char *path_refs = get_refs_dir_path(repo, refname);
+
+	if (path_refs == NULL) {
+		err = got_error(GOT_ERR_NO_MEM);
+		goto done;
+	}
 	
 	/* XXX For now, this assumes that refs exist in the filesystem. */
 
@@ -143,10 +192,11 @@ got_ref_open(struct got_reference **ref, const char *path_refs,
 		goto done;
 	}
 
-	err = parse_ref_file(ref, refname, normpath ? normpath : path_refs);
+	err = parse_ref_file(ref, refname, normpath);
 done:
 	free(normpath);
 	free(path_ref);
+	free(path_refs);
 	return err;
 }
 
@@ -159,3 +209,81 @@ got_ref_close(struct got_reference *ref)
 		free(ref->ref.ref.name);
 	free(ref);
 }
+
+struct got_reference *
+got_ref_dup(struct got_reference *ref)
+{
+	struct got_reference *ret = calloc(1, sizeof(*ret));
+	char *name = NULL;
+	char *symref = NULL;
+
+	if (ret == NULL)
+		return NULL;
+
+	ret->flags = ref->flags;
+	if (ref->flags & GOT_REF_IS_SYMBOLIC) {
+		ret->ref.symref.name = strdup(ref->ref.symref.name);
+		if (ret->ref.symref.name == NULL) {
+			free(ret);
+			return NULL;
+		}
+		ret->ref.symref.ref = strdup(ref->ref.symref.ref);
+		if (ret->ref.symref.ref == NULL) {
+			free(ret->ref.symref.name);
+			free(ret);
+			return NULL;
+		}
+	} else {
+		ref->ref.ref.name = strdup(ref->ref.ref.name);
+		if (ref->ref.ref.name == NULL) {
+			free(ret);
+			return NULL;
+		}
+		memcpy(ret->ref.ref.sha1, ref->ref.ref.sha1,
+		    SHA1_DIGEST_LENGTH);
+	}
+
+	return ret;
+}
+
+static const struct got_error *
+resolve_symbolic_ref(struct got_reference **resolved,
+    struct got_repository *repo, struct got_reference *ref)
+{
+	struct got_reference *nextref;
+	const struct got_error *err;
+
+	err = got_ref_open(&nextref, repo, ref->ref.symref.ref);
+	if (err)
+		return err;
+
+	if (nextref->flags & GOT_REF_IS_SYMBOLIC)
+		err = resolve_symbolic_ref(resolved, repo, nextref);
+	else
+		*resolved = got_ref_dup(nextref);
+
+	got_ref_close(nextref);
+	return err;
+}
+
+const struct got_error *
+got_ref_resolve(struct got_object_id **id, struct got_repository *repo,
+    struct got_reference *ref)
+{
+	const struct got_error *err;
+
+	if (ref->flags & GOT_REF_IS_SYMBOLIC) {
+		struct got_reference *resolved = NULL;
+		err = resolve_symbolic_ref(&resolved, repo, ref);
+		if (err == NULL)
+			err = got_ref_resolve(id, repo, resolved);
+		free(resolved);
+		return err;
+	}
+
+	*id = calloc(1, sizeof(**id));
+	if (*id == NULL)
+		return got_error(GOT_ERR_NO_MEM);
+	memcpy((*id)->sha1, ref->ref.ref.sha1, SHA1_DIGEST_LENGTH);
+	return NULL;
+}
diff --git a/lib/repository.c b/lib/repository.c
index 6d7b5ec..9e794a4 100644
--- a/lib/repository.c
+++ b/lib/repository.c
@@ -37,8 +37,8 @@
 #define GOT_FETCH_HEAD_FILE	"FETCH_HEAD"
 #define GOT_ORIG_HEAD_FILE	"ORIG_HEAD"
 
-static char *
-get_path_git_dir(struct got_repository *repo)
+char *
+got_repo_get_path_git_dir(struct got_repository *repo)
 {
 	char *path_git;
 	
@@ -60,14 +60,14 @@ get_path_git_child(struct got_repository *repo, const char *basename)
 	return path_child;
 }
 
-static char *
-get_path_objects(struct got_repository *repo)
+char *
+got_repo_get_path_objects(struct got_repository *repo)
 {
 	return get_path_git_child(repo, GOT_OBJECTS_DIR);
 }
 
-static char *
-get_path_refs(struct got_repository *repo)
+char *
+got_repo_get_path_refs(struct got_repository *repo)
 {
 	return get_path_git_child(repo, GOT_REFS_DIR);
 }
@@ -81,9 +81,9 @@ get_path_head(struct got_repository *repo)
 static int
 is_git_repo(struct got_repository *repo)
 {
-	char *path_git = get_path_git_dir(repo);
-	char *path_objects = get_path_objects(repo);
-	char *path_refs = get_path_refs(repo);
+	char *path_git = got_repo_get_path_git_dir(repo);
+	char *path_objects = got_repo_get_path_objects(repo);
+	char *path_refs = got_repo_get_path_refs(repo);
 	char *path_head = get_path_head(repo);
 	int ret;
 
@@ -145,24 +145,3 @@ got_repo_get_path(struct got_repository *repo)
 {
 	return repo->path;
 }
-
-const struct got_error *
-got_repo_get_reference(struct got_reference **ref,
-    struct got_repository *repo, const char *refname)
-{
-	const struct got_error *err = NULL;
-	char *path_refs;
-
-	/* Some refs live in the .git directory. */
-	if (strcmp(refname, GOT_REF_HEAD) == 0 ||
-	    strcmp(refname, GOT_REF_ORIG_HEAD) == 0 ||
-	    strcmp(refname, GOT_REF_MERGE_HEAD) == 0 ||
-	    strcmp(refname, GOT_REF_FETCH_HEAD) == 0)
-		path_refs = get_path_git_dir(repo);
-	else
-		path_refs = get_path_refs(repo);
-
-	err = got_ref_open(ref, path_refs, refname);
-	free(path_refs);
-	return err;
-}
diff --git a/regress/repository/repository_test.c b/regress/repository/repository_test.c
index f70ab8c..1973fa4 100644
--- a/regress/repository/repository_test.c
+++ b/regress/repository/repository_test.c
@@ -19,6 +19,7 @@
 #include <sha1.h>
 
 #include "got_error.h"
+#include "got_object.h"
 #include "got_refs.h"
 #include "got_repository.h"
 
@@ -52,7 +53,7 @@ repo_get_head_ref(const char *repo_path)
 	err = got_repo_open(&repo, repo_path);
 	if (err != NULL || repo == NULL)
 		return 0;
-	err = got_repo_get_reference(&head_ref, repo, GOT_REF_HEAD);
+	err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
 	if (err != NULL || head_ref == NULL)
 		return 0;
 	got_ref_close(head_ref);
@@ -60,6 +61,30 @@ repo_get_head_ref(const char *repo_path)
 	return 1;
 }
 
+static int
+repo_resolve_head_ref(const char *repo_path)
+{
+	const struct got_error *err;
+	struct got_repository *repo;
+	struct got_reference *head_ref;
+	struct got_object_id *id;
+	int ret;
+
+	err = got_repo_open(&repo, repo_path);
+	if (err != NULL || repo == NULL)
+		return 0;
+	err = got_ref_open(&head_ref, repo, GOT_REF_HEAD);
+	if (err != NULL || head_ref == NULL)
+		return 0;
+	err = got_ref_resolve(&id, repo, head_ref);
+	if (err != NULL || head_ref == NULL)
+		return 0;
+	free(id);
+	got_ref_close(head_ref);
+	got_repo_close(repo);
+	return 1;
+}
+
 int
 main(int argc, const char *argv[])
 {
@@ -77,6 +102,7 @@ main(int argc, const char *argv[])
 
 	RUN_TEST(repo_open_test(repo_path), "repo_open");
 	RUN_TEST(repo_get_head_ref(repo_path), "get_head_ref");
+	RUN_TEST(repo_resolve_head_ref(repo_path), "resolve_head_ref");
 
 	return failure ? 1 : 0;
 }