Commit 1f39593b141005b0c8035852d891fa01ead7bd78

Patrick Steinhardt 2020-06-30T08:53:59

refdb: extract function to check whether to append HEAD to the reflog The logic to determine whether a reflog entry should be for the HEAD reference is non-trivial. Currently, the only user of this is the filesystem-based refdb, but with the advent of the reftable refdb we're going to add a second user that's interested in having the same behaviour. Let's pull out a new function that checks whether a given reference should cause a entry to be written to the HEAD reflog as a preparatory step.

diff --git a/src/refdb.c b/src/refdb.c
index 19b9887..764d2c1 100644
--- a/src/refdb.c
+++ b/src/refdb.c
@@ -268,6 +268,55 @@ int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *
 	return 0;
 }
 
+int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref)
+{
+	git_reference *head = NULL, *peeled = NULL;
+	const char *name;
+	int error;
+
+	*out = 0;
+
+	if (ref->type == GIT_REFERENCE_SYMBOLIC) {
+		error = 0;
+		goto out;
+	}
+
+	if ((error = git_refdb_lookup(&head, db, GIT_HEAD_FILE)) < 0)
+		goto out;
+
+	if (git_reference_type(head) == GIT_REFERENCE_DIRECT)
+		goto out;
+
+	/* Go down the symref chain until we find the branch */
+	while (git_reference_type(head) == GIT_REFERENCE_SYMBOLIC) {
+		if ((error = git_refdb_lookup(&peeled, db, git_reference_symbolic_target(head))) < 0)
+			break;
+
+		git_reference_free(head);
+		head = peeled;
+		peeled = NULL;
+	}
+
+	if (error < 0) {
+		if (error != GIT_ENOTFOUND)
+			goto out;
+		error = 0;
+		name = git_reference_symbolic_target(head);
+	} else {
+		name = git_reference_name(head);
+	}
+
+	if (strcmp(name, ref->name))
+		goto out;
+
+	*out = 1;
+
+out:
+	git_reference_free(peeled);
+	git_reference_free(head);
+	return error;
+}
+
 int git_refdb_has_log(git_refdb *db, const char *refname)
 {
 	assert(db && refname);
diff --git a/src/refdb.h b/src/refdb.h
index 7578f66..05c0e1c 100644
--- a/src/refdb.h
+++ b/src/refdb.h
@@ -78,6 +78,22 @@ int git_refdb_reflog_write(git_reflog *reflog);
  */
 int git_refdb_should_write_reflog(int *out, git_refdb *db, const git_reference *ref);
 
+/**
+ * Determine whether a reflog entry should be created for HEAD if creating one
+ * for the given reference
+ *
+ * In case the given reference is being pointed to by HEAD, then creating a
+ * reflog entry for this reference also requires us to create a corresponding
+ * reflog entry for HEAD. This function can be used to determine that scenario.
+ *
+ * @param out pointer to which the result will be written, `1` means a reflog
+ *            entry should be written, `0` means none should be written.
+ * @param db The refdb to decide this for.
+ * @param ref The reference one wants to check.
+ * @return `0` on success, a negative error code otherwise.
+ */
+int git_refdb_should_write_head_reflog(int *out, git_refdb *db, const git_reference *ref);
+
 int git_refdb_has_log(git_refdb *db, const char *refname);
 int git_refdb_ensure_log(git_refdb *refdb, const char *refname);
 
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index eeddac3..7e04819 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -1181,54 +1181,28 @@ out:
  */
 static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message)
 {
-	int error;
+	git_reference *head = NULL;
+	git_refdb *refdb = NULL;
+	int error, write_reflog;
 	git_oid old_id;
-	git_reference *tmp = NULL, *head = NULL, *peeled = NULL;
-	const char *name;
 
-	if (ref->type == GIT_REFERENCE_SYMBOLIC)
-		return 0;
+	if ((error = git_repository_refdb(&refdb, backend->repo)) < 0 ||
+	    (error = git_refdb_should_write_head_reflog(&write_reflog, refdb, ref)) < 0)
+		goto out;
+	if (!write_reflog)
+		goto out;
 
 	/* if we can't resolve, we use {0}*40 as old id */
 	if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0)
 		memset(&old_id, 0, sizeof(old_id));
 
-	if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0)
-		return error;
-
-	if (git_reference_type(head) == GIT_REFERENCE_DIRECT)
-		goto cleanup;
-
-	if ((error = git_reference_lookup(&tmp, backend->repo, GIT_HEAD_FILE)) < 0)
-		goto cleanup;
-
-	/* Go down the symref chain until we find the branch */
-	while (git_reference_type(tmp) == GIT_REFERENCE_SYMBOLIC) {
-		error = git_reference_lookup(&peeled, backend->repo, git_reference_symbolic_target(tmp));
-		if (error < 0)
-			break;
-
-		git_reference_free(tmp);
-		tmp = peeled;
-	}
-
-	if (error == GIT_ENOTFOUND) {
-		error = 0;
-		name = git_reference_symbolic_target(tmp);
-	} else if (error < 0) {
-		goto cleanup;
-	} else {
-		name = git_reference_name(tmp);
-	}
-
-	if (strcmp(name, ref->name))
-		goto cleanup;
-
-	error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message);
+	if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0 ||
+	    (error = reflog_append(backend, head, &old_id, git_reference_target(ref), who, message)) < 0)
+		goto out;
 
-cleanup:
-	git_reference_free(tmp);
+out:
 	git_reference_free(head);
+	git_refdb_free(refdb);
 	return error;
 }