Commit cf7dd05bdab243e1150f771359be6c2a766a0a3a

Patrick Steinhardt 2020-06-30T13:26:05

refdb: return resolved symbolic refs pointing to nonexistent refs In some cases, resolving references requires us to also know about the final symbolic reference that's pointing to a nonexistent branch, e.g. in an empty repository where the main branch is yet unborn but HEAD already points to it. Right now, the resolving logic is thus split up into two, where one is the new refdb implementation and the second one is an ad-hoc implementation inside "refs.c". Let's extend `git_refdb_resolve` to also return such final dangling references pointing to nonexistent branches so we can deduplicate the resolving logic.

diff --git a/src/refdb.c b/src/refdb.c
index 12dfaf1..6879b6a 100644
--- a/src/refdb.c
+++ b/src/refdb.c
@@ -161,8 +161,16 @@ int git_refdb_resolve(
 
 		if (ref->type == GIT_REFERENCE_DIRECT)
 			break;
-		if ((error = git_refdb_lookup(&resolved, db, git_reference_symbolic_target(ref))) < 0)
+
+		if ((error = git_refdb_lookup(&resolved, db, git_reference_symbolic_target(ref))) < 0) {
+			/* If we found a symbolic reference with a nonexistent target, return it. */
+			if (error == GIT_ENOTFOUND) {
+				error = 0;
+				*out = ref;
+				ref = NULL;
+			}
 			goto out;
+		}
 
 		git_reference_free(ref);
 		ref = resolved;
diff --git a/src/refdb.h b/src/refdb.h
index aeddaf7..84e19b1 100644
--- a/src/refdb.h
+++ b/src/refdb.h
@@ -30,6 +30,25 @@ int git_refdb_lookup(
 	git_refdb *refdb,
 	const char *ref_name);
 
+/**
+ * Resolve the reference by following symbolic references.
+ *
+ * Given a reference name, this function will follow any symbolic references up
+ * to `max_nesting` deep and return the resolved direct reference. If any of
+ * the intermediate symbolic references points to a non-existing reference,
+ * then that symbolic reference is returned instead with an error code of `0`.
+ * If the given reference is a direct reference already, it is returned
+ * directly.
+ *
+ * If `max_nesting` is `0`, the reference will not be resolved. If it's
+ * negative, it will be set to the default resolve depth which is `5`.
+ *
+ * @param out Pointer to store the result in.
+ * @param db The refdb to use for resolving the reference.
+ * @param ref_name The reference name to lookup and resolve.
+ * @param max_nesting The maximum nesting depth.
+ * @return `0` on success, a negative error code otherwise.
+ */
 int git_refdb_resolve(
 	git_reference **out,
 	git_refdb *db,
diff --git a/src/refs.c b/src/refs.c
index e7105c7..ddbfd7e 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -225,6 +225,18 @@ int git_reference_lookup_resolved(
 	    (error = git_refdb_resolve(ref_out, refdb, normalized, max_nesting)) < 0)
 		return error;
 
+	/*
+	 * The resolved reference may be a symbolic reference in case its
+	 * target doesn't exist. If the user asked us to resolve (e.g.
+	 * `max_nesting != 0`), then we need to return an error in case we got
+	 * a symbolic reference back.
+	 */
+	if (max_nesting && git_reference_type(*ref_out) == GIT_REFERENCE_SYMBOLIC) {
+		git_reference_free(*ref_out);
+		*ref_out = NULL;
+		return GIT_ENOTFOUND;
+	}
+
 	return 0;
 }