Merge pull request #1941 from libgit2/rb/preserve-iterator-error Preserve error messages during file system iterator cleanup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
diff --git a/include/git2/errors.h b/include/git2/errors.h
index a454ac9..be7a31d 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -8,6 +8,7 @@
#define INCLUDE_git_errors_h__
#include "common.h"
+#include "buffer.h"
/**
* @file git2/errors.h
@@ -45,6 +46,7 @@ typedef struct {
/** Error classes */
typedef enum {
+ GITERR_NONE = 0,
GITERR_NOMEMORY,
GITERR_OS,
GITERR_INVALID,
@@ -85,6 +87,18 @@ GIT_EXTERN(const git_error *) giterr_last(void);
GIT_EXTERN(void) giterr_clear(void);
/**
+ * Get the last error data and clear it.
+ *
+ * This copies the last error into the given `git_error` struct
+ * and returns 0 if the copy was successful, leaving the error
+ * cleared as if `giterr_clear` had been called.
+ *
+ * If there was no existing error in the library, -1 will be returned
+ * and the contents of `cpy` will be left unmodified.
+ */
+GIT_EXTERN(int) giterr_detach(git_error *cpy);
+
+/**
* Set the error message string for this thread.
*
* This function is public so that custom ODB backends and the like can
diff --git a/src/errors.c b/src/errors.c
index c9d9e4e..d04da4c 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -112,6 +112,24 @@ void giterr_clear(void)
#endif
}
+int giterr_detach(git_error *cpy)
+{
+ git_error *error = GIT_GLOBAL->last_error;
+
+ assert(cpy);
+
+ if (!error)
+ return -1;
+
+ cpy->message = error->message;
+ cpy->klass = error->klass;
+
+ error->message = NULL;
+ giterr_clear();
+
+ return 0;
+}
+
const git_error *giterr_last(void)
{
return GIT_GLOBAL->last_error;
diff --git a/src/iterator.c b/src/iterator.c
index c0d7862..8646399 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -991,8 +991,21 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
fi->base.start, fi->base.end, &ff->entries);
if (error < 0) {
+ git_error last_error = {0};
+
+ giterr_detach(&last_error);
+
+ /* these callbacks may clear the error message */
fs_iterator__free_frame(ff);
fs_iterator__advance_over(NULL, (git_iterator *)fi);
+ /* next time return value we skipped to */
+ fi->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
+
+ if (last_error.message) {
+ giterr_set_str(last_error.klass, last_error.message);
+ free(last_error.message);
+ }
+
return error;
}
diff --git a/tests-clar/repo/iterator.c b/tests-clar/repo/iterator.c
index 1c513e9..56b5185 100644
--- a/tests-clar/repo/iterator.c
+++ b/tests-clar/repo/iterator.c
@@ -926,3 +926,37 @@ void test_repo_iterator__fs2(void)
expect_iterator_items(i, 12, expect_base, 12, expect_base);
git_iterator_free(i);
}
+
+void test_repo_iterator__fs_preserves_error(void)
+{
+ git_iterator *i;
+ const git_index_entry *e;
+
+ if (!cl_is_chmod_supported())
+ return;
+
+ g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+ cl_must_pass(p_mkdir("empty_standard_repo/r", 0777));
+ cl_git_mkfile("empty_standard_repo/r/a", "hello");
+ cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777));
+ cl_git_mkfile("empty_standard_repo/r/b/problem", "not me");
+ cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000));
+ cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777));
+ cl_git_mkfile("empty_standard_repo/r/d", "final");
+
+ cl_git_pass(git_iterator_for_filesystem(
+ &i, "empty_standard_repo/r", 0, NULL, NULL));
+
+ cl_git_pass(git_iterator_advance(&e, i)); /* a */
+ cl_git_fail(git_iterator_advance(&e, i)); /* b */
+ cl_assert(giterr_last());
+ cl_assert(giterr_last()->message != NULL);
+ /* skip 'c/' empty directory */
+ cl_git_pass(git_iterator_advance(&e, i)); /* d */
+ cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i));
+
+ cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777));
+
+ git_iterator_free(i);
+}