diff options
Diffstat (limited to '')
-rw-r--r-- | docs/error-handling.md | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/docs/error-handling.md b/docs/error-handling.md new file mode 100644 index 0000000..13ce78f --- /dev/null +++ b/docs/error-handling.md @@ -0,0 +1,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; + } + + ... + } + ~~~ |