@@ -1109,6 +1109,7 @@ LIB_OBJS += preload-index.o
LIB_OBJS += pretty.o
LIB_OBJS += prio-queue.o
LIB_OBJS += progress.o
+LIB_OBJS += promise.o
LIB_OBJS += promisor-remote.o
LIB_OBJS += prompt.o
LIB_OBJS += protocol.o
new file mode 100644
@@ -0,0 +1,89 @@
+/*
+ * Generic implementation of callbacks with await checking.
+ */
+#include "promise.h"
+
+void promise_assert_finished(struct promise_t *p) {
+ if (p->state == PROMISE_UNRESOLVED) {
+ BUG("expected promise to have been resolved/rejected");
+ }
+}
+
+void promise_assert_failure(struct promise_t *p) {
+ if (p->state != PROMISE_FAILURE) {
+ BUG("expected promise to have been rejected");
+ }
+}
+
+void promise_resolve(struct promise_t *p, int status) {
+ if (p->state != PROMISE_UNRESOLVED) {
+ BUG("promise was already resolved/rejected");
+ return;
+ }
+ p->result.success_result = status;
+ p->state = PROMISE_SUCCESS;
+}
+
+void promise_reject(struct promise_t *p, int status, const char* fmt, ...) {
+ va_list args;
+ if (p->state != PROMISE_UNRESOLVED) {
+ BUG("promise was already resolved/rejected");
+ return;
+ }
+ p->result.failure_result.status = status;
+
+ strbuf_init(&p->result.failure_result.message, 0);
+
+ va_start(args, fmt);
+ strbuf_vaddf(&p->result.failure_result.message, fmt, args);
+ va_end(args);
+
+ p->state = PROMISE_FAILURE;
+}
+
+struct promise_t *promise_init(void) {
+ // Promises are allocated on the heap, because they represent potentially long-running tasks,
+ // and a stack-allocated value might not live long enough.
+ struct promise_t *new_promise = xmalloc(sizeof(struct promise_t));
+ struct failure_result_t failure_result;
+
+ new_promise->state = PROMISE_UNRESOLVED;
+ failure_result.status = 0;
+ new_promise->result.failure_result = failure_result;
+
+ return new_promise;
+}
+
+/**
+ * Outputs an error message and size from a failed promise. The error message must be
+ * free()'ed by the caller. Calling this function is not allowed if the promise is not
+ * failed.
+ *
+ * Argument `size` may be omitted by passing in NULL.
+ *
+ * Note that although *error_message is null-terminated, its size may be larger
+ * than the terminated string, and its actual size is indicated by *size.
+ */
+void promise_copy_error(struct promise_t *p, char **error_message, size_t *size) {
+ size_t local_size;
+ promise_assert_failure(p);
+
+ *error_message = strbuf_detach(&p->result.failure_result.message, &local_size);
+ if (size) {
+ *size = local_size;
+ }
+
+ // We are only doing a copy, not a consume, so we need to put the error message back
+ // the way we found it.
+ strbuf_add(&p->result.failure_result.message, *error_message, strlen(*error_message));
+}
+
+/**
+ * Fully deallocates the promise as well as the error message, if any.
+ */
+void promise_release(struct promise_t *p) {
+ if (p->state == PROMISE_FAILURE) {
+ strbuf_release(&p->result.failure_result.message);
+ }
+ free(p);
+}
new file mode 100644
@@ -0,0 +1,71 @@
+#ifndef PROMISE_H
+#define PROMISE_H
+
+#include "git-compat-util.h"
+#include "strbuf.h"
+
+enum promise_state {
+ PROMISE_UNRESOLVED = 0,
+ PROMISE_SUCCESS = 1,
+ PROMISE_FAILURE = 2,
+};
+
+typedef int success_result_t;
+
+struct failure_result_t {
+ int status;
+ struct strbuf message;
+};
+
+struct promise_t {
+ enum promise_state state;
+ union {
+ success_result_t success_result;
+ struct failure_result_t failure_result;
+ } result;
+};
+
+// Function to assert that a promise has been resolved
+void promise_assert_finished(struct promise_t *p);
+
+// Function to assert that a promise has been rejected
+void promise_assert_failure(struct promise_t *p);
+
+// Function to resolve a promise with a success result
+void promise_resolve(struct promise_t *p, int status);
+
+// Function to reject a promise with a failure result and an optional formatted error message
+void promise_reject(struct promise_t *p, int status, const char* fmt, ...);
+
+// Function to create a new promise
+struct promise_t *promise_init(void);
+
+// Copies the error out of a failed promise
+void promise_copy_error(struct promise_t *promise, char **error_message, size_t *size);
+
+// Fully deallocates the promise
+void promise_release(struct promise_t *promise);
+
+#define PROMISE_SUCCEED(p, errcode) do { \
+ promise_resolve(p, errcode); \
+ return; \
+} while (0)
+
+#define PROMISE_THROW(p, errcode, ...) do { \
+ promise_reject(p, errcode, __VA_ARGS__); \
+ return; \
+} while (0)
+
+#define PROMISE_BUBBLE_UP(dst, src, ...) do { \
+ if (strlen(src->result.failure_result.message.buf) != 0) { \
+ strbuf_insertf(&src->result.failure_result.message, 0, "\n\t"); \
+ strbuf_insertf(&src->result.failure_result.message, 0, _("caused by:")); \
+ strbuf_insertf(&src->result.failure_result.message, 0, "\n"); \
+ strbuf_insertf(&src->result.failure_result.message, 0, __VA_ARGS__); \
+ } \
+ promise_reject(dst, src->result.failure_result.status, "%s", src->result.failure_result.message.buf); \
+ promise_release(src); \
+ return; \
+} while (0)
+
+#endif