Merge branch 'development' into error-handling
The code in this branch has been modified so it works with the global
state introduced in development.
diff --git a/include/git2/errors.h b/include/git2/errors.h
index 5ac0d5b..db71fc2 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -8,6 +8,7 @@
#define INCLUDE_git_errors_h__
#include "common.h"
+#include "types.h"
/**
* @file git2/errors.h
@@ -113,7 +114,7 @@
/** The buffer is too short to satisfy the request */
GIT_ESHORTBUFFER = -32,
-} git_error;
+} git_error_code;
/**
* Return a detailed error string with the latest error
@@ -139,6 +140,16 @@
*/
GIT_EXTERN(void) git_clearerror(void);
+GIT_EXTERN(void) git_error_free(git_error *err);
+
+/**
+ * Print a stack trace to stderr
+ *
+ * A bog standard stack trace. You can use it if you don't want to do
+ * anything more complex in your UI.
+ */
+GIT_EXTERN(void) git_error_print_stack(git_error *error_in);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/types.h b/include/git2/types.h
index 1df1897..03b5fcf 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -131,6 +131,15 @@
/** Representation of a reference log */
typedef struct git_reflog git_reflog;
+/** Represensation of a git_error */
+typedef struct git_error {
+ int code;
+ char *msg;
+ struct git_error *child;
+ const char *file;
+ unsigned int line;
+} git_error;
+
/** Time in a signature */
typedef struct git_time {
git_time_t time; /** time in seconds from epoch */
diff --git a/src/common.h b/src/common.h
index 727a08e..f4dcc1c 100644
--- a/src/common.h
+++ b/src/common.h
@@ -50,14 +50,7 @@
#include "thread-utils.h"
#include "bswap.h"
-extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2);
-#define git__throw(error, ...) \
- (git___throw(__VA_ARGS__), error)
-
-extern void git___rethrow(const char *, ...) GIT_FORMAT_PRINTF(1, 2);
-#define git__rethrow(error, ...) \
- (git___rethrow(__VA_ARGS__), error)
-
+#include "errors.h"
#include "util.h"
#endif /* INCLUDE_common_h__ */
diff --git a/src/errors.c b/src/errors.c
index 81770e7..22b8ae7 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -4,7 +4,10 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
+
#include "common.h"
+#include "errors.h"
+#include "posix.h"
#include "global.h"
#include <stdarg.h>
@@ -55,50 +58,109 @@
return "Unknown error";
}
-#define ERROR_MAX_LEN 1024
+static git_error git_error_OOM = {
+ GIT_ENOMEM,
+ "out of memory",
+ NULL,
+ NULL,
+ -1
+};
-void git___rethrow(const char *msg, ...)
+git_error * git_error_oom(void)
{
- char new_error[ERROR_MAX_LEN];
- char *last_error;
- char *old_error = NULL;
-
- va_list va;
-
- last_error = GIT_GLOBAL->error.last;
-
- va_start(va, msg);
- vsnprintf(new_error, ERROR_MAX_LEN, msg, va);
- va_end(va);
-
- old_error = git__strdup(last_error);
-
- snprintf(last_error, ERROR_MAX_LEN, "%s \n - %s", new_error, old_error);
-
- git__free(old_error);
+ /*
+ * Throw an out-of-memory error:
+ * what we return is actually a static pointer, because on
+ * oom situations we cannot afford to allocate a new error
+ * object.
+ *
+ * The `git_error_free` function will take care of not
+ * freeing this special type of error.
+ *
+ */
+ return &git_error_OOM;
}
-void git___throw(const char *msg, ...)
+git_error * git_error_createf(const char *file, unsigned int line, int code,
+ const char *fmt, ...)
{
- va_list va;
+ git_error *err;
+ va_list ap;
+ size_t size;
- va_start(va, msg);
- vsnprintf(GIT_GLOBAL->error.last, ERROR_MAX_LEN, msg, va);
- va_end(va);
+ err = git__malloc(sizeof(git_error));
+ if (err == NULL)
+ return git_error_oom();
+
+ memset(err, 0x0, sizeof(git_error));
+
+ va_start(ap, fmt);
+ size = p_vsnprintf(err->msg, 0, fmt, ap);
+ va_end(ap);
+
+ size++;
+
+ err->msg = git__malloc(size);
+ if (err->msg == NULL) {
+ free(err);
+ return git_error_oom();
+ }
+
+ va_start(ap, fmt);
+ size = p_vsnprintf(err->msg, size, fmt, ap);
+ va_end(ap);
+
+ err->code = code;
+ err->child = GIT_GLOBAL->git_errno;
+ err->file = file;
+ err->line = line;
+
+ GIT_GLOBAL->git_errno = err;
+
+ return err;
}
-const char *git_lasterror(void)
+git_error * git_error__quick_wrap(const char *file, int line,
+ git_error_code error, const char *msg)
{
- char *last_error = GIT_GLOBAL->error.last;
+ if (error == GIT_SUCCESS)
+ return GIT_SUCCESS;
- if (!last_error[0])
- return NULL;
+ return git_error_createf(file, line, error, "%s", msg);
+}
- return last_error;
+void git_error_free(git_error *err)
+{
+ if (err == NULL)
+ return;
+
+ if (err->child)
+ git_error_free(err->child);
+
+ if (err->msg)
+ free(err->msg);
+
+ free(err);
}
void git_clearerror(void)
{
- char *last_error = GIT_GLOBAL->error.last;
- last_error[0] = '\0';
+ git_error_free(GIT_GLOBAL->git_errno);
+ GIT_GLOBAL->git_errno = NULL;
+}
+
+const char *git_lasterror(void)
+{
+ return GIT_GLOBAL->git_errno == NULL ? NULL : GIT_GLOBAL->git_errno->msg;
+}
+
+void git_error_print_stack(git_error *error_in)
+{
+ git_error *error;
+
+ if (error_in == NULL)
+ error_in = GIT_GLOBAL->git_errno;
+
+ for (error = error_in; error; error = error->child)
+ fprintf(stderr, "%s:%u %s\n", error->file, error->line, error->msg);
}
diff --git a/src/errors.h b/src/errors.h
new file mode 100644
index 0000000..525cb0f
--- /dev/null
+++ b/src/errors.h
@@ -0,0 +1,48 @@
+#ifndef INCLUDE_errors_h__
+#define INCLUDE_errors_h__
+
+#include "git2/common.h"
+
+/* Deprecated - please use the more advanced functions below. */
+#define git__throw(error, ...) \
+ (git_error_createf(__FILE__, __LINE__, error, __VA_ARGS__), error)
+
+#define git__rethrow(error, ...) \
+ (git_error_createf(__FILE__, __LINE__, error, __VA_ARGS__), error)
+
+/*
+ * This implementation is loosely based on subversion's error
+ * handling.
+ */
+
+git_error * git_error_createf(const char *file, unsigned int line, int code,
+ const char *msg, ...) GIT_FORMAT_PRINTF(4, 5);
+
+git_error * git_error__quick_wrap(const char *file, int line,
+ git_error_code error, const char *msg);
+
+/*
+ * Wrap an error with a message. All git_error values are assigned with
+ * child's fields.
+ */
+#define git_error_quick_wrap(error, message) \
+ git_error__quick_wrap(__FILE__, __LINE__, error, message)
+
+/*
+ * Use this function to wrap functions like
+ *
+ * git_error * foo(void)
+ * {
+ * return git_error_trace(bar());
+ * }
+ *
+ * Otherwise the call of foo() wouldn't be visible in the trace.
+ *
+ */
+#define git_error_trace(error) \
+ git_error_quick_wrap(error, "traced error");
+
+/* Throw an out-of-memory error */
+extern git_error * git_error_oom(void);
+
+#endif /* INCLUDE_errors_h__ */
diff --git a/src/global.h b/src/global.h
index 641f47c..6a15a8d 100644
--- a/src/global.h
+++ b/src/global.h
@@ -8,12 +8,10 @@
#define INCLUDE_global_h__
#include "mwindow.h"
+#include "git2/types.h"
typedef struct {
- struct {
- char last[1024];
- } error;
-
+ git_error *git_errno;
git_mwindow_ctl mem_ctl;
} git_global_st;
diff --git a/src/remote.c b/src/remote.c
index 3ff08a2..51e77e5 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -14,6 +14,7 @@
#include "remote.h"
#include "fetch.h"
#include "refs.h"
+#include "error.h"
static int refspec_parse(git_refspec *refspec, const char *str)
{
diff --git a/tests-clay/object/tree/frompath.c b/tests-clay/object/tree/frompath.c
index 1effcb1..651a86d 100644
--- a/tests-clay/object/tree/frompath.c
+++ b/tests-clay/object/tree/frompath.c
@@ -23,9 +23,10 @@
{
git_tree_close(tree);
git_repository_free(repo);
+ git_clearerror();
}
-static void assert_tree_from_path(git_tree *root, const char *path, git_error expected_result, const char *expected_raw_oid)
+static void assert_tree_from_path(git_tree *root, const char *path, int expected_result, const char *expected_raw_oid)
{
git_tree *containing_tree = NULL;
diff --git a/tests-clay/status/worktree.c b/tests-clay/status/worktree.c
index 1e8a5dd..7d120ec 100644
--- a/tests-clay/status/worktree.c
+++ b/tests-clay/status/worktree.c
@@ -92,6 +92,7 @@
_repository = NULL;
cl_fixture_cleanup("status");
+ git_clearerror();
}
/**
diff --git a/tests/t18-status.c b/tests/t18-status.c
index 73e328c..3b75472 100644
--- a/tests/t18-status.c
+++ b/tests/t18-status.c
@@ -430,6 +430,8 @@
git_repository_free(repo);
git_futils_rmdir_r(TEMP_REPO_FOLDER, 1);
+
+ git_clearerror();
END_TEST
BEGIN_SUITE(status)
diff --git a/tests/test_lib.c b/tests/test_lib.c
index a4c39df..9d3cba1 100755
--- a/tests/test_lib.c
+++ b/tests/test_lib.c
@@ -10,12 +10,15 @@
#define DO_ALLOC(TYPE) ((TYPE*) malloc(sizeof(TYPE)))
#define GIT_MAX_TEST_CASES 64
+extern git_error *git_errno;
+
struct git_test {
char *name;
char *message;
char *failed_pos;
char *description;
char *error_message;
+ git_error *error_stack;
git_testfunc function;
unsigned failed:1, ran:1;
@@ -36,6 +39,7 @@
free(t->failed_pos);
free(t->message);
free(t->error_message);
+ git_error_free(t->error_stack);
free(t);
}
}
@@ -84,6 +88,8 @@
tc->failed = 1;
tc->message = strdup(message);
tc->failed_pos = strdup(buf);
+ tc->error_stack = GIT_GLOBAL->git_errno;
+ GIT_GLOBAL->git_errno = NULL;
if (last_error)
tc->error_message = strdup(last_error);
@@ -146,6 +152,14 @@
ts->list[ts->count++] = create_test(test);
}
+static void print_trace(git_error *error)
+{
+ git_error *err;
+
+ for (err = error; err; err = err->child)
+ printf("\t%s:%u %s\n", err->file, err->line, err->msg);
+}
+
static void print_details(git_testsuite *ts)
{
int i;
@@ -165,6 +179,8 @@
failCount, tc->description, tc->name, tc->failed_pos, tc->message);
if (tc->error_message)
printf("\tError: %s\n", tc->error_message);
+ fprintf(stderr, "\tError stack trace:\n");
+ print_trace(tc->error_stack);
}
}
}
diff --git a/tests/test_lib.h b/tests/test_lib.h
index 9d90e48..7552ebf 100755
--- a/tests/test_lib.h
+++ b/tests/test_lib.h
@@ -7,6 +7,7 @@
#include <string.h>
#include "common.h"
+#include "global.h"
#include <git2.h>
#define DECLARE_SUITE(SNAME) extern git_testsuite *libgit2_suite_##SNAME(void)