Hash :
9324d16e
Author :
Date :
2021-11-06T16:14:47
Libgit2 tries to follow the POSIX style: functions return an int
value
with 0 (zero) indicating success and negative values indicating an error.
There are specific negative error codes for each “expected failure”
(e.g. GIT_ENOTFOUND
for files that take a path which might be missing)
and a generic error code (-1) for all critical or non-specific failures
(e.g. running out of memory or system corruption).
When a negative value is returned, an error message is also set. The
message can be accessed via the git_error_last
function which will return a
pointer to a git_error
structure containing the error message text and
the class of error (i.e. what part of the library generated the error).
For instance: An object lookup by SHA prefix (git_object_lookup_prefix
)
has two expected failure cases: the SHA is not found at all which returns
GIT_ENOTFOUND
or the SHA prefix is ambiguous (i.e. two or more objects
share the prefix) which returns GIT_EAMBIGUOUS
. There are any number of
critical failures (such as a packfile being corrupted, a loose object
having the wrong access permissions, etc.) all of which will return -1.
When the object lookup is successful, it will return 0.
If libgit2 was compiled with threads enabled (-DUSE_THREADS=ON
when using
CMake), then the error message will be kept in thread-local storage, so it
will not be modified by other threads. If threads are not enabled, then
the error message is in global data.
All of the error return codes, the git_error
type, the error access
functions, and the error classes are defined in include/git2/errors.h
.
See the documentation there for details on the APIs for accessing,
clearing, and even setting error codes.
When writing libgit2 code, please be smart and conservative when returning error codes. Functions usually have a maximum of two or three “expected errors” and in most cases only one. If you feel there are more possible expected error scenarios, then the API you are writing may be at too high a level for core libgit2.
When using libgit2, you will typically capture the return value from
functions using an int
variable and check to see if it is negative.
When that happens, you can, if you wish, look at the specific value or
look at the error message that was generated.
{
git_repository *repo;
int error = git_repository_open(&repo, "path/to/repo");
if (error < 0) {
fprintf(stderr, "Could not open repository: %s\n", git_error_last()->message);
exit(1);
}
... use `repo` here ...
git_repository_free(repo); /* void function - no error return code */
}
Some of the error return values do have meaning. Optionally, you can look at the specific error values to decide what to do.
{
git_repository *repo;
const char *path = "path/to/repo";
int error = git_repository_open(&repo, path);
if (error < 0) {
if (error == GIT_ENOTFOUND)
fprintf(stderr, "Could not find repository at path '%s'\n", path);
else
fprintf(stderr, "Unable to open repository: %s\n",
git_error_last()->message);
exit(1);
}
... happy ...
}
Some of the higher-level language bindings may use a range of information
from libgit2 to convert error return codes into exceptions, including the
specific error return codes and even the class of error and the error
message returned by git_error_last
, but the full range of that logic is
beyond the scope of this document.
Internally, libgit2 detects error scenarios, records error messages, and returns error values. Errors from low-level functions are generally passed upwards (unless the higher level can either handle the error or wants to translate the error into something more meaningful).
int git_repository_open(git_repository **repository, const char *path)
{
/* perform some logic to open the repository */
if (p_exists(path) < 0) {
git_error_set(GIT_ERROR_REPOSITORY, "The path '%s' doesn't exist", path);
return GIT_ENOTFOUND;
}
...
}
Note that some error codes have been defined with a specific meaning in the context of callbacks:
GIT_EUSER
provides a way to bubble up a non libgit2-related failure, which
allows it to be preserved all the way up to the initial function call (a git_cred
setup trying to access an unavailable LDAP server for instance). GIT_EPASSTHROUGH
provides a way to tell libgit2 that it should behave as if
no callback was provided. This is of special interest to bindings, which would
always provide a C function as a “trampoline”, and decide at runtime what to do.
const git_error *git_error_last(void)
: The main function used to look up
the last error. This may return NULL if no error has occurred.
Otherwise this should return a git_error
object indicating the class
of error and the error message that was generated by the library.
Do not use this function unless the prior call to a libgit2 API
returned an error, as it can otherwise give misleading results.
libgit2’s error strings are not cleared aggressively,
and this function may return an error string that reflects a prior error,
possibly even reflecting internal state.
The last error is stored in thread-local storage when libgit2 is compiled with thread support, so you do not have to worry about another thread overwriting the value. When thread support is off, the last error is a global value.
Note There are some known bugs in the library where this may return NULL even when an error code was generated. Please report these as bugs, but in the meantime, please code defensively and check for NULL when calling this function.
void git_error_clear(void)
: This function clears the last error. The
library will call this when an error is generated by low level function
and the higher level function handles the error.
Note There are some known bugs in the library where a low level
function’s error message is not cleared by higher level code that
handles the error and returns zero. Please report these as bugs, but in
the meantime, a zero return value from a libgit2 API does not guarantee
that git_error_last()
will return NULL.
void git_error_set(int error_class, const char *message)
: This
function can be used when writing a custom backend module to set the
libgit2 error message. See the documentation on this function for its
use. Normal usage of libgit2 will probably never need to call this API.
void git_error_set_oom(void)
: This is a standard function for reporting
an out-of-memory error. It is written in a manner that it doesn’t have
to allocate any extra memory in order to record the error, so this is
the best way to report that scenario.
There are some public functions that do not return int
values. There
are two primary cases:
void
return values: If a function has a void
return, then it will
never fail. This primary will be used for object destructors.
git_xyz *
return values: These are simple accessor functions where the
only meaningful error would typically be looking something up by index
and having the index be out of bounds. In those cases, the function
will typically return NULL.
Boolean return values: There are some cases where a function cannot fail
and wants to return a boolean value. In those cases, we try to return 1
for true and 0 for false. These cases are rare and the return value for
the function should probably be an unsigned int
to denote these cases.
If you find an exception, please open an issue and let’s fix it.
There are a few other exceptions to these rules here and there in the library, but those are extremely rare and should probably be converted over to other to more standard patterns for usage. Feel free to open issues pointing these out.
There are some known bugs in the library where some functions may return a
negative value but not set an error message and some other functions may
return zero (no error) and yet leave an error message set. Please report
these cases as issues and they will be fixed. In the meanwhile, please
code defensively, checking that the return value of git_error_last
is not
NULL before using it, and not relying on git_error_last
to return NULL when
a function returns 0 for success.
void git_error_set(int error_class, const char *fmt, ...)
: This is the
main internal function for setting an error. It works like printf
to
format the error message. See the notes of git_error_set_str
for a
general description of how error messages are stored (and also about
special handling for error_class
of GIT_ERROR_OS
). Here are some guidelines when writing error messages:
Use proper English, and an impersonal or past tenses: The given path does not exist, Failed to lookup object in ODB
Use short, direct and objective messages. One line, max. libgit2 is a low level library: think that all the messages reported will be thrown as Ruby or Python exceptions. Think how long are common exception messages in those languages.
Do not add redundant information to the error message, specially information that can be inferred from the context.
E.g. in git_repository_open
, do not report a message like “Failed to
open repository: path not found”. Somebody is calling that
function. If it fails, they already know that the repository failed to
open!
Libgit2 does not handle programming errors with these
functions. Programming errors are assert
ed, and when their source is
internal, fixed as soon as possible. This is C, people.
Example of programming errors that would not be handled: passing
NULL to a function that expects a valid pointer; passing a git_tree
to a function that expects a git_commit
. All these cases need to be
identified with assert
and fixed asap.
Example of a runtime error: failing to parse a git_tree
because it
contains invalid data. Failing to open a file because it doesn’t exist
on disk. These errors are handled, a meaningful error message is set,
and an error code is returned.
In general, do not try to overwrite errors internally and do propagate error codes from lower level functions to the higher level. There are some cases where propagating an error code will be more confusing rather than less, so there are some exceptions to this rule, but the default behavior should be to simply clean up and pass the error on up to the caller.
WRONG
int git_commit_parent(...)
{
...
if (git_commit_lookup(parent, repo, parent_id) < 0) {
git_error_set(GIT_ERROR_COMMIT, "Overwrite lookup error message");
return -1; /* mask error code */
}
...
}
RIGHT
int git_commit_parent(...)
{
...
error = git_commit_lookup(parent, repo, parent_id);
if (error < 0) {
/* cleanup intermediate objects if necessary */
/* leave error message and propagate error code */
return error;
}
...
}
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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
Error reporting in libgit2
==========================
Libgit2 tries to follow the POSIX style: functions return an `int` value
with 0 (zero) indicating success and negative values indicating an error.
There are specific negative error codes for each "expected failure"
(e.g. `GIT_ENOTFOUND` for files that take a path which might be missing)
and a generic error code (-1) for all critical or non-specific failures
(e.g. running out of memory or system corruption).
When a negative value is returned, an error message is also set. The
message can be accessed via the `git_error_last` function which will return a
pointer to a `git_error` structure containing the error message text and
the class of error (i.e. what part of the library generated the error).
For instance: An object lookup by SHA prefix (`git_object_lookup_prefix`)
has two expected failure cases: the SHA is not found at all which returns
`GIT_ENOTFOUND` or the SHA prefix is ambiguous (i.e. two or more objects
share the prefix) which returns `GIT_EAMBIGUOUS`. There are any number of
critical failures (such as a packfile being corrupted, a loose object
having the wrong access permissions, etc.) all of which will return -1.
When the object lookup is successful, it will return 0.
If libgit2 was compiled with threads enabled (`-DUSE_THREADS=ON` when using
CMake), then the error message will be kept in thread-local storage, so it
will not be modified by other threads. If threads are not enabled, then
the error message is in global data.
All of the error return codes, the `git_error` type, the error access
functions, and the error classes are defined in `include/git2/errors.h`.
See the documentation there for details on the APIs for accessing,
clearing, and even setting error codes.
When writing libgit2 code, please be smart and conservative when returning
error codes. Functions usually have a maximum of two or three "expected
errors" and in most cases only one. If you feel there are more possible
expected error scenarios, then the API you are writing may be at too high
a level for core libgit2.
Example usage
-------------
When using libgit2, you will typically capture the return value from
functions using an `int` variable and check to see if it is negative.
When that happens, you can, if you wish, look at the specific value or
look at the error message that was generated.
~~~c
{
git_repository *repo;
int error = git_repository_open(&repo, "path/to/repo");
if (error < 0) {
fprintf(stderr, "Could not open repository: %s\n", git_error_last()->message);
exit(1);
}
... use `repo` here ...
git_repository_free(repo); /* void function - no error return code */
}
~~~
Some of the error return values do have meaning. Optionally, you can look
at the specific error values to decide what to do.
~~~c
{
git_repository *repo;
const char *path = "path/to/repo";
int error = git_repository_open(&repo, path);
if (error < 0) {
if (error == GIT_ENOTFOUND)
fprintf(stderr, "Could not find repository at path '%s'\n", path);
else
fprintf(stderr, "Unable to open repository: %s\n",
git_error_last()->message);
exit(1);
}
... happy ...
}
~~~
Some of the higher-level language bindings may use a range of information
from libgit2 to convert error return codes into exceptions, including the
specific error return codes and even the class of error and the error
message returned by `git_error_last`, but the full range of that logic is
beyond the scope of this document.
Example internal implementation
-------------------------------
Internally, libgit2 detects error scenarios, records error messages, and
returns error values. Errors from low-level functions are generally
passed upwards (unless the higher level can either handle the error or
wants to translate the error into something more meaningful).
~~~c
int git_repository_open(git_repository **repository, const char *path)
{
/* perform some logic to open the repository */
if (p_exists(path) < 0) {
git_error_set(GIT_ERROR_REPOSITORY, "The path '%s' doesn't exist", path);
return GIT_ENOTFOUND;
}
...
}
~~~
Note that some error codes have been defined with a specific meaning in the
context of callbacks:
- `GIT_EUSER` provides a way to bubble up a non libgit2-related failure, which
allows it to be preserved all the way up to the initial function call (a `git_cred`
setup trying to access an unavailable LDAP server for instance).
- `GIT_EPASSTHROUGH` provides a way to tell libgit2 that it should behave as if
no callback was provided. This is of special interest to bindings, which would
always provide a C function as a "trampoline", and decide at runtime what to do.
The public error API
--------------------
- `const git_error *git_error_last(void)`: The main function used to look up
the last error. This may return NULL if no error has occurred.
Otherwise this should return a `git_error` object indicating the class
of error and the error message that was generated by the library.
Do not use this function unless the prior call to a libgit2 API
returned an error, as it can otherwise give misleading results.
libgit2's error strings are not cleared aggressively,
and this function may return an error string that reflects a prior error,
possibly even reflecting internal state.
The last error is stored in thread-local storage when libgit2 is
compiled with thread support, so you do not have to worry about another
thread overwriting the value. When thread support is off, the last
error is a global value.
_Note_ There are some known bugs in the library where this may return
NULL even when an error code was generated. Please report these as
bugs, but in the meantime, please code defensively and check for NULL
when calling this function.
- `void git_error_clear(void)`: This function clears the last error. The
library will call this when an error is generated by low level function
and the higher level function handles the error.
_Note_ There are some known bugs in the library where a low level
function's error message is not cleared by higher level code that
handles the error and returns zero. Please report these as bugs, but in
the meantime, a zero return value from a libgit2 API does not guarantee
that `git_error_last()` will return NULL.
- `void git_error_set(int error_class, const char *message)`: This
function can be used when writing a custom backend module to set the
libgit2 error message. See the documentation on this function for its
use. Normal usage of libgit2 will probably never need to call this API.
- `void git_error_set_oom(void)`: This is a standard function for reporting
an out-of-memory error. It is written in a manner that it doesn't have
to allocate any extra memory in order to record the error, so this is
the best way to report that scenario.
Deviations from the standard
----------------------------
There are some public functions that do not return `int` values. There
are two primary cases:
* `void` return values: If a function has a `void` return, then it will
never fail. This primary will be used for object destructors.
* `git_xyz *` return values: These are simple accessor functions where the
only meaningful error would typically be looking something up by index
and having the index be out of bounds. In those cases, the function
will typically return NULL.
* Boolean return values: There are some cases where a function cannot fail
and wants to return a boolean value. In those cases, we try to return 1
for true and 0 for false. These cases are rare and the return value for
the function should probably be an `unsigned int` to denote these cases.
If you find an exception, please open an issue and let's fix it.
There are a few other exceptions to these rules here and there in the
library, but those are extremely rare and should probably be converted
over to other to more standard patterns for usage. Feel free to open
issues pointing these out.
There are some known bugs in the library where some functions may return a
negative value but not set an error message and some other functions may
return zero (no error) and yet leave an error message set. Please report
these cases as issues and they will be fixed. In the meanwhile, please
code defensively, checking that the return value of `git_error_last` is not
NULL before using it, and not relying on `git_error_last` to return NULL when
a function returns 0 for success.
The internal error API
----------------------
- `void git_error_set(int error_class, const char *fmt, ...)`: This is the
main internal function for setting an error. It works like `printf` to
format the error message. See the notes of `git_error_set_str` for a
general description of how error messages are stored (and also about
special handling for `error_class` of `GIT_ERROR_OS`).
Writing error messages
----------------------
Here are some guidelines when writing error messages:
- Use proper English, and an impersonal or past tenses: *The given path
does not exist*, *Failed to lookup object in ODB*
- Use short, direct and objective messages. **One line, max**. libgit2 is
a low level library: think that all the messages reported will be thrown
as Ruby or Python exceptions. Think how long are common exception
messages in those languages.
- **Do not add redundant information to the error message**, specially
information that can be inferred from the context.
E.g. in `git_repository_open`, do not report a message like "Failed to
open repository: path not found". Somebody is calling that
function. If it fails, they already know that the repository failed to
open!
General guidelines for error reporting
--------------------------------------
- Libgit2 does not handle programming errors with these
functions. Programming errors are `assert`ed, and when their source is
internal, fixed as soon as possible. This is C, people.
Example of programming errors that would **not** be handled: passing
NULL to a function that expects a valid pointer; passing a `git_tree`
to a function that expects a `git_commit`. All these cases need to be
identified with `assert` and fixed asap.
Example of a runtime error: failing to parse a `git_tree` because it
contains invalid data. Failing to open a file because it doesn't exist
on disk. These errors are handled, a meaningful error message is set,
and an error code is returned.
- In general, *do not* try to overwrite errors internally and *do*
propagate error codes from lower level functions to the higher level.
There are some cases where propagating an error code will be more
confusing rather than less, so there are some exceptions to this rule,
but the default behavior should be to simply clean up and pass the error
on up to the caller.
**WRONG**
~~~c
int git_commit_parent(...)
{
...
if (git_commit_lookup(parent, repo, parent_id) < 0) {
git_error_set(GIT_ERROR_COMMIT, "Overwrite lookup error message");
return -1; /* mask error code */
}
...
}
~~~
**RIGHT**
~~~c
int git_commit_parent(...)
{
...
error = git_commit_lookup(parent, repo, parent_id);
if (error < 0) {
/* cleanup intermediate objects if necessary */
/* leave error message and propagate error code */
return error;
}
...
}
~~~