diff --git a/Makefile b/Makefile
index 7344a7f725..5d9bf992e9 100644
--- a/Makefile
+++ b/Makefile
@@ -1013,6 +1013,7 @@ LIB_OBJS += dir.o
 LIB_OBJS += editor.o
 LIB_OBJS += entry.o
 LIB_OBJS += environment.o
+LIB_OBJS += error.o
 LIB_OBJS += ewah/bitmap.o
 LIB_OBJS += ewah/ewah_bitmap.o
 LIB_OBJS += ewah/ewah_io.o
diff --git a/error.c b/error.c
new file mode 100644
index 0000000000..713bc42187
--- /dev/null
+++ b/error.c
@@ -0,0 +1,43 @@
+#include "git-compat-util.h"
+#include "gettext.h"
+#include "error.h"
+#include "hex.h"
+#include "strbuf.h"
+
+const char *git_error_string(struct git_error err)
+{
+	struct strbuf buf = STRBUF_INIT;
+	if (!git_error_strbuf(&buf, err))
+		return NULL;
+	return strbuf_detach(&buf, NULL);
+}
+
+const char *git_error_strbuf(struct strbuf *buf, struct git_error err)
+{
+	if (GIT_ERROR_SUCCESS(err)) {
+		return NULL;
+	} else if (GIT_ERROR_ERRNO(err) != -1) {
+		return xstrdup(strerror(GIT_ERROR_ERRNO(err)));
+	} else {
+		struct git_error_multiple *me = err.meta;
+		switch (GIT_ERROR_GITERR(err)) {
+		case GIT_ERR_OBJECT_NOT_FOUND:
+			if (err.meta)
+				strbuf_addf(buf, _("object not found: %s"), oid_to_hex(err.meta));
+			else
+				strbuf_addf(buf, _("object not found"));
+		case GIT_ERR_NULL_OID:
+			if (err.meta)
+				strbuf_addf(buf, _("null object ID not allowed in this context: %s"), (char *)err.meta);
+			else
+				strbuf_addf(buf, _("null object ID not allowed"));
+		case GIT_ERR_MULTIPLE:
+			strbuf_addf(buf, _("multiple errors:\n"));
+			for (size_t i = 0; i < me->count; i++) {
+				git_error_strbuf(buf, me->errs[i]);
+				strbuf_addstr(buf, "\n");
+			}
+		}
+		return buf->buf;
+	}
+}
diff --git a/error.h b/error.h
new file mode 100644
index 0000000000..485cca99e0
--- /dev/null
+++ b/error.h
@@ -0,0 +1,168 @@
+#ifndef ERROR_H
+#define ERROR_H
+
+#include "git-compat-util.h"
+
+/* Set if this value is initialized. */
+#define GIT_ERROR_BIT_INIT (1ULL << 63)
+/* Set if the code is an errno code, clear if it's a git error code. */
+#define GIT_ERROR_BIT_ERRNO (1ULL << 62)
+/*
+ * Set if the memory in meta should be freed; otherwise, it's statically
+ * allocated.
+ */
+#define GIT_ERROR_BIT_ALLOC (1ULL << 61)
+/*
+ * Set if the memory in meta is a C string; otherwise, it's a metadata struct.
+ */
+#define GIT_ERROR_BIT_MSG (1ULL << 60)
+
+#define GIT_ERROR_BIT_MASK (0xf << 60)
+
+#define GIT_ERROR_OK (git_error_ok())
+
+#define GIT_ERROR_SUCCESS(e) (((e).code == GIT_ERROR_BIT_INIT))
+#define GIT_ERROR_SUCCESS_CONSUME(e) ((git_error_free(&(e)).code == GIT_ERROR_BIT_INIT)
+
+#define GIT_ERROR_ERRNO(e) (((e).code & GIT_ERROR_BIT_ERRNO) ? ((e).code & 0xffffffff) : -1)
+#define GIT_ERROR_GITERR(e) (((e).code & GIT_ERROR_BIT_ERRNO) ? -1 : ((e).code & 0xffffffff))
+
+/*
+ * A value representing an error in Git.
+ */
+struct git_error {
+	uint64_t code;
+	void *meta;
+};
+
+struct git_error_multiple {
+	struct git_error *errs;
+	size_t count;
+};
+
+enum git_error_code {
+	/* The operation was a success. */
+	GIT_ERR_SUCCESS = 0,
+	/* An object ID was provided, but it was not found.
+	 *
+	 * meta will be NULL or a pointer to struct object ID.
+	 */
+	GIT_ERR_OBJECT_NOT_FOUND = 1,
+	/*
+	 * An object ID was provided, but it is all zeros and that is not
+	 * allowed.
+	 *
+	 * meta will be NULL or a message explaining the context.
+	 */
+	GIT_ERR_NULL_OID = 2,
+	/*
+	 * Multiple errors occurred.
+	 *
+	 * meta must be a pointer to struct git_error_multiple.
+	 */
+	GIT_ERR_MULTIPLE = 3,
+};
+
+const char *git_error_string(struct git_error err);
+const char *git_error_strbuf(struct strbuf *buf, struct git_error err);
+
+/*
+ * A successful error status.
+ */
+static inline struct git_error git_error_ok(void) {
+	struct git_error e =  {
+		.code = 0 | GIT_ERROR_BIT_INIT,
+		.meta = NULL,
+	};
+	return e;
+}
+
+static inline struct git_error git_error_new_errno(uint32_t errnoval, const char *msg, int to_free) {
+	struct git_error e =  {
+		.code = errnoval | GIT_ERROR_BIT_INIT | GIT_ERROR_BIT_ERRNO |
+			GIT_ERROR_BIT_MSG | (to_free ? GIT_ERROR_BIT_ALLOC : 0),
+		.meta = (void *)msg,
+	};
+	return e;
+}
+
+static inline struct git_error git_error_new_git(uint32_t gitcode, const char *msg, int to_free) {
+	struct git_error e =  {
+		.code = gitcode | GIT_ERROR_BIT_INIT |
+			GIT_ERROR_BIT_MSG | (to_free ? GIT_ERROR_BIT_ALLOC : 0),
+		.meta = (void *)msg,
+	};
+	return e;
+}
+
+static inline struct git_error git_error_new_simple(uint32_t gitcode) {
+	struct git_error e =  {
+		.code = gitcode | GIT_ERROR_BIT_INIT,
+		.meta = NULL,
+	};
+	return e;
+}
+
+static inline struct git_error git_error_new_multiple(struct git_error *errs, size_t count)
+{
+	struct git_error_multiple *me = xmalloc(sizeof(*me));
+	struct git_error e =  {
+		.code = GIT_ERR_MULTIPLE | GIT_ERROR_BIT_INIT | GIT_ERROR_BIT_ALLOC,
+		.meta = me,
+	};
+	me->errs = errs;
+	me->count = count;
+	return e;
+}
+
+/*
+ * If this is a git error and the code matches the given code, or if this is a
+ * multiple error and any of the contained errors are a git error whose code
+ * matches, returns a pointer to that error.  If there is no match, returns
+ * NULL.
+ */
+static inline struct git_error *git_error_is_git(struct git_error *e, int code) {
+	int64_t giterr = GIT_ERROR_GITERR(*e);
+	if (giterr == code) {
+		return e;
+	} else if (giterr == GIT_ERR_MULTIPLE) {
+		struct git_error_multiple *me = e->meta;
+		for (size_t i = 0; i < me->count; i++)
+			return git_error_is_git(me->errs + i, code);
+		return NULL;
+	} else {
+		return NULL;
+	}
+}
+
+/*
+ * If this is an errno error and the code matches the given code, or if this is
+ * a multiple error and any of the contained errors are an errno error whose
+ * code matches, returns a pointer to that error.  Otherwise, returns NULL.
+ */
+static inline struct git_error *git_error_is_errno(struct git_error *e, int code) {
+	int64_t giterr = GIT_ERROR_GITERR(*e);
+	if (GIT_ERROR_ERRNO(*e) == code) {
+		return e;
+	} else if (giterr == GIT_ERR_MULTIPLE) {
+		struct git_error_multiple *me = e->meta;
+		for (size_t i = 0; i < me->count; i++)
+			return git_error_is_errno(me->errs + i, code);
+		return NULL;
+	} else {
+		return NULL;
+	}
+}
+
+/* Frees and deinitializes this error structure. */
+static inline uint64_t git_error_free(struct git_error *e)
+{
+	uint64_t code = e->code;
+	if (e->code & GIT_ERROR_BIT_ALLOC)
+		free(e->meta);
+	e->code = 0;
+	e->meta = NULL;
+	return code;
+}
+
+#endif
