Blame docs/error-handling.md

Packit Service 20376f
Error reporting in libgit2
Packit Service 20376f
==========================
Packit Service 20376f
Packit Service 20376f
Libgit2 tries to follow the POSIX style: functions return an `int` value
Packit Service 20376f
with 0 (zero) indicating success and negative values indicating an error.
Packit Service 20376f
There are specific negative error codes for each "expected failure"
Packit Service 20376f
(e.g. `GIT_ENOTFOUND` for files that take a path which might be missing)
Packit Service 20376f
and a generic error code (-1) for all critical or non-specific failures
Packit Service 20376f
(e.g. running out of memory or system corruption).
Packit Service 20376f
Packit Service 20376f
When a negative value is returned, an error message is also set.  The
Packit Service 20376f
message can be accessed via the `giterr_last` function which will return a
Packit Service 20376f
pointer to a `git_error` structure containing the error message text and
Packit Service 20376f
the class of error (i.e. what part of the library generated the error).
Packit Service 20376f
Packit Service 20376f
For instance: An object lookup by SHA prefix (`git_object_lookup_prefix`)
Packit Service 20376f
has two expected failure cases: the SHA is not found at all which returns
Packit Service 20376f
`GIT_ENOTFOUND` or the SHA prefix is ambiguous (i.e. two or more objects
Packit Service 20376f
share the prefix) which returns `GIT_EAMBIGUOUS`.  There are any number of
Packit Service 20376f
critical failures (such as a packfile being corrupted, a loose object
Packit Service 20376f
having the wrong access permissions, etc.) all of which will return -1.
Packit Service 20376f
When the object lookup is successful, it will return 0.
Packit Service 20376f
Packit Service 20376f
If libgit2 was compiled with threads enabled (`-DTHREADSAFE=ON` when using
Packit Service 20376f
CMake), then the error message will be kept in thread-local storage, so it
Packit Service 20376f
will not be modified by other threads.  If threads are not enabled, then
Packit Service 20376f
the error message is in global data.
Packit Service 20376f
Packit Service 20376f
All of the error return codes, the `git_error` type, the error access
Packit Service 20376f
functions, and the error classes are defined in `include/git2/errors.h`.
Packit Service 20376f
See the documentation there for details on the APIs for accessing,
Packit Service 20376f
clearing, and even setting error codes.
Packit Service 20376f
Packit Service 20376f
When writing libgit2 code, please be smart and conservative when returning
Packit Service 20376f
error codes.  Functions usually have a maximum of two or three "expected
Packit Service 20376f
errors" and in most cases only one.  If you feel there are more possible
Packit Service 20376f
expected error scenarios, then the API you are writing may be at too high
Packit Service 20376f
a level for core libgit2.
Packit Service 20376f
Packit Service 20376f
Example usage
Packit Service 20376f
-------------
Packit Service 20376f
Packit Service 20376f
When using libgit2, you will typically capture the return value from
Packit Service 20376f
functions using an `int` variable and check to see if it is negative.
Packit Service 20376f
When that happens, you can, if you wish, look at the specific value or
Packit Service 20376f
look at the error message that was generated.
Packit Service 20376f
Packit Service 20376f
~~~c
Packit Service 20376f
{
Packit Service 20376f
	git_repository *repo;
Packit Service 20376f
	int error = git_repository_open(&repo, "path/to/repo");
Packit Service 20376f
Packit Service 20376f
	if (error < 0) {
Packit Service 20376f
		fprintf(stderr, "Could not open repository: %s\n", giterr_last()->message);
Packit Service 20376f
		exit(1);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	... use `repo` here ...
Packit Service 20376f
Packit Service 20376f
	git_repository_free(repo); /* void function - no error return code */
Packit Service 20376f
}
Packit Service 20376f
~~~
Packit Service 20376f
Packit Service 20376f
Some of the error return values do have meaning.  Optionally, you can look
Packit Service 20376f
at the specific error values to decide what to do.
Packit Service 20376f
Packit Service 20376f
~~~c
Packit Service 20376f
{
Packit Service 20376f
	git_repository *repo;
Packit Service 20376f
	const char *path = "path/to/repo";
Packit Service 20376f
	int error = git_repository_open(&repo, path);
Packit Service 20376f
Packit Service 20376f
	if (error < 0) {
Packit Service 20376f
		if (error == GIT_ENOTFOUND)
Packit Service 20376f
			fprintf(stderr, "Could not find repository at path '%s'\n", path);
Packit Service 20376f
		else
Packit Service 20376f
			fprintf(stderr, "Unable to open repository: %s\n",
Packit Service 20376f
				giterr_last()->message);
Packit Service 20376f
		exit(1);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	... happy ...
Packit Service 20376f
}
Packit Service 20376f
~~~
Packit Service 20376f
Packit Service 20376f
Some of the higher-level language bindings may use a range of information
Packit Service 20376f
from libgit2 to convert error return codes into exceptions, including the
Packit Service 20376f
specific error return codes and even the class of error and the error
Packit Service 20376f
message returned by `giterr_last`, but the full range of that logic is
Packit Service 20376f
beyond the scope of this document.
Packit Service 20376f
Packit Service 20376f
Example internal implementation
Packit Service 20376f
-------------------------------
Packit Service 20376f
Packit Service 20376f
Internally, libgit2 detects error scenarios, records error messages, and
Packit Service 20376f
returns error values.  Errors from low-level functions are generally
Packit Service 20376f
passed upwards (unless the higher level can either handle the error or
Packit Service 20376f
wants to translate the error into something more meaningful).
Packit Service 20376f
Packit Service 20376f
~~~c
Packit Service 20376f
int git_repository_open(git_repository **repository, const char *path)
Packit Service 20376f
{
Packit Service 20376f
	/* perform some logic to open the repository */
Packit Service 20376f
	if (p_exists(path) < 0) {
Packit Service 20376f
		giterr_set(GITERR_REPOSITORY, "The path '%s' doesn't exist", path);
Packit Service 20376f
		return GIT_ENOTFOUND;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	...
Packit Service 20376f
}
Packit Service 20376f
~~~
Packit Service 20376f
Packit Service 20376f
The public error API
Packit Service 20376f
--------------------
Packit Service 20376f
Packit Service 20376f
- `const git_error *giterr_last(void)`: The main function used to look up
Packit Service 20376f
  the last error.  This may return NULL if no error has occurred.
Packit Service 20376f
  Otherwise this should return a `git_error` object indicating the class
Packit Service 20376f
  of error and the error message that was generated by the library.
Packit Service 20376f
Packit Service 20376f
  The last error is stored in thread-local storage when libgit2 is
Packit Service 20376f
  compiled with thread support, so you do not have to worry about another
Packit Service 20376f
  thread overwriting the value.  When thread support is off, the last
Packit Service 20376f
  error is a global value.
Packit Service 20376f
Packit Service 20376f
  _Note_ There are some known bugs in the library where this may return
Packit Service 20376f
  NULL even when an error code was generated.  Please report these as
Packit Service 20376f
  bugs, but in the meantime, please code defensively and check for NULL
Packit Service 20376f
  when calling this function.
Packit Service 20376f
Packit Service 20376f
- `void giterr_clear(void)`: This function clears the last error.  The
Packit Service 20376f
  library will call this when an error is generated by low level function
Packit Service 20376f
  and the higher level function handles the error.
Packit Service 20376f
Packit Service 20376f
  _Note_ There are some known bugs in the library where a low level
Packit Service 20376f
  function's error message is not cleared by higher level code that
Packit Service 20376f
  handles the error and returns zero.  Please report these as bugs, but in
Packit Service 20376f
  the meantime, a zero return value from a libgit2 API does not guarantee
Packit Service 20376f
  that `giterr_last()` will return NULL.
Packit Service 20376f
Packit Service 20376f
- `void giterr_set_str(int error_class, const char *message)`: This
Packit Service 20376f
  function can be used when writing a custom backend module to set the
Packit Service 20376f
  libgit2 error message.  See the documentation on this function for its
Packit Service 20376f
  use.  Normal usage of libgit2 will probably never need to call this API.
Packit Service 20376f
Packit Service 20376f
- `void giterr_set_oom(void)`: This is a standard function for reporting
Packit Service 20376f
  an out-of-memory error.  It is written in a manner that it doesn't have
Packit Service 20376f
  to allocate any extra memory in order to record the error, so this is
Packit Service 20376f
  the best way to report that scenario.
Packit Service 20376f
Packit Service 20376f
Deviations from the standard
Packit Service 20376f
----------------------------
Packit Service 20376f
Packit Service 20376f
There are some public functions that do not return `int` values.  There
Packit Service 20376f
are two primary cases:
Packit Service 20376f
Packit Service 20376f
* `void` return values: If a function has a `void` return, then it will
Packit Service 20376f
  never fail.  This primary will be used for object destructors.
Packit Service 20376f
Packit Service 20376f
* `git_xyz *` return values: These are simple accessor functions where the
Packit Service 20376f
  only meaningful error would typically be looking something up by index
Packit Service 20376f
  and having the index be out of bounds.  In those cases, the function
Packit Service 20376f
  will typically return NULL.
Packit Service 20376f
Packit Service 20376f
* Boolean return values: There are some cases where a function cannot fail
Packit Service 20376f
  and wants to return a boolean value.  In those cases, we try to return 1
Packit Service 20376f
  for true and 0 for false.  These cases are rare and the return value for
Packit Service 20376f
  the function should probably be an `unsigned int` to denote these cases.
Packit Service 20376f
  If you find an exception, please open an issue and let's fix it.
Packit Service 20376f
Packit Service 20376f
There are a few other exceptions to these rules here and there in the
Packit Service 20376f
library, but those are extremely rare and should probably be converted
Packit Service 20376f
over to other to more standard patterns for usage.  Feel free to open
Packit Service 20376f
issues pointing these out.
Packit Service 20376f
Packit Service 20376f
There are some known bugs in the library where some functions may return a
Packit Service 20376f
negative value but not set an error message and some other functions may
Packit Service 20376f
return zero (no error) and yet leave an error message set.  Please report
Packit Service 20376f
these cases as issues and they will be fixed.  In the meanwhile, please
Packit Service 20376f
code defensively, checking that the return value of `giterr_last` is not
Packit Service 20376f
NULL before using it, and not relying on `giterr_last` to return NULL when
Packit Service 20376f
a function returns 0 for success.
Packit Service 20376f
Packit Service 20376f
The internal error API
Packit Service 20376f
----------------------
Packit Service 20376f
Packit Service 20376f
- `void giterr_set(int error_class, const char *fmt, ...)`: This is the
Packit Service 20376f
  main internal function for setting an error.  It works like `printf` to
Packit Service 20376f
  format the error message.  See the notes of `giterr_set_str` for a
Packit Service 20376f
  general description of how error messages are stored (and also about
Packit Service 20376f
  special handling for `error_class` of `GITERR_OS`).
Packit Service 20376f
Packit Service 20376f
Writing error messages
Packit Service 20376f
----------------------
Packit Service 20376f
Packit Service 20376f
Here are some guidelines when writing error messages:
Packit Service 20376f
Packit Service 20376f
- Use proper English, and an impersonal or past tenses: *The given path
Packit Service 20376f
  does not exist*, *Failed to lookup object in ODB*
Packit Service 20376f
Packit Service 20376f
- Use short, direct and objective messages. **One line, max**. libgit2 is
Packit Service 20376f
  a low level library: think that all the messages reported will be thrown
Packit Service 20376f
  as Ruby or Python exceptions. Think how long are common exception
Packit Service 20376f
  messages in those languages.
Packit Service 20376f
Packit Service 20376f
- **Do not add redundant information to the error message**, specially
Packit Service 20376f
  information that can be inferred from the context.
Packit Service 20376f
Packit Service 20376f
	E.g. in `git_repository_open`, do not report a message like "Failed to
Packit Service 20376f
	open repository: path not found". Somebody is calling that
Packit Service 20376f
	function. If it fails, they already know that the repository failed to
Packit Service 20376f
	open!
Packit Service 20376f
Packit Service 20376f
General guidelines for error reporting
Packit Service 20376f
--------------------------------------
Packit Service 20376f
Packit Service 20376f
- Libgit2 does not handle programming errors with these
Packit Service 20376f
  functions. Programming errors are `assert`ed, and when their source is
Packit Service 20376f
  internal, fixed as soon as possible. This is C, people.
Packit Service 20376f
Packit Service 20376f
	Example of programming errors that would **not** be handled: passing
Packit Service 20376f
    NULL to a function that expects a valid pointer; passing a `git_tree`
Packit Service 20376f
    to a function that expects a `git_commit`. All these cases need to be
Packit Service 20376f
    identified with `assert` and fixed asap.
Packit Service 20376f
Packit Service 20376f
	Example of a runtime error: failing to parse a `git_tree` because it
Packit Service 20376f
    contains invalid data. Failing to open a file because it doesn't exist
Packit Service 20376f
    on disk. These errors are handled, a meaningful error message is set,
Packit Service 20376f
    and an error code is returned.
Packit Service 20376f
Packit Service 20376f
- In general, *do not* try to overwrite errors internally and *do*
Packit Service 20376f
  propagate error codes from lower level functions to the higher level.
Packit Service 20376f
  There are some cases where propagating an error code will be more
Packit Service 20376f
  confusing rather than less, so there are some exceptions to this rule,
Packit Service 20376f
  but the default behavior should be to simply clean up and pass the error
Packit Service 20376f
  on up to the caller.
Packit Service 20376f
Packit Service 20376f
    **WRONG**
Packit Service 20376f
Packit Service 20376f
	~~~c
Packit Service 20376f
	int git_commit_parent(...)
Packit Service 20376f
	{
Packit Service 20376f
		...
Packit Service 20376f
Packit Service 20376f
		if (git_commit_lookup(parent, repo, parent_id) < 0) {
Packit Service 20376f
			giterr_set(GITERR_COMMIT, "Overwrite lookup error message");
Packit Service 20376f
			return -1; /* mask error code */
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		...
Packit Service 20376f
	}
Packit Service 20376f
	~~~
Packit Service 20376f
Packit Service 20376f
	**RIGHT**
Packit Service 20376f
Packit Service 20376f
	~~~c
Packit Service 20376f
	int git_commit_parent(...)
Packit Service 20376f
	{
Packit Service 20376f
		...
Packit Service 20376f
Packit Service 20376f
		error = git_commit_lookup(parent, repo, parent_id);
Packit Service 20376f
		if (error < 0) {
Packit Service 20376f
			/* cleanup intermediate objects if necessary */
Packit Service 20376f
			/* leave error message and propagate error code */
Packit Service 20376f
			return error;
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		...
Packit Service 20376f
	}
Packit Service 20376f
	~~~