diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f90540b55 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.c eol=lf +*.h eol=lf diff --git a/README.md b/README.md index dae6a76bf..7cfde59f8 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Under Unix-like systems, like Linux, *BSD and Mac OS X, libgit2 expects `pthread they should be installed by default on all systems. Under Windows, libgit2 uses the native Windows API for threading. -Additionally, he following libraries may be used as replacement for built-in functionality: +Additionally, the following libraries may be used as replacement for built-in functionality: * LibSSL **(optional)** @@ -88,6 +88,9 @@ The waf build system for libgit2 accepts the following flags: --with-sqlite Enable sqlite support. + --with-redis + Enable redis support. + You can run `./waf --help` to see a full list of install options and targets. diff --git a/include/git2.h b/include/git2.h index 29fa98e18..0d0e54898 100644 --- a/include/git2.h +++ b/include/git2.h @@ -26,9 +26,9 @@ #ifndef INCLUDE_git_git_h__ #define INCLUDE_git_git_h__ -#define LIBGIT2_VERSION "0.11.0" +#define LIBGIT2_VERSION "0.12.0" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 10 +#define LIBGIT2_VER_MINOR 12 #define LIBGIT2_VER_REVISION 0 #include "git2/common.h" @@ -52,5 +52,6 @@ #include "git2/tree.h" #include "git2/index.h" +#include "git2/config.h" #endif diff --git a/include/git2/common.h b/include/git2/common.h index 22c7cc466..9a27ac2e5 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -84,92 +84,6 @@ * @{ */ -/** Operation completed successfully. */ -#define GIT_SUCCESS 0 - -/** - * Operation failed, with unspecified reason. - * This value also serves as the base error code; all other - * error codes are subtracted from it such that all errors - * are < 0, in typical POSIX C tradition. - */ -#define GIT_ERROR -1 - -/** Input was not a properly formatted Git object id. */ -#define GIT_ENOTOID (GIT_ERROR - 1) - -/** Input does not exist in the scope searched. */ -#define GIT_ENOTFOUND (GIT_ERROR - 2) - -/** Not enough space available. */ -#define GIT_ENOMEM (GIT_ERROR - 3) - -/** Consult the OS error information. */ -#define GIT_EOSERR (GIT_ERROR - 4) - -/** The specified object is of invalid type */ -#define GIT_EOBJTYPE (GIT_ERROR - 5) - -/** The specified object has its data corrupted */ -#define GIT_EOBJCORRUPTED (GIT_ERROR - 6) - -/** The specified repository is invalid */ -#define GIT_ENOTAREPO (GIT_ERROR - 7) - -/** The object type is invalid or doesn't match */ -#define GIT_EINVALIDTYPE (GIT_ERROR - 8) - -/** The object cannot be written because it's missing internal data */ -#define GIT_EMISSINGOBJDATA (GIT_ERROR - 9) - -/** The packfile for the ODB is corrupted */ -#define GIT_EPACKCORRUPTED (GIT_ERROR - 10) - -/** Failed to acquire or release a file lock */ -#define GIT_EFLOCKFAIL (GIT_ERROR - 11) - -/** The Z library failed to inflate/deflate an object's data */ -#define GIT_EZLIB (GIT_ERROR - 12) - -/** The queried object is currently busy */ -#define GIT_EBUSY (GIT_ERROR - 13) - -/** The index file is not backed up by an existing repository */ -#define GIT_EBAREINDEX (GIT_ERROR - 14) - -/** The name of the reference is not valid */ -#define GIT_EINVALIDREFNAME (GIT_ERROR - 15) - -/** The specified reference has its data corrupted */ -#define GIT_EREFCORRUPTED (GIT_ERROR - 16) - -/** The specified symbolic reference is too deeply nested */ -#define GIT_ETOONESTEDSYMREF (GIT_ERROR - 17) - -/** The pack-refs file is either corrupted or its format is not currently supported */ -#define GIT_EPACKEDREFSCORRUPTED (GIT_ERROR - 18) - -/** The path is invalid */ -#define GIT_EINVALIDPATH (GIT_ERROR - 19) - -/** The revision walker is empty; there are no more commits left to iterate */ -#define GIT_EREVWALKOVER (GIT_ERROR - 20) - -/** The state of the reference is not valid */ -#define GIT_EINVALIDREFSTATE (GIT_ERROR - 21) - -/** This feature has not been implemented yet */ -#define GIT_ENOTIMPLEMENTED (GIT_ERROR - 22) - -/** A reference with this name already exists */ -#define GIT_EEXISTS (GIT_ERROR - 23) - -/** The given integer literal is too large to be parsed */ -#define GIT_EOVERFLOW (GIT_ERROR - 24) - -/** The given literal is not a valid number */ -#define GIT_ENOTNUM (GIT_ERROR - 25) - GIT_BEGIN_DECL typedef struct { diff --git a/include/git2/config.h b/include/git2/config.h new file mode 100644 index 000000000..3ebbe64de --- /dev/null +++ b/include/git2/config.h @@ -0,0 +1,171 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDE_git_config_h__ +#define INCLUDE_git_config_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/config.h + * @brief Git config management routines + * @defgroup git_config Git config management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Allocate a new configuration + */ +GIT_EXTERN(int) git_config_new(git_config **out); + +/** + * Open a configuration file + * + * @param cfg_out pointer to the configuration data + * @param path where to load the confiration from + */ +GIT_EXTERN(int) git_config_open_bare(git_config **cfg_out, const char *path); + +/** + * + */ +GIT_EXTERN(int) git_config_add_backend(git_config *cfg, git_config_backend *backend, int priority); + +/** + * Free the configuration and its associated memory + * + * @param cfg the configuration to free + */ +GIT_EXTERN(void) git_config_free(git_config *cfg); + +/** + * Get the value of an integer config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_get_int(git_config *cfg, const char *name, int *out); + +/** + * Get the value of a long integer config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_get_long(git_config *cfg, const char *name, long int *out); + +/** + * Get the value of a boolean config variable. + * + * This function uses the usual C convention of 0 being false and + * anything else true. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_get_bool(git_config *cfg, const char *name, int *out); + +/** + * Get the value of a string config variable. + * + * The string is owned by the variable and should not be freed by the + * user. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable's value + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_get_string(git_config *cfg, const char *name, const char **out); + +/** + * Set the value of an integer config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_set_int(git_config *cfg, const char *name, int value); + +/** + * Set the value of a long integer config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param out pointer to the variable where the value should be stored + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_set_long(git_config *cfg, const char *name, long int value); + +/** + * Set the value of a boolean config variable. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param value the value to store + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value); + +/** + * Set the value of a string config variable. + * + * A copy of the string is made and the user is free to use it + * afterwards. + * + * @param cfg where to look for the variable + * @param name the variable's name + * @param value the string to store. + * @return GIT_SUCCESS on success; error code otherwise + */ +GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value); + +/** + * Perform an operation on each config variable. + * + * The callback is passed a pointer to a config variable name and the + * data pointer passed to this function. As soon as one of the + * callback functions returns something other than 0, this function + * returns that value. + * + * @param cfg where to get the variables from + * @param callback the function to call on each variable + * @param data the data to pass to the callback + * @return GIT_SUCCESS or the return value of the callback which didn't return 0 + */ +GIT_EXTERN(int) git_config_foreach(git_config *cfg, int (*callback)(const char *, void *data), void *data); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/config_backend.h b/include/git2/config_backend.h new file mode 100644 index 000000000..427cd95dd --- /dev/null +++ b/include/git2/config_backend.h @@ -0,0 +1,57 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDE_git_config_backend_h__ +#define INCLUDE_git_config_backend_h__ + +#include "common.h" +#include "types.h" +#include "config.h" + +GIT_BEGIN_DECL + +struct git_config; + +struct git_config_backend { + struct git_config *cfg; + /* Open means open the file/database and parse if necessary */ + int (*open)(struct git_config_backend *); + int (* get)(struct git_config_backend *, const char *key, const char **value); + int (* set)(struct git_config_backend *, const char *key, const char *value); + int (*foreach)(struct git_config_backend *, int (*fn)(const char *, void *), void *data); + void (*free)(struct git_config_backend *); +}; + +/** + * Create a file-backed configuration backend + * + * @param out the new backend + * @path where the config file is located + */ +GIT_EXTERN(int) git_config_backend_file(struct git_config_backend **out, const char *path); + +GIT_END_DECL + +#endif diff --git a/include/git2/errors.h b/include/git2/errors.h index 627e67c70..7e957b803 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -25,6 +25,8 @@ #ifndef INCLUDE_git_errors_h__ #define INCLUDE_git_errors_h__ +#include "common.h" + /** * @file git2/errors.h * @brief Git error handling routines and variables @@ -33,8 +35,106 @@ */ GIT_BEGIN_DECL +typedef enum { + GIT_SUCCESS = 0, + GIT_ERROR = -1, + + /** Input was not a properly formatted Git object id. */ + GIT_ENOTOID = -2, + + /** Input does not exist in the scope searched. */ + GIT_ENOTFOUND = -3, + + /** Not enough space available. */ + GIT_ENOMEM = -4, + + /** Consult the OS error information. */ + GIT_EOSERR = -5, + + /** The specified object is of invalid type */ + GIT_EOBJTYPE = -6, + + /** The specified repository is invalid */ + GIT_ENOTAREPO = -7, + + /** The object type is invalid or doesn't match */ + GIT_EINVALIDTYPE = -8, + + /** The object cannot be written because it's missing internal data */ + GIT_EMISSINGOBJDATA = -9, + + /** The packfile for the ODB is corrupted */ + GIT_EPACKCORRUPTED = -10, + + /** Failed to acquire or release a file lock */ + GIT_EFLOCKFAIL = -11, + + /** The Z library failed to inflate/deflate an object's data */ + GIT_EZLIB = -12, + + /** The queried object is currently busy */ + GIT_EBUSY = -13, + + /** The index file is not backed up by an existing repository */ + GIT_EBAREINDEX = -14, + + /** The name of the reference is not valid */ + GIT_EINVALIDREFNAME = -15, + + /** The specified reference has its data corrupted */ + GIT_EREFCORRUPTED = -16, + + /** The specified symbolic reference is too deeply nested */ + GIT_ETOONESTEDSYMREF = -17, + + /** The pack-refs file is either corrupted or its format is not currently supported */ + GIT_EPACKEDREFSCORRUPTED = -18, + + /** The path is invalid */ + GIT_EINVALIDPATH = -19, + + /** The revision walker is empty; there are no more commits left to iterate */ + GIT_EREVWALKOVER = -20, + + /** The state of the reference is not valid */ + GIT_EINVALIDREFSTATE = -21, + + /** This feature has not been implemented yet */ + GIT_ENOTIMPLEMENTED = -22, + + /** A reference with this name already exists */ + GIT_EEXISTS = -23, + + /** The given integer literal is too large to be parsed */ + GIT_EOVERFLOW = -24, + + /** The given literal is not a valid number */ + GIT_ENOTNUM = -25, + + /** Streaming error */ + GIT_ESTREAM = -26, + + /** invalid arguments to function */ + GIT_EINVALIDARGS = -27, + + /** The specified object has its data corrupted */ + GIT_EOBJCORRUPTED = -28, +} git_error; + +/** + * Return a detailed error string with the latest error + * that occurred in the library. + * @return a string explaining the error + */ +GIT_EXTERN(const char *) git_lasterror(void); + /** * strerror() for the Git library + * + * Get a string description for a given error code. + * NOTE: This method will be eventually deprecated in favor + * of the new `git_lasterror`. + * * @param num The error code to explain * @return a string explaining the error code */ diff --git a/include/git2/index.h b/include/git2/index.h index 09993a154..2e5c17bc3 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -51,39 +51,29 @@ GIT_BEGIN_DECL * * In-memory only flags: */ -#define GIT_IDXENTRY_UPDATE (1 << 16) -#define GIT_IDXENTRY_REMOVE (1 << 17) -#define GIT_IDXENTRY_UPTODATE (1 << 18) -#define GIT_IDXENTRY_ADDED (1 << 19) +#define GIT_IDXENTRY_UPDATE (1 << 0) +#define GIT_IDXENTRY_REMOVE (1 << 1) +#define GIT_IDXENTRY_UPTODATE (1 << 2) +#define GIT_IDXENTRY_ADDED (1 << 3) -#define GIT_IDXENTRY_HASHED (1 << 20) -#define GIT_IDXENTRY_UNHASHED (1 << 21) -#define GIT_IDXENTRY_WT_REMOVE (1 << 22) /* remove in work directory */ -#define GIT_IDXENTRY_CONFLICTED (1 << 23) +#define GIT_IDXENTRY_HASHED (1 << 4) +#define GIT_IDXENTRY_UNHASHED (1 << 5) +#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */ +#define GIT_IDXENTRY_CONFLICTED (1 << 7) -#define GIT_IDXENTRY_UNPACKED (1 << 24) -#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 25) +#define GIT_IDXENTRY_UNPACKED (1 << 8) +#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9) /* * Extended on-disk flags: */ -#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 29) -#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 30) +#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13) +#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14) /* GIT_IDXENTRY_EXTENDED2 is for future extension */ -#define GIT_IDXENTRY_EXTENDED2 (1 << 31) +#define GIT_IDXENTRY_EXTENDED2 (1 << 15) #define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) -/* - * Safeguard to avoid saving wrong flags: - * - GIT_IDXENTRY_EXTENDED2 won't get saved until its semantic is known - * - Bits in 0x0000FFFF have been saved in flags already - * - Bits in 0x003F0000 are currently in-memory flags - */ -#if GIT_IDXENTRY_EXTENDED_FLAGS & 0x803FFFFF -#error "GIT_IDXENTRY_EXTENDED_FLAGS out of range" -#endif - /** Time used in a git index entry */ typedef struct { git_time_t seconds; @@ -188,7 +178,12 @@ GIT_EXTERN(int) git_index_write(git_index *index); GIT_EXTERN(int) git_index_find(git_index *index, const char *path); /** - * Add or update an index entry from a file in disk. + * Add or update an index entry from a file in disk + * + * The file `path` must be relative to the repository's + * working folder and must be readable. + * + * This method will fail in bare index instances. * * @param index an existing index object * @param path filename to add @@ -198,7 +193,55 @@ GIT_EXTERN(int) git_index_find(git_index *index, const char *path); GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); /** - * Remove an entry from the index + * Add or update an index entry from an in-memory struct + * + * A full copy (including the 'path' string) of the given + * 'source_entry' will be inserted on the index. + * + * @param index an existing index object + * @param source_entry new entry object + * @return 0 on success, otherwise an error code + */ +GIT_EXTERN(int) git_index_add2(git_index *index, const git_index_entry *source_entry); + +/** + * Add (append) an index entry from a file in disk + * + * A new entry will always be inserted into the index; + * if the index already contains an entry for such + * path, the old entry will **not** be replaced. + * + * The file `path` must be relative to the repository's + * working folder and must be readable. + * + * This method will fail in bare index instances. + * + * @param index an existing index object + * @param path filename to add + * @param stage stage for the entry + * @return 0 on success, otherwise an error code + */ +GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage); + +/** + * Add (append) an index entry from an in-memory struct + * + * A new entry will always be inserted into the index; + * if the index already contains an entry for the path + * in the `entry` struct, the old entry will **not** be + * replaced. + * + * A full copy (including the 'path' string) of the given + * 'source_entry' will be inserted on the index. + * + * @param index an existing index object + * @param source_entry new entry object + * @return 0 on success, otherwise an error code + */ +GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *source_entry); + +/** + * Remove an entry from the index * * @param index an existing index object * @param position position of the entry to remove @@ -206,18 +249,6 @@ GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); */ GIT_EXTERN(int) git_index_remove(git_index *index, int position); -/** - * Insert an entry into the index. - * A full copy (including the 'path' string) of the given - * 'source_entry' will be inserted on the index; if the index - * already contains an entry for the same path, the entry - * will be updated. - * - * @param index an existing index object - * @param source_entry new entry object - * @return 0 on success, otherwise an error code - */ -GIT_EXTERN(int) git_index_insert(git_index *index, const git_index_entry *source_entry); /** * Get a pointer to one of the entries in the index diff --git a/include/git2/thread-utils.h b/include/git2/thread-utils.h index fb8644b93..e26876bea 100644 --- a/include/git2/thread-utils.h +++ b/include/git2/thread-utils.h @@ -35,6 +35,7 @@ #if defined(__APPLE__) && defined(__MACH__) # undef GIT_TLS +# define GIT_TLS #elif defined(__GNUC__) || \ defined(__SUNPRO_C) || \ diff --git a/include/git2/types.h b/include/git2/types.h index 6123abc82..ab7dc523e 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -130,6 +130,12 @@ typedef struct git_treebuilder git_treebuilder; /** Memory representation of an index file. */ typedef struct git_index git_index; +/** Memory representation of a config file */ +typedef struct git_config git_config; + +/** A specific implementation of a config backend */ +typedef struct git_config_backend git_config_backend; + /** Time in a signature */ typedef struct git_time { git_time_t time; /** time in seconds from epoch */ diff --git a/src/backends/hiredis.c b/src/backends/hiredis.c index 707412bf6..2533a16da 100644 --- a/src/backends/hiredis.c +++ b/src/backends/hiredis.c @@ -53,7 +53,7 @@ int hiredis_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backe reply = redisCommand(backend->db, "HMGET %b %s %s", oid->id, GIT_OID_RAWSZ, "type", "size"); - if (reply->type == REDIS_REPLY_ARRAY) { + if (reply && reply->type == REDIS_REPLY_ARRAY) { if (reply->element[0]->type != REDIS_REPLY_NIL && reply->element[0]->type != REDIS_REPLY_NIL) { *type_p = (git_otype) atoi(reply->element[0]->str); @@ -83,7 +83,7 @@ int hiredis_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_o reply = redisCommand(backend->db, "HMGET %b %s %s %s", oid->id, GIT_OID_RAWSZ, "type", "size", "data"); - if (reply->type == REDIS_REPLY_ARRAY) { + if (reply && reply->type == REDIS_REPLY_ARRAY) { if (reply->element[0]->type != REDIS_REPLY_NIL && reply->element[1]->type != REDIS_REPLY_NIL && reply->element[2]->type != REDIS_REPLY_NIL) { @@ -118,7 +118,7 @@ int hiredis_backend__exists(git_odb_backend *_backend, const git_oid *oid) { found = 0; reply = redisCommand(backend->db, "exists %b", oid->id, GIT_OID_RAWSZ); - if (reply->type != REDIS_REPLY_NIL && reply->type != REDIS_REPLY_ERROR) + if (reply && reply->type != REDIS_REPLY_NIL && reply->type != REDIS_REPLY_ERROR) found = 1; @@ -144,7 +144,8 @@ int hiredis_backend__write(git_oid *id, git_odb_backend *_backend, const void *d "size %d " "data %b ", id->id, GIT_OID_RAWSZ, (int) type, len, data, len); - error = reply->type == REDIS_REPLY_ERROR ? GIT_ERROR : GIT_SUCCESS; + + error = (reply == NULL || reply->type == REDIS_REPLY_ERROR) ? GIT_ERROR : GIT_SUCCESS; freeReplyObject(reply); return error; diff --git a/src/blob.c b/src/blob.c index 5e3c22fbf..987169358 100644 --- a/src/blob.c +++ b/src/blob.c @@ -62,7 +62,7 @@ int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *b git_odb_stream *stream; if ((error = git_odb_open_wstream(&stream, repo->db, len, GIT_OBJ_BLOB)) < GIT_SUCCESS) - return error; + return git__rethrow(error, "Failed to create blob. Can't open write stream"); stream->write(stream, buffer, len); @@ -81,7 +81,7 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat git_odb_stream *stream; if (repo->path_workdir == NULL) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Failed to create blob. No workdir given"); git__joinpath(full_path, repo->path_workdir, path); @@ -106,7 +106,7 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat if (read_len < 0) { gitfo_close(fd); stream->free(stream); - return GIT_EOSERR; + return git__throw(GIT_EOSERR, "Failed to create blob. Can't read full file"); } stream->write(stream, buffer, read_len); diff --git a/src/cache.c b/src/cache.c index fd42e2c5b..433fc3d9c 100644 --- a/src/cache.c +++ b/src/cache.c @@ -29,10 +29,7 @@ #include "thread-utils.h" #include "cache.h" -#define GIT_CACHE_OPENADR 3 - - -void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) +int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) { size_t i; @@ -52,12 +49,15 @@ void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_p cache->free_obj = free_ptr; cache->nodes = git__malloc((size + 1) * sizeof(cache_node)); + if (cache->nodes == NULL) + return GIT_ENOMEM; for (i = 0; i < (size + 1); ++i) { git_mutex_init(&cache->nodes[i].lock); cache->nodes[i].ptr = NULL; - cache->nodes[i].lru = 0; } + + return GIT_SUCCESS; } void git_cache_free(git_cache *cache) @@ -77,85 +77,53 @@ void git_cache_free(git_cache *cache) void *git_cache_get(git_cache *cache, const git_oid *oid) { const uint32_t *hash; - size_t i, pos, found = 0; cache_node *node = NULL; + void *result = NULL; hash = (const uint32_t *)oid->id; + node = &cache->nodes[hash[0] & cache->size_mask]; - for (i = 0; !found && i < GIT_CACHE_OPENADR; ++i) { - pos = hash[i] & cache->size_mask; - node = &cache->nodes[pos]; - - git_mutex_lock(&node->lock); - { - if (node->ptr && git_cached_obj_compare(node->ptr, oid) == 0) { - git_cached_obj_incref(node->ptr); - node->lru = ++cache->lru_count; - found = 1; - } + git_mutex_lock(&node->lock); + { + if (node->ptr && git_cached_obj_compare(node->ptr, oid) == 0) { + git_cached_obj_incref(node->ptr); + result = node->ptr; } - git_mutex_unlock(&node->lock); } + git_mutex_unlock(&node->lock); - - return found ? node->ptr : NULL; + return result; } void *git_cache_try_store(git_cache *cache, void *entry) { - cache_node *nodes[GIT_CACHE_OPENADR], *lru_node; const uint32_t *hash; const git_oid *oid; - size_t i; + cache_node *node = NULL; oid = &((git_cached_obj*)entry)->oid; hash = (const uint32_t *)oid->id; + node = &cache->nodes[hash[0] & cache->size_mask]; /* increase the refcount on this object, because * the cache now owns it */ git_cached_obj_incref(entry); + git_mutex_lock(&node->lock); - for (i = 0; i < GIT_CACHE_OPENADR; ++i) { - size_t pos = hash[i] & cache->size_mask; - - nodes[i] = &cache->nodes[pos]; - git_mutex_lock(&nodes[i]->lock); - } - - lru_node = nodes[0]; - - for (i = 0; i < GIT_CACHE_OPENADR; ++i) { - - if (nodes[i]->ptr == NULL) { - nodes[i]->ptr = entry; - nodes[i]->lru = ++cache->lru_count; - break; - } else if (git_cached_obj_compare(nodes[i]->ptr, oid) == 0) { - git_cached_obj_decref(entry, cache->free_obj); - entry = nodes[i]->ptr; - nodes[i]->lru = ++cache->lru_count; - break; - } - - if (nodes[i]->lru < lru_node->lru) - lru_node = nodes[i]; - } - - if (i == GIT_CACHE_OPENADR) { - void *old_entry = lru_node->ptr; - assert(old_entry); - - git_cached_obj_decref(old_entry, cache->free_obj); - lru_node->ptr = entry; - lru_node->lru = ++cache->lru_count; + if (node->ptr == NULL) { + node->ptr = entry; + } else if (git_cached_obj_compare(node->ptr, oid) == 0) { + git_cached_obj_decref(entry, cache->free_obj); + entry = node->ptr; + } else { + git_cached_obj_decref(node->ptr, cache->free_obj); + node->ptr = entry; } /* increase the refcount again, because we are * returning it to the user */ git_cached_obj_incref(entry); - - for (i = 0; i < GIT_CACHE_OPENADR; ++i) - git_mutex_unlock(&nodes[i]->lock); + git_mutex_unlock(&node->lock); return entry; } diff --git a/src/cache.h b/src/cache.h index 975aaff7e..4794dea3a 100644 --- a/src/cache.h +++ b/src/cache.h @@ -19,7 +19,6 @@ typedef struct { typedef struct { git_cached_obj *ptr; git_mutex lock; - unsigned int lru; } cache_node; typedef struct { @@ -31,7 +30,7 @@ typedef struct { } git_cache; -void git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr); +int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr); void git_cache_free(git_cache *cache); void *git_cache_try_store(git_cache *cache, void *entry); diff --git a/src/commit.c b/src/commit.c index 9621703c3..54d7a47fe 100644 --- a/src/commit.c +++ b/src/commit.c @@ -224,9 +224,18 @@ int git_commit_create( if (error < GIT_SUCCESS) return error; - if (git_reference_type(head) == GIT_REF_SYMBOLIC) { - if ((error = git_reference_resolve(&head, head)) < GIT_SUCCESS) + error = git_reference_resolve(&head, head); + if (error < GIT_SUCCESS) { + if (error != GIT_ENOTFOUND) return error; + /* + * The target of the reference was not found. This can happen + * just after a repository has been initialized (the master + * branch doesn't exist yet, as it doesn't have anything to + * point to) or after an orphan checkout, so if the target + * branch doesn't exist yet, create it and return. + */ + return git_reference_create_oid_f(&head, repo, git_reference_target(head), oid); } error = git_reference_set_oid(head, oid); @@ -277,7 +286,7 @@ int commit_parse_buffer(git_commit *commit, const void *data, size_t len) if (buffer < buffer_end) { const char *line_end; - size_t message_len = buffer_end - buffer; + size_t message_len; /* Long message */ message_len = buffer_end - buffer; diff --git a/src/common.h b/src/common.h index 5ad878e26..f4f11fd2f 100644 --- a/src/common.h +++ b/src/common.h @@ -50,10 +50,14 @@ typedef SSIZE_T ssize_t; #include "git2/common.h" #include "git2/types.h" -#include "util.h" +#include "git2/errors.h" #include "thread-utils.h" #include "bswap.h" #define GIT_PATH_MAX 4096 +extern int git__throw(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); +extern int git__rethrow(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); + +#include "util.h" #endif /* INCLUDE_common_h__ */ diff --git a/src/config.c b/src/config.c new file mode 100644 index 000000000..cd0a73ccd --- /dev/null +++ b/src/config.c @@ -0,0 +1,323 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "fileops.h" +#include "hashtable.h" +#include "config.h" +#include "git2/config_backend.h" +#include "vector.h" + +#include + +typedef struct { + git_config_backend *backend; + int priority; +} backend_internal; + +int git_config_open_bare(git_config **out, const char *path) +{ + git_config_backend *backend = NULL; + git_config *cfg = NULL; + int error = GIT_SUCCESS; + + error = git_config_new(&cfg); + if (error < GIT_SUCCESS) + goto error; + + error = git_config_backend_file(&backend, path); + if (error < GIT_SUCCESS) + goto error; + + error = git_config_add_backend(cfg, backend, 1); + if (error < GIT_SUCCESS) + goto error; + + error = backend->open(backend); + if (error < GIT_SUCCESS) + goto error; + + *out = cfg; + + return error; + + error: + if(backend) + backend->free(backend); + + return error; +} + +void git_config_free(git_config *cfg) +{ + unsigned int i; + git_config_backend *backend; + backend_internal *internal; + + for(i = 0; i < cfg->backends.length; ++i){ + internal = git_vector_get(&cfg->backends, i); + backend = internal->backend; + backend->free(backend); + free(internal); + } + + git_vector_free(&cfg->backends); + free(cfg); +} + +static int config_backend_cmp(const void *a, const void *b) +{ + const backend_internal *bk_a = *(const backend_internal **)(a); + const backend_internal *bk_b = *(const backend_internal **)(b); + + return bk_b->priority - bk_a->priority; +} + +int git_config_new(git_config **out) +{ + git_config *cfg; + + cfg = git__malloc(sizeof(git_config)); + if (cfg == NULL) + return GIT_ENOMEM; + + memset(cfg, 0x0, sizeof(git_config)); + + if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) { + free(cfg); + return GIT_ENOMEM; + } + + *out = cfg; + + return GIT_SUCCESS; +} + +int git_config_add_backend(git_config *cfg, git_config_backend *backend, int priority) +{ + backend_internal *internal; + + assert(cfg && backend); + + internal = git__malloc(sizeof(backend_internal)); + if (internal == NULL) + return GIT_ENOMEM; + + internal->backend = backend; + internal->priority = priority; + + if (git_vector_insert(&cfg->backends, internal) < 0) { + free(internal); + return GIT_ENOMEM; + } + + git_vector_sort(&cfg->backends); + internal->backend->cfg = cfg; + + return GIT_SUCCESS; +} + +/* + * Loop over all the variables + */ + +int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *data) +{ + int ret = GIT_SUCCESS; + unsigned int i; + backend_internal *internal; + git_config_backend *backend; + + for(i = 0; i < cfg->backends.length && ret == 0; ++i) { + internal = git_vector_get(&cfg->backends, i); + backend = internal->backend; + ret = backend->foreach(backend, fn, data); + } + + return ret; +} + + +/************** + * Setters + **************/ + +/* + * Internal function to actually set the string value of a variable + */ + +int git_config_set_long(git_config *cfg, const char *name, long int value) +{ + char str_value[5]; /* Most numbers should fit in here */ + int buf_len = sizeof(str_value), ret; + char *help_buf = NULL; + + if ((ret = snprintf(str_value, buf_len, "%ld", value)) >= buf_len - 1){ + /* The number is too large, we need to allocate more memory */ + buf_len = ret + 1; + help_buf = git__malloc(buf_len); + snprintf(help_buf, buf_len, "%ld", value); + ret = git_config_set_string(cfg, name, help_buf); + free(help_buf); + } else { + ret = git_config_set_string(cfg, name, str_value); + } + + return ret; +} + +int git_config_set_int(git_config *cfg, const char *name, int value) +{ + return git_config_set_long(cfg, name, value); +} + +int git_config_set_bool(git_config *cfg, const char *name, int value) +{ + const char *str_value; + + if (value == 0) + str_value = "false"; + else + str_value = "true"; + + return git_config_set_string(cfg, name, str_value); +} + +int git_config_set_string(git_config *cfg, const char *name, const char *value) +{ + backend_internal *internal; + git_config_backend *backend; + + assert(cfg->backends.length > 0); + + internal = git_vector_get(&cfg->backends, 0); + backend = internal->backend; + + return backend->set(backend, name, value); +} + +/*********** + * Getters + ***********/ + +int git_config_get_long(git_config *cfg, const char *name, long int *out) +{ + const char *value, *num_end; + int ret; + long int num; + + ret = git_config_get_string(cfg, name, &value); + if (ret < GIT_SUCCESS) + return ret; + + ret = git__strtol32(&num, value, &num_end, 0); + if (ret < GIT_SUCCESS) + return ret; + + switch (*num_end) { + case '\0': + break; + case 'k': + case 'K': + num *= 1024; + break; + case 'm': + case 'M': + num *= 1024 * 1024; + break; + case 'g': + case 'G': + num *= 1024 * 1024 * 1024; + break; + default: + return GIT_EINVALIDTYPE; + } + + *out = num; + + return GIT_SUCCESS; +} + +int git_config_get_int(git_config *cfg, const char *name, int *out) +{ + long int tmp; + int ret; + + ret = git_config_get_long(cfg, name, &tmp); + + *out = (int) tmp; + + return ret; +} + +int git_config_get_bool(git_config *cfg, const char *name, int *out) +{ + const char *value; + int error = GIT_SUCCESS; + + error = git_config_get_string(cfg, name, &value); + if (error < GIT_SUCCESS) + return error; + + /* A missing value means true */ + if (value == NULL) { + *out = 1; + return GIT_SUCCESS; + } + + if (!strcasecmp(value, "true") || + !strcasecmp(value, "yes") || + !strcasecmp(value, "on")) { + *out = 1; + return GIT_SUCCESS; + } + if (!strcasecmp(value, "false") || + !strcasecmp(value, "no") || + !strcasecmp(value, "off")) { + *out = 0; + return GIT_SUCCESS; + } + + /* Try to parse it as an integer */ + error = git_config_get_int(cfg, name, out); + if (error == GIT_SUCCESS) + *out = !!(*out); + + return error; +} + +int git_config_get_string(git_config *cfg, const char *name, const char **out) +{ + backend_internal *internal; + git_config_backend *backend; + + assert(cfg->backends.length > 0); + + internal = git_vector_get(&cfg->backends, 0); + backend = internal->backend; + + return backend->get(backend, name, out); +} + diff --git a/src/config.h b/src/config.h new file mode 100644 index 000000000..a811fd850 --- /dev/null +++ b/src/config.h @@ -0,0 +1,12 @@ +#ifndef INCLUDE_config_h__ +#define INCLUDE_config_h__ + +#include "git2.h" +#include "git2/config.h" +#include "vector.h" + +struct git_config { + git_vector backends; +}; + +#endif diff --git a/src/config_file.c b/src/config_file.c new file mode 100644 index 000000000..37bb2794e --- /dev/null +++ b/src/config_file.c @@ -0,0 +1,1011 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "config.h" +#include "fileops.h" +#include "git2/config_backend.h" +#include "git2/types.h" + +#include + +typedef struct cvar_t { + struct cvar_t *next; + char *section; + char *name; + char *value; +} cvar_t; + +typedef struct { + struct cvar_t *head; + struct cvar_t *tail; +} cvar_t_list; + +#define CVAR_LIST_HEAD(list) ((list)->head) + +#define CVAR_LIST_TAIL(list) ((list)->tail) + +#define CVAR_LIST_NEXT(var) ((var)->next) + +#define CVAR_LIST_EMPTY(list) ((list)->head == NULL) + +#define CVAR_LIST_APPEND(list, var) do {\ + if (CVAR_LIST_EMPTY(list)) {\ + CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\ + } else {\ + CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\ + CVAR_LIST_TAIL(list) = var;\ + }\ +} while(0) + +#define CVAR_LIST_REMOVE_HEAD(list) do {\ + CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\ +} while(0) + +#define CVAR_LIST_REMOVE_AFTER(var) do {\ + CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\ +} while(0) + +#define CVAR_LIST_FOREACH(list, iter)\ + for ((iter) = CVAR_LIST_HEAD(list);\ + (iter) != NULL;\ + (iter) = CVAR_LIST_NEXT(iter)) + +/* + * Inspired by the FreeBSD functions + */ +#define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\ + for ((iter) = CVAR_LIST_HEAD(vars);\ + (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\ + (iter) = (tmp)) + + + +typedef struct { + git_config_backend parent; + + cvar_t_list var_list; + + struct { + gitfo_buf buffer; + char *read_ptr; + int line_number; + int eof; + } reader; + + char *file_path; +} file_backend; + +static int config_parse(file_backend *cfg_file); +static int parse_variable(file_backend *cfg, char **var_name, char **var_value); + +static void cvar_free(cvar_t *var) +{ + if (var == NULL) + return; + + free(var->section); + free(var->name); + free(var->value); + free(var); +} + +static void cvar_list_free(cvar_t_list *list) +{ + cvar_t *cur; + + while (!CVAR_LIST_EMPTY(list)) { + cur = CVAR_LIST_HEAD(list); + CVAR_LIST_REMOVE_HEAD(list); + cvar_free(cur); + } +} + +/* + * Compare two strings according to the git section-subsection + * rules. The order of the strings is important because local is + * assumed to have the internal format (only the section name and with + * case information) and input the normalized one (only dots, no case + * information). + */ +static int cvar_match_section(const char *local, const char *input) +{ + char *first_dot, *last_dot; + char *local_sp = strchr(local, ' '); + int comparison_len; + + /* + * If the local section name doesn't contain a space, then we can + * just do a case-insensitive compare. + */ + if (local_sp == NULL) + return !strncasecmp(local, input, strlen(local)); + + /* + * From here onwards, there is a space diving the section and the + * subsection. Anything before the space in local is + * case-insensitive. + */ + if (strncasecmp(local, input, local_sp - local)) + return 0; + + /* + * We compare starting from the first character after the + * quotation marks, which is two characters beyond the space. For + * the input, we start one character beyond the dot. If the names + * have different lengths, then we can fail early, as we know they + * can't be the same. + * The length is given by the length between the quotation marks. + */ + + first_dot = strchr(input, '.'); + last_dot = strrchr(input, '.'); + comparison_len = strlen(local_sp + 2) - 1; + + if (last_dot == first_dot || last_dot - first_dot - 1 != comparison_len) + return 0; + + return !strncmp(local_sp + 2, first_dot + 1, comparison_len); +} + +static int cvar_match_name(const cvar_t *var, const char *str) +{ + const char *name_start; + + if (!cvar_match_section(var->section, str)) { + return 0; + } + /* Early exit if the lengths are different */ + name_start = strrchr(str, '.') + 1; + if (strlen(var->name) != strlen(name_start)) + return 0; + + return !strcasecmp(var->name, name_start); +} + +static cvar_t *cvar_list_find(cvar_t_list *list, const char *name) +{ + cvar_t *iter; + + CVAR_LIST_FOREACH (list, iter) { + if (cvar_match_name(iter, name)) + return iter; + } + + return NULL; +} + +static int cvar_name_normalize(const char *input, char **output) +{ + char *input_sp = strchr(input, ' '); + char *quote, *str; + int i; + + /* We need to make a copy anyway */ + str = git__strdup(input); + if (str == NULL) + return GIT_ENOMEM; + + *output = str; + + /* If there aren't any spaces, we don't need to do anything */ + if (input_sp == NULL) + return GIT_SUCCESS; + + /* + * If there are spaces, we replace the space by a dot, move the + * variable name so that the dot before it replaces the last + * quotation mark and repeat so that the first quotation mark + * disappears. + */ + str[input_sp - input] = '.'; + + for (i = 0; i < 2; ++i) { + quote = strrchr(str, '"'); + memmove(quote, quote + 1, strlen(quote)); + } + + return GIT_SUCCESS; +} + +static int config_open(git_config_backend *cfg) +{ + int error; + file_backend *b = (file_backend *)cfg; + + error = gitfo_read_file(&b->reader.buffer, b->file_path); + if(error < GIT_SUCCESS) + goto cleanup; + + error = config_parse(b); + if (error < GIT_SUCCESS) + goto cleanup; + + gitfo_free_buf(&b->reader.buffer); + + return error; + + cleanup: + cvar_list_free(&b->var_list); + gitfo_free_buf(&b->reader.buffer); + free(cfg); + + return error; +} + +static void backend_free(git_config_backend *_backend) +{ + file_backend *backend = (file_backend *)_backend; + + if (backend == NULL) + return; + + free(backend->file_path); + cvar_list_free(&backend->var_list); + + free(backend); +} + +static int file_foreach(git_config_backend *backend, int (*fn)(const char *, void *), void *data) +{ + int ret = GIT_SUCCESS; + cvar_t *var; + char *normalized; + file_backend *b = (file_backend *)backend; + + CVAR_LIST_FOREACH(&b->var_list, var) { + ret = cvar_name_normalize(var->name, &normalized); + if (ret < GIT_SUCCESS) + return ret; + + ret = fn(normalized, data); + free(normalized); + if (ret) + break; + } + + return ret; +} + +static int config_set(git_config_backend *cfg, const char *name, const char *value) +{ + cvar_t *var = NULL; + cvar_t *existing = NULL; + int error = GIT_SUCCESS; + const char *last_dot; + file_backend *b = (file_backend *)cfg; + + /* + * If it already exists, we just need to update its value. + */ + existing = cvar_list_find(&b->var_list, name); + if (existing != NULL) { + char *tmp = value ? git__strdup(value) : NULL; + if (tmp == NULL && value != NULL) + return GIT_ENOMEM; + + free(existing->value); + existing->value = tmp; + + return GIT_SUCCESS; + } + + /* + * Otherwise, create it and stick it at the end of the queue. + */ + + last_dot = strrchr(name, '.'); + if (last_dot == NULL) { + return git__throw(GIT_EINVALIDTYPE, "Variables without section aren't allowed"); + } + + var = git__malloc(sizeof(cvar_t)); + if (var == NULL) + return GIT_ENOMEM; + + memset(var, 0x0, sizeof(cvar_t)); + + var->section = git__strndup(name, last_dot - name); + if (var->section == NULL) { + error = GIT_ENOMEM; + goto out; + } + + var->name = git__strdup(last_dot + 1); + if (var->name == NULL) { + error = GIT_ENOMEM; + goto out; + } + + var->value = value ? git__strdup(value) : NULL; + if (var->value == NULL && value != NULL) { + error = GIT_ENOMEM; + goto out; + } + + CVAR_LIST_APPEND(&b->var_list, var); + + out: + if (error < GIT_SUCCESS) + cvar_free(var); + + return error; +} + +/* + * Internal function that actually gets the value in string form + */ +static int config_get(git_config_backend *cfg, const char *name, const char **out) +{ + cvar_t *var; + int error = GIT_SUCCESS; + file_backend *b = (file_backend *)cfg; + + var = cvar_list_find(&b->var_list, name); + + if (var == NULL) + return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name); + + *out = var->value; + + return error; +} + +int git_config_backend_file(git_config_backend **out, const char *path) +{ + file_backend *backend; + + backend = git__malloc(sizeof(file_backend)); + if (backend == NULL) + return GIT_ENOMEM; + + memset(backend, 0x0, sizeof(file_backend)); + + backend->file_path = git__strdup(path); + if (backend->file_path == NULL) { + free(backend); + return GIT_ENOMEM; + } + + backend->parent.open = config_open; + backend->parent.get = config_get; + backend->parent.set = config_set; + backend->parent.foreach = file_foreach; + backend->parent.free = backend_free; + + *out = (git_config_backend *)backend; + + return GIT_SUCCESS; +} + +static int cfg_getchar_raw(file_backend *cfg) +{ + int c; + + c = *cfg->reader.read_ptr++; + + /* + Win 32 line breaks: if we find a \r\n sequence, + return only the \n as a newline + */ + if (c == '\r' && *cfg->reader.read_ptr == '\n') { + cfg->reader.read_ptr++; + c = '\n'; + } + + if (c == '\n') + cfg->reader.line_number++; + + if (c == 0) { + cfg->reader.eof = 1; + c = '\n'; + } + + return c; +} + +#define SKIP_WHITESPACE (1 << 1) +#define SKIP_COMMENTS (1 << 2) + +static int cfg_getchar(file_backend *cfg_file, int flags) +{ + const int skip_whitespace = (flags & SKIP_WHITESPACE); + const int skip_comments = (flags & SKIP_COMMENTS); + int c; + + assert(cfg_file->reader.read_ptr); + + do c = cfg_getchar_raw(cfg_file); + while (skip_whitespace && isspace(c)); + + if (skip_comments && (c == '#' || c == ';')) { + do c = cfg_getchar_raw(cfg_file); + while (c != '\n'); + } + + return c; +} + +/* + * Read the next char, but don't move the reading pointer. + */ +static int cfg_peek(file_backend *cfg, int flags) +{ + void *old_read_ptr; + int old_lineno, old_eof; + int ret; + + assert(cfg->reader.read_ptr); + + old_read_ptr = cfg->reader.read_ptr; + old_lineno = cfg->reader.line_number; + old_eof = cfg->reader.eof; + + ret = cfg_getchar(cfg, flags); + + cfg->reader.read_ptr = old_read_ptr; + cfg->reader.line_number = old_lineno; + cfg->reader.eof = old_eof; + + return ret; +} + +static const char *LINEBREAK_UNIX = "\\\n"; +static const char *LINEBREAK_WIN32 = "\\\r\n"; + +static int is_linebreak(const char *pos) +{ + return memcmp(pos - 1, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0 || + memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0; +} + +/* + * Read and consume a line, returning it in newly-allocated memory. + */ +static char *cfg_readline(file_backend *cfg) +{ + char *line = NULL; + char *line_src, *line_end; + int line_len; + + line_src = cfg->reader.read_ptr; + line_end = strchr(line_src, '\n'); + + /* no newline at EOF */ + if (line_end == NULL) + line_end = strchr(line_src, 0); + else + while (is_linebreak(line_end)) + line_end = strchr(line_end + 1, '\n'); + + + while (line_src < line_end && isspace(*line_src)) + line_src++; + + line = (char *)git__malloc((size_t)(line_end - line_src) + 1); + if (line == NULL) + return NULL; + + line_len = 0; + while (line_src < line_end) { + + if (memcmp(line_src, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0) { + line_src += sizeof(LINEBREAK_UNIX); + continue; + } + + if (memcmp(line_src, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0) { + line_src += sizeof(LINEBREAK_WIN32); + continue; + } + + line[line_len++] = *line_src++; + } + + line[line_len] = '\0'; + + while (--line_len >= 0 && isspace(line[line_len])) + line[line_len] = '\0'; + + if (*line_end == '\n') + line_end++; + + if (*line_end == '\0') + cfg->reader.eof = 1; + + cfg->reader.line_number++; + cfg->reader.read_ptr = line_end; + + return line; +} + +/* + * Consume a line, without storing it anywhere + */ +void cfg_consume_line(file_backend *cfg) +{ + char *line_start, *line_end; + + line_start = cfg->reader.read_ptr; + line_end = strchr(line_start, '\n'); + /* No newline at EOF */ + if(line_end == NULL){ + line_end = strchr(line_start, '\0'); + } + + if (*line_end == '\n') + line_end++; + + if (*line_end == '\0') + cfg->reader.eof = 1; + + cfg->reader.line_number++; + cfg->reader.read_ptr = line_end; +} + +static inline int config_keychar(int c) +{ + return isalnum(c) || c == '-'; +} + +static int parse_section_header_ext(const char *line, const char *base_name, char **section_name) +{ + int buf_len, total_len, pos, rpos; + int c, ret; + char *subsection, *first_quote, *last_quote; + int error = GIT_SUCCESS; + int quote_marks; + /* + * base_name is what came before the space. We should be at the + * first quotation mark, except for now, line isn't being kept in + * sync so we only really use it to calculate the length. + */ + + first_quote = strchr(line, '"'); + last_quote = strrchr(line, '"'); + + if (last_quote - first_quote == 0) + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. There is no final quotation mark"); + + buf_len = last_quote - first_quote + 2; + + subsection = git__malloc(buf_len + 2); + if (subsection == NULL) + return GIT_ENOMEM; + + pos = 0; + rpos = 0; + quote_marks = 0; + + line = first_quote; + c = line[rpos++]; + + /* + * At the end of each iteration, whatever is stored in c will be + * added to the string. In case of error, jump to out + */ + do { + switch (c) { + case '"': + if (quote_marks++ >= 2) + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Too many quotes"); + break; + case '\\': + c = line[rpos++]; + switch (c) { + case '"': + case '\\': + break; + default: + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Unsupported escape char \\%c", c); + goto out; + } + default: + break; + } + + subsection[pos++] = c; + } while ((c = line[rpos++]) != ']'); + + subsection[pos] = '\0'; + + total_len = strlen(base_name) + strlen(subsection) + 2; + *section_name = git__malloc(total_len); + if (*section_name == NULL) { + error = GIT_ENOMEM; + goto out; + } + + ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection); + if (ret >= total_len) { + /* If this fails, we've checked the length wrong */ + error = git__throw(GIT_ERROR, "Failed to parse ext header. Wrong total lenght calculation"); + goto out; + } else if (ret < 0) { + error = git__throw(GIT_EOSERR, "Failed to parse ext header. OS error: %s", strerror(errno)); + goto out; + } + + git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name); + + out: + free(subsection); + + return error; +} + +static int parse_section_header(file_backend *cfg, char **section_out) +{ + char *name, *name_end; + int name_length, c, pos; + int error = GIT_SUCCESS; + char *line; + + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; + + /* find the end of the variable's name */ + name_end = strchr(line, ']'); + if (name_end == NULL) + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Can't find header name end"); + + name = (char *)git__malloc((size_t)(name_end - line) + 1); + if (name == NULL) + return GIT_ENOMEM; + + name_length = 0; + pos = 0; + + /* Make sure we were given a section header */ + c = line[pos++]; + if (c != '[') { + error = git__throw(GIT_ERROR, "Failed to parse header. Didn't get section header. This is a bug"); + goto error; + } + + c = line[pos++]; + + do { + if (cfg->reader.eof){ + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Config file ended unexpectedly"); + goto error; + } + + if (isspace(c)){ + name[name_length] = '\0'; + error = parse_section_header_ext(line, name, section_out); + free(line); + free(name); + return error; + } + + if (!config_keychar(c) && c != '.') { + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Wrong format on header"); + goto error; + } + + name[name_length++] = tolower(c); + + } while ((c = line[pos++]) != ']'); + + name[name_length] = 0; + free(line); + git__strtolower(name); + *section_out = name; + return GIT_SUCCESS; + +error: + free(line); + free(name); + return error; +} + +static int skip_bom(file_backend *cfg) +{ + static const char *utf8_bom = "\xef\xbb\xbf"; + + if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) + cfg->reader.read_ptr += sizeof(utf8_bom); + + /* TODO: the reference implementation does pretty stupid + shit with the BoM + */ + + return GIT_SUCCESS; +} + +/* + (* basic types *) + digit = "0".."9" + integer = digit { digit } + alphabet = "a".."z" + "A" .. "Z" + + section_char = alphabet | "." | "-" + extension_char = (* any character except newline *) + any_char = (* any character *) + variable_char = "alphabet" | "-" + + + (* actual grammar *) + config = { section } + + section = header { definition } + + header = "[" section [subsection | subsection_ext] "]" + + subsection = "." section + subsection_ext = "\"" extension "\"" + + section = section_char { section_char } + extension = extension_char { extension_char } + + definition = variable_name ["=" variable_value] "\n" + + variable_name = variable_char { variable_char } + variable_value = string | boolean | integer + + string = quoted_string | plain_string + quoted_string = "\"" plain_string "\"" + plain_string = { any_char } + + boolean = boolean_true | boolean_false + boolean_true = "yes" | "1" | "true" | "on" + boolean_false = "no" | "0" | "false" | "off" +*/ + +static void strip_comments(char *line) +{ + int quote_count = 0; + char *ptr; + + for (ptr = line; *ptr; ++ptr) { + if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') + quote_count++; + + if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) { + ptr[0] = '\0'; + break; + } + } + + if (isspace(ptr[-1])) { + /* TODO skip whitespace */ + } +} + +static int config_parse(file_backend *cfg_file) +{ + int error = GIT_SUCCESS, c; + char *current_section = NULL; + char *var_name; + char *var_value; + cvar_t *var; + + /* Initialise the reading position */ + cfg_file->reader.read_ptr = cfg_file->reader.buffer.data; + cfg_file->reader.eof = 0; + + skip_bom(cfg_file); + + while (error == GIT_SUCCESS && !cfg_file->reader.eof) { + + c = cfg_peek(cfg_file, SKIP_WHITESPACE); + + switch (c) { + case '\0': /* We've arrived at the end of the file */ + break; + + case '[': /* section header, new section begins */ + free(current_section); + error = parse_section_header(cfg_file, ¤t_section); + break; + + case ';': + case '#': + cfg_consume_line(cfg_file); + break; + + default: /* assume variable declaration */ + error = parse_variable(cfg_file, &var_name, &var_value); + + if (error < GIT_SUCCESS) + break; + + var = malloc(sizeof(cvar_t)); + if (var == NULL) { + error = GIT_ENOMEM; + break; + } + + memset(var, 0x0, sizeof(cvar_t)); + + var->section = git__strdup(current_section); + if (var->section == NULL) { + error = GIT_ENOMEM; + free(var); + break; + } + + var->name = var_name; + var->value = var_value; + git__strtolower(var->name); + + CVAR_LIST_APPEND(&cfg_file->var_list, var); + + break; + } + } + + if (current_section) + free(current_section); + + return error; +} + +static int is_multiline_var(const char *str) +{ + char *end = strrchr(str, '\0') - 1; + + while (isspace(*end)) + --end; + + return *end == '\\'; +} + +static int parse_multiline_variable(file_backend *cfg, const char *first, char **out) +{ + char *line = NULL, *end; + int error = GIT_SUCCESS, len, ret; + char *buf; + + /* Check that the next line exists */ + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; + + /* We've reached the end of the file, there is input missing */ + if (line[0] == '\0') { + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse multiline var. File ended unexpectedly"); + goto out; + } + + strip_comments(line); + + /* If it was just a comment, pretend it didn't exist */ + if (line[0] == '\0') { + error = parse_multiline_variable(cfg, first, out); + goto out; + } + + /* Find the continuation character '\' and strip the whitespace */ + end = strrchr(first, '\\'); + while (isspace(end[-1])) + --end; + + *end = '\0'; /* Terminate the string here */ + + len = strlen(first) + strlen(line) + 2; + buf = git__malloc(len); + if (buf == NULL) { + error = GIT_ENOMEM; + goto out; + } + + ret = snprintf(buf, len, "%s %s", first, line); + if (ret < 0) { + error = git__throw(GIT_EOSERR, "Failed to parse multiline var. Failed to put together two lines. OS err: %s", strerror(errno)); + free(buf); + goto out; + } + + /* + * If we need to continue reading the next line, pretend + * everything we've read up to now was in one line and call + * ourselves. + */ + if (is_multiline_var(buf)) { + char *final_val; + error = parse_multiline_variable(cfg, buf, &final_val); + free(buf); + buf = final_val; + } + + *out = buf; + + out: + free(line); + return error; +} + +static int parse_variable(file_backend *cfg, char **var_name, char **var_value) +{ + char *tmp; + int error = GIT_SUCCESS; + const char *var_end = NULL; + const char *value_start = NULL; + char *line; + + line = cfg_readline(cfg); + if (line == NULL) + return GIT_ENOMEM; + + strip_comments(line); + + var_end = strchr(line, '='); + + if (var_end == NULL) + var_end = strchr(line, '\0'); + else + value_start = var_end + 1; + + if (isspace(var_end[-1])) { + do var_end--; + while (isspace(var_end[0])); + } + + tmp = git__strndup(line, var_end - line + 1); + if (tmp == NULL) { + error = GIT_ENOMEM; + goto out; + } + + *var_name = tmp; + + /* + * Now, let's try to parse the value + */ + if (value_start != NULL) { + + while (isspace(value_start[0])) + value_start++; + + if (value_start[0] == '\0') + goto out; + + if (is_multiline_var(value_start)) { + error = parse_multiline_variable(cfg, value_start, var_value); + if (error < GIT_SUCCESS) + free(*var_name); + goto out; + } + + tmp = strdup(value_start); + if (tmp == NULL) { + free(*var_name); + error = GIT_ENOMEM; + goto out; + } + + *var_value = tmp; + } else { + /* If thre is no value, boolean true is assumed */ + *var_value = NULL; + } + + out: + free(line); + return error; +} diff --git a/src/errors.c b/src/errors.c index c3a495cc9..1fb68ee65 100644 --- a/src/errors.c +++ b/src/errors.c @@ -1,6 +1,35 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ #include "common.h" +#include "git2/thread-utils.h" /* for GIT_TLS */ #include "thread-utils.h" /* for GIT_TLS */ +#include + +static GIT_TLS char g_last_error[1024]; + static struct { int num; const char *str; @@ -13,7 +42,7 @@ static struct { {GIT_EOBJTYPE, "The specified object is of invalid type"}, {GIT_EOBJCORRUPTED, "The specified object has its data corrupted"}, {GIT_ENOTAREPO, "The specified repository is invalid"}, - {GIT_EINVALIDTYPE, "The object type is invalid or doesn't match"}, + {GIT_EINVALIDTYPE, "The object or config variable type is invalid or doesn't match"}, {GIT_EMISSINGOBJDATA, "The object cannot be written that because it's missing internal data"}, {GIT_EPACKCORRUPTED, "The packfile for the ODB is corrupted"}, {GIT_EFLOCKFAIL, "Failed to adquire or release a file lock"}, @@ -46,3 +75,38 @@ const char *git_strerror(int num) return "Unknown error"; } + +int git__rethrow(int error, const char *msg, ...) +{ + char new_error[1024]; + char *old_error = NULL; + + va_list va; + + va_start(va, msg); + vsnprintf(new_error, sizeof(new_error), msg, va); + va_end(va); + + old_error = strdup(g_last_error); + snprintf(g_last_error, sizeof(g_last_error), "%s \n - %s", new_error, old_error); + free(old_error); + + return error; +} + +int git__throw(int error, const char *msg, ...) +{ + va_list va; + + va_start(va, msg); + vsnprintf(g_last_error, sizeof(g_last_error), msg, va); + va_end(va); + + return error; +} + +const char *git_lasterror(void) +{ + return g_last_error; +} + diff --git a/src/fileops.c b/src/fileops.c index 5dd4a3806..45dd8f444 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -10,7 +10,7 @@ int gitfo_mkdir_2file(const char *file_path) error = git__dirname_r(target_folder_path, sizeof(target_folder_path), file_path); if (error < GIT_SUCCESS) - return error; + return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path); /* Does the containing folder exist? */ if (gitfo_isdir(target_folder_path)) { @@ -19,7 +19,7 @@ int gitfo_mkdir_2file(const char *file_path) /* Let's create the tree structure */ error = gitfo_mkdir_recurs(target_folder_path, mode); if (error < GIT_SUCCESS) - return error; + return error; /* The callee already takes care of setting the correct error message. */ } return GIT_SUCCESS; @@ -164,19 +164,19 @@ int gitfo_read_file(gitfo_buf *obj, const char *path) if (((size = gitfo_size(fd)) < 0) || !git__is_sizet(size+1)) { gitfo_close(fd); - return GIT_ERROR; + return git__throw(GIT_ERROR, "Failed to read file `%s`. Either an error occured while calculating its size or the file is too large", path); } len = (size_t) size; if ((buff = git__malloc(len + 1)) == NULL) { gitfo_close(fd); - return GIT_ERROR; + return GIT_ENOMEM; } if (gitfo_read(fd, buff, len) < 0) { gitfo_close(fd); free(buff); - return GIT_ERROR; + return git__throw(GIT_ERROR, "Failed to read file `%s`", path); } buff[len] = '\0'; @@ -197,13 +197,15 @@ void gitfo_free_buf(gitfo_buf *obj) int gitfo_mv(const char *from, const char *to) { + int error; + #ifdef GIT_WIN32 /* * Win32 POSIX compilance my ass. If the destination * file exists, the `rename` call fails. This is as * close as it gets with the Win32 API. */ - return MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR; + error = MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR; #else /* Don't even try this on Win32 */ if (!link(from, to)) { @@ -214,16 +216,21 @@ int gitfo_mv(const char *from, const char *to) if (!rename(from, to)) return GIT_SUCCESS; - return GIT_EOSERR; + error = GIT_EOSERR; #endif + + if (error < GIT_SUCCESS) + return git__throw(error, "Failed to move file from `%s`to `%s`", from, to); + + return GIT_SUCCESS; } int gitfo_mv_force(const char *from, const char *to) { if (gitfo_mkdir_2file(to) < GIT_SUCCESS) - return GIT_EOSERR; + return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */ - return gitfo_mv(from, to); + return gitfo_mv(from, to); /* The callee already takes care of setting the correct error message. */ } int gitfo_map_ro(git_map *out, git_file fd, git_off_t begin, size_t len) @@ -338,7 +345,7 @@ int gitfo_dirent( struct dirent *de; if (!wd_len || path_sz < wd_len + 2) - return GIT_ERROR; + return git__throw(GIT_EINVALIDARGS, "Failed to process `%s` tree structure. Path is either empty or buffer size is too short", path); while (path[wd_len - 1] == '/') wd_len--; @@ -347,7 +354,7 @@ int gitfo_dirent( dir = opendir(path); if (!dir) - return GIT_EOSERR; + return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path); while ((de = readdir(dir)) != NULL) { size_t de_len; @@ -364,14 +371,14 @@ int gitfo_dirent( de_len = strlen(de->d_name); if (path_sz < wd_len + de_len + 1) { closedir(dir); - return GIT_ERROR; + return git__throw(GIT_ERROR, "Failed to process `%s` tree structure. Buffer size is too short", path); } strcpy(path + wd_len, de->d_name); result = fn(arg, path); if (result < GIT_SUCCESS) { closedir(dir); - return result; + return result; /* The callee is reponsible for setting the correct error message */ } if (result > 0) { closedir(dir); @@ -399,7 +406,7 @@ int retrieve_path_root_offset(const char *path) if (*(path + offset) == '/') return offset; - return GIT_ERROR; + return -1; /* Not a real error. Rather a signal than the path is not rooted */ } @@ -438,7 +445,11 @@ int gitfo_mkdir_recurs(const char *path, int mode) error = gitfo_mkdir(path, mode); free(path_copy); - return error; + + if (error < GIT_SUCCESS) + return git__throw(error, "Failed to recursively create `%s` tree structure", path); + + return GIT_SUCCESS; } static int retrieve_previous_path_component_start(const char *path) @@ -484,7 +495,7 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) if (root_path_offset < 0) { error = gitfo_getcwd(buffer_out, size); if (error < GIT_SUCCESS) - return error; + return error; /* The callee already takes care of setting the correct error message. */ len = strlen(buffer_out); buffer_out += len; @@ -529,7 +540,7 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) /* Are we escaping out of the root dir? */ if (len < 0) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path escapes out of the root directory", path); buffer_out = (char *)buffer_out_start + len; continue; @@ -537,7 +548,7 @@ int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) /* Guard against potential multiple dot path traversal (cf http://cwe.mitre.org/data/definitions/33.html) */ if (only_dots && segment_len > 0) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path contains a segment with three `.` or more", path); *buffer_out++ = '/'; len++; @@ -557,21 +568,21 @@ int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path) /* Let's make sure the filename isn't empty nor a dot */ if (path_len == 0 || (path_len == 1 && *path == '.')) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path is either empty or equals `.`", path); /* Let's make sure the filename doesn't end with "/", "/." or "/.." */ for (i = 1; path_len > i && i < 4; i++) { if (!strncmp(path + path_len - i, pattern, i)) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); } error = gitfo_prettify_dir_path(buffer_out, size, path); if (error < GIT_SUCCESS) - return error; + return error; /* The callee already takes care of setting the correct error message. */ path_len = strlen(buffer_out); - if (path_len < 2) - return GIT_EINVALIDPATH; + if (path_len < 2) /* TODO: Fixme. We should also take of detecting Windows rooted path (probably through usage of retrieve_path_root_offset) */ + return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); /* Remove the trailing slash */ buffer_out[path_len - 1] = '\0'; @@ -616,11 +627,11 @@ int gitfo_getcwd(char *buffer_out, size_t size) #ifdef GIT_WIN32 cwd_buffer = _getcwd(buffer_out, size); #else - cwd_buffer = getcwd(buffer_out, size); //TODO: Fixme. Ensure the required headers are correctly included + cwd_buffer = getcwd(buffer_out, size); #endif if (cwd_buffer == NULL) - return GIT_EOSERR; + return git__throw(GIT_EOSERR, "Failed to retrieve current working directory"); posixify_path(buffer_out); diff --git a/src/index.c b/src/index.c index 94d1c9e2a..d912d90f7 100644 --- a/src/index.c +++ b/src/index.c @@ -103,6 +103,7 @@ static git_index_tree *read_tree_internal(const char **, const char *, git_index static int read_unmerged_internal(git_index *, const char **, size_t buffer_size); static int parse_index(git_index *index, const char *buffer, size_t buffer_size); +static int is_index_extended(git_index *index); static void sort_index(git_index *index); static int write_index(git_index *index, git_filebuf *file); @@ -317,56 +318,12 @@ git_index_entry *git_index_get(git_index *index, int n) return git_vector_get(&index->entries, (unsigned int)n); } -int git_index_add(git_index *index, const char *rel_path, int stage) -{ - git_index_entry entry; - char full_path[GIT_PATH_MAX]; - struct stat st; - int error; - - if (index->repository == NULL) - return GIT_EBAREINDEX; - - git__joinpath(full_path, index->repository->path_workdir, rel_path); - - if (gitfo_exists(full_path) < 0) - return GIT_ENOTFOUND; - - if (gitfo_stat(full_path, &st) < 0) - return GIT_EOSERR; - - if (stage < 0 || stage > 3) - return GIT_ERROR; - - memset(&entry, 0x0, sizeof(git_index_entry)); - - entry.ctime.seconds = (git_time_t)st.st_ctime; - entry.mtime.seconds = (git_time_t)st.st_mtime; - /* entry.mtime.nanoseconds = st.st_mtimensec; */ - /* entry.ctime.nanoseconds = st.st_ctimensec; */ - entry.dev= st.st_rdev; - entry.ino = st.st_ino; - entry.mode = st.st_mode; - entry.uid = st.st_uid; - entry.gid = st.st_gid; - entry.file_size = st.st_size; - - /* write the blob to disk and get the oid */ - if ((error = git_blob_create_fromfile(&entry.oid, index->repository, rel_path)) < GIT_SUCCESS) - return error; - - entry.flags |= (stage << GIT_IDXENTRY_STAGESHIFT); - entry.path = (char *)rel_path; /* do not duplicate; index_insert already does this */ - - return git_index_insert(index, &entry); -} - -void sort_index(git_index *index) +static void sort_index(git_index *index) { git_vector_sort(&index->entries); } -int git_index_insert(git_index *index, const git_index_entry *source_entry) +static int index_insert(git_index *index, const git_index_entry *source_entry, int replace) { git_index_entry *entry; size_t path_length; @@ -402,13 +359,15 @@ int git_index_insert(git_index *index, const git_index_entry *source_entry) /* look if an entry with this path already exists */ position = git_index_find(index, source_entry->path); - /* if no entry exists, add the entry at the end; + /* if no entry exists and replace is not set, + * add the entry at the end; * the index is no longer sorted */ - if (position == GIT_ENOTFOUND) { + if (!replace || position == GIT_ENOTFOUND) { if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) return GIT_ENOMEM; - /* if a previous entry exists, replace it */ + /* if a previous entry exists and replace is set, + * replace it */ } else { git_index_entry **entry_array = (git_index_entry **)index->entries.contents; @@ -421,6 +380,81 @@ int git_index_insert(git_index *index, const git_index_entry *source_entry) return GIT_SUCCESS; } +static int index_init_entry(git_index_entry *entry, git_index *index, const char *rel_path, int stage) +{ + char full_path[GIT_PATH_MAX]; + struct stat st; + int error; + + if (index->repository == NULL) + return GIT_EBAREINDEX; + + git__joinpath(full_path, index->repository->path_workdir, rel_path); + + if (gitfo_exists(full_path) < 0) + return GIT_ENOTFOUND; + + if (gitfo_stat(full_path, &st) < 0) + return GIT_EOSERR; + + if (stage < 0 || stage > 3) + return GIT_ERROR; + + memset(entry, 0x0, sizeof(git_index_entry)); + + entry->ctime.seconds = (git_time_t)st.st_ctime; + entry->mtime.seconds = (git_time_t)st.st_mtime; + /* entry.mtime.nanoseconds = st.st_mtimensec; */ + /* entry.ctime.nanoseconds = st.st_ctimensec; */ + entry->dev= st.st_rdev; + entry->ino = st.st_ino; + entry->mode = st.st_mode; + entry->uid = st.st_uid; + entry->gid = st.st_gid; + entry->file_size = st.st_size; + + /* write the blob to disk and get the oid */ + if ((error = git_blob_create_fromfile(&entry->oid, index->repository, rel_path)) < GIT_SUCCESS) + return error; + + entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); + entry->path = (char *)rel_path; /* do not duplicate; index_insert already does this */ + return GIT_SUCCESS; +} + +int git_index_add(git_index *index, const char *path, int stage) +{ + int error; + git_index_entry entry; + + if ((error = index_init_entry(&entry, index, path, stage)) < GIT_SUCCESS) + return error; + + return index_insert(index, &entry, 1); +} + +int git_index_append(git_index *index, const char *path, int stage) +{ + int error; + git_index_entry entry; + + if ((error = index_init_entry(&entry, index, path, stage)) < GIT_SUCCESS) + return error; + + return index_insert(index, &entry, 0); +} + +int git_index_add2(git_index *index, const git_index_entry *source_entry) +{ + return index_insert(index, source_entry, 1); +} + +int git_index_append2(git_index *index, const git_index_entry *source_entry) +{ + return index_insert(index, source_entry, 0); +} + + int git_index_remove(git_index *index, int position) { assert(index); @@ -798,6 +832,24 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) return GIT_SUCCESS; } +static int is_index_extended(git_index *index) +{ + unsigned int i, extended; + + extended = 0; + + for (i = 0; i < index->entries.length; ++i) { + git_index_entry *entry; + entry = git_vector_get(&index->entries, i); + entry->flags &= ~GIT_IDXENTRY_EXTENDED; + if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) { + extended++; + entry->flags |= GIT_IDXENTRY_EXTENDED; + } + } + return extended; +} + static int write_disk_entry(git_filebuf *file, git_index_entry *entry) { struct entry_short *ondisk; @@ -866,12 +918,14 @@ static int write_index(git_index *index, git_filebuf *file) struct index_header header; - int is_extended = 1; + int is_extended; assert(index && file); + is_extended = is_index_extended(index); + header.signature = htonl(INDEX_HEADER_SIG); - header.version = htonl(is_extended ? INDEX_VERSION_NUMBER : INDEX_VERSION_NUMBER_EXT); + header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER); header.entry_count = htonl(index->entries.length); git_filebuf_write(file, &header, sizeof(struct index_header)); diff --git a/src/odb.c b/src/odb.c index 43d0ef9a4..f09b6d7e9 100644 --- a/src/odb.c +++ b/src/odb.c @@ -221,7 +221,7 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend * ***********************************************************/ -int backend_sort_cmp(const void *a, const void *b) +static int backend_sort_cmp(const void *a, const void *b) { const backend_internal *backend_a = *(const backend_internal **)(a); const backend_internal *backend_b = *(const backend_internal **)(b); @@ -234,15 +234,19 @@ int backend_sort_cmp(const void *a, const void *b) int git_odb_new(git_odb **out) { + int error; + git_odb *db = git__calloc(1, sizeof(*db)); if (!db) return GIT_ENOMEM; - git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object); + error = git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object); + if (error < GIT_SUCCESS) + return error; - if (git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { + if ((error = git_vector_init(&db->backends, 4, backend_sort_cmp)) < GIT_SUCCESS) { free(db); - return GIT_ENOMEM; + return error; } *out = db; @@ -444,7 +448,7 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git return error; *len_p = object->raw.len; - *type_p = object->raw.len; + *type_p = object->raw.type; git_odb_object_close(object); } diff --git a/src/odb_loose.c b/src/odb_loose.c index 8ee01cd2c..873dbfa0a 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -336,7 +336,6 @@ static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) { unsigned char head[64], *buf; z_stream zs; - int z_status; obj_hdr hdr; size_t used; @@ -350,7 +349,7 @@ static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) * inflate the initial part of the io buffer in order * to parse the object header (type and size). */ - if ((z_status = start_inflate(&zs, obj, head, sizeof(head))) < Z_OK) + if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK) return GIT_ERROR; if ((used = get_object_header(&hdr, head)) == 0) @@ -434,6 +433,9 @@ static int read_header_loose(git_rawobj *out, const char *loc) if ((read_bytes = read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { set_stream_input(&zs, raw_buffer, read_bytes); z_return = inflate(&zs, 0); + } else { + z_return = Z_STREAM_END; + break; } } while (z_return == Z_OK); diff --git a/src/odb_pack.c b/src/odb_pack.c index 561b32935..57ad5e34b 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -113,7 +113,7 @@ struct pack_backend { git_vector packs; struct pack_file *last_found; char *pack_folder; - off_t pack_folder_size; + time_t pack_folder_mtime; size_t window_size; /* needs default value */ @@ -874,7 +874,7 @@ static int packfile_refresh_all(struct pack_backend *backend) if (gitfo_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) return GIT_ENOTFOUND; - if (st.st_size != backend->pack_folder_size) { + if (st.st_mtime != backend->pack_folder_mtime) { char path[GIT_PATH_MAX]; strcpy(path, backend->pack_folder); @@ -884,7 +884,7 @@ static int packfile_refresh_all(struct pack_backend *backend) return error; git_vector_sort(&backend->packs); - backend->pack_folder_size = st.st_size; + backend->pack_folder_mtime = st.st_mtime; } return GIT_SUCCESS; @@ -1385,6 +1385,7 @@ void pack_backend__free(git_odb_backend *_backend) } git_vector_free(&backend->packs); + free(backend->pack_folder); free(backend); } @@ -1408,7 +1409,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) git__joinpath(path, objects_dir, "pack"); if (gitfo_isdir(path) == GIT_SUCCESS) { backend->pack_folder = git__strdup(path); - backend->pack_folder_size = -1; + backend->pack_folder_mtime = 0; if (backend->pack_folder == NULL) { free(backend); diff --git a/src/refs.c b/src/refs.c index 00b9ff6b2..c4d3d6ae6 100644 --- a/src/refs.c +++ b/src/refs.c @@ -122,7 +122,8 @@ static int reference_create( else if (type == GIT_REF_OID) size = sizeof(reference_oid); else - return GIT_EINVALIDREFSTATE; + return git__throw(GIT_EINVALIDARGS, + "Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier"); reference = git__malloc(size); if (reference == NULL) @@ -159,11 +160,9 @@ static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *re /* Determine the full path of the file */ git__joinpath(path, repo_path, ref_name); - if (gitfo_stat(path, &st) < 0) - return GIT_ENOTFOUND; - - if (S_ISDIR(st.st_mode)) - return GIT_EOBJCORRUPTED; + if (gitfo_stat(path, &st) < 0 || S_ISDIR(st.st_mode)) + return git__throw(GIT_ENOTFOUND, + "Cannot read reference file '%s'", ref_name); if (mtime) *mtime = st.st_mtime; @@ -205,7 +204,8 @@ static int loose_update(git_reference *ref) else if (ref->type == GIT_REF_OID) error = loose_parse_oid(ref, &ref_file); else - error = GIT_EINVALIDREFSTATE; + error = git__throw(GIT_EOBJCORRUPTED, + "Invalid reference type (%d) for loose reference", ref->type); gitfo_free_buf(&ref_file); @@ -229,7 +229,8 @@ static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) ref_sym = (reference_symbolic *)ref; if (file_content->len < (header_len + 1)) - return GIT_EREFCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, + "Failed to parse loose reference. Object too short"); /* * Assume we have already checked for the header @@ -246,7 +247,8 @@ static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) /* remove newline at the end of file */ eol = strchr(ref_sym->target, '\n'); if (eol == NULL) - return GIT_EREFCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, + "Failed to parse loose reference. Missing EOL"); *eol = '\0'; if (eol[-1] == '\r') @@ -257,6 +259,7 @@ static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content) { + int error; reference_oid *ref_oid; char *buffer; @@ -265,17 +268,19 @@ static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content) /* File format: 40 chars (OID) + newline */ if (file_content->len < GIT_OID_HEXSZ + 1) - return GIT_EREFCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, + "Failed to parse loose reference. Reference too short"); - if (git_oid_mkstr(&ref_oid->oid, buffer) < GIT_SUCCESS) - return GIT_EREFCORRUPTED; + if ((error = git_oid_mkstr(&ref_oid->oid, buffer)) < GIT_SUCCESS) + return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference."); buffer = buffer + GIT_OID_HEXSZ; if (*buffer == '\r') buffer++; if (*buffer != '\n') - return GIT_EREFCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, + "Failed to parse loose reference. Missing EOL"); return GIT_SUCCESS; } @@ -387,7 +392,7 @@ static int loose_write(git_reference *ref) strcpy(ref_contents, GIT_SYMREF); strcat(ref_contents, ref_sym->target); } else { - error = GIT_EINVALIDREFSTATE; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to write reference. Invalid reference type"); goto unlock; } @@ -684,7 +689,7 @@ static int packed_loadloose(git_repository *repository) /* Remove any loose references from the cache */ { - const void *_unused; + const void *GIT_UNUSED(_unused); git_reference *reference; GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference, @@ -787,6 +792,8 @@ static int packed_find_peel(reference_oid *ref) */ } + git_object_close(object); + return GIT_SUCCESS; } @@ -868,7 +875,7 @@ static int packed_write(git_repository *repo) /* Load all the packfile into a vector */ { git_reference *reference; - const void *_unused; + const void *GIT_UNUSED(_unused); GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference, git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */ @@ -1480,8 +1487,9 @@ int git_reference_resolve(git_reference **resolved_ref, git_reference *ref) for (i = 0; i < MAX_NESTING_LEVEL; ++i) { reference_symbolic *ref_sym; + *resolved_ref = ref; + if (ref->type & GIT_REF_OID) { - *resolved_ref = ref; return GIT_SUCCESS; } @@ -1518,7 +1526,7 @@ int git_reference_listcb(git_repository *repo, unsigned int list_flags, int (*ca /* list all the packed references first */ if (list_flags & GIT_REF_PACKED) { const char *ref_name; - void *_unused; + void *GIT_UNUSED(_unused); if ((error = packed_load(repo)) < GIT_SUCCESS) return error; @@ -1597,7 +1605,7 @@ int git_repository__refcache_init(git_refcache *refs) void git_repository__refcache_free(git_refcache *refs) { git_reference *reference; - const void *_unused; + const void *GIT_UNUSED(_unused); assert(refs); @@ -1692,8 +1700,9 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref) } /* Object id refname have to contain at least one slash, except - * for HEAD in a detached state */ - if (is_oid_ref && !contains_a_slash && strcmp(name, GIT_HEAD_FILE)) + * for HEAD in a detached state or MERGE_HEAD if we're in the + * middle of a merge */ + if (is_oid_ref && !contains_a_slash && (strcmp(name, GIT_HEAD_FILE) && strcmp(name, GIT_MERGE_HEAD_FILE))) return GIT_EINVALIDREFNAME; /* A refname can not end with ".lock" */ diff --git a/src/refs.h b/src/refs.h index bebb1b97d..b8f3e2f6d 100644 --- a/src/refs.h +++ b/src/refs.h @@ -17,6 +17,7 @@ #define MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH 100 #define GIT_HEAD_FILE "HEAD" +#define GIT_MERGE_HEAD_FILE "MERGE_HEAD" #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master" struct git_reference { diff --git a/src/repository.c b/src/repository.c index c428b00af..eab56fc9a 100644 --- a/src/repository.c +++ b/src/repository.c @@ -58,7 +58,6 @@ static int assign_repository_dirs( const char *git_work_tree) { char path_aux[GIT_PATH_MAX]; - size_t git_dir_path_len; int error = GIT_SUCCESS; assert(repo); @@ -70,8 +69,6 @@ static int assign_repository_dirs( if (error < GIT_SUCCESS) return error; - git_dir_path_len = strlen(path_aux); - /* store GIT_DIR */ repo->path_repository = git__strdup(path_aux); if (repo->path_repository == NULL) @@ -127,16 +124,16 @@ static int check_repository_dirs(git_repository *repo) char path_aux[GIT_PATH_MAX]; if (gitfo_isdir(repo->path_repository) < GIT_SUCCESS) - return GIT_ENOTAREPO; + return git__throw(GIT_ENOTAREPO, "`%s` is not a folder", repo->path_repository); /* Ensure GIT_OBJECT_DIRECTORY exists */ if (gitfo_isdir(repo->path_odb) < GIT_SUCCESS) - return GIT_ENOTAREPO; + return git__throw(GIT_ENOTAREPO, "`%s` does not exist", repo->path_odb); /* Ensure HEAD file exists */ git__joinpath(path_aux, repo->path_repository, GIT_HEAD_FILE); if (gitfo_exists(path_aux) < 0) - return GIT_ENOTAREPO; + return git__throw(GIT_ENOTAREPO, "HEAD file is missing"); return GIT_SUCCESS; } @@ -148,12 +145,12 @@ static int guess_repository_dirs(git_repository *repo, const char *repository_pa /* Git directory name */ if (git__basename_r(buffer, sizeof(buffer), repository_path) < 0) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Unable to parse folder name from `%s`", repository_path); if (strcmp(buffer, DOT_GIT) == 0) { /* Path to working dir */ if (git__dirname_r(buffer, sizeof(buffer), repository_path) < 0) - return GIT_EINVALIDPATH; + return git__throw(GIT_EINVALIDPATH, "Unable to parse parent folder name from `%s`", repository_path); path_work_tree = buffer; } @@ -162,13 +159,19 @@ static int guess_repository_dirs(git_repository *repo, const char *repository_pa static git_repository *repository_alloc() { + int error; + git_repository *repo = git__malloc(sizeof(git_repository)); if (!repo) return NULL; memset(repo, 0x0, sizeof(git_repository)); - git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free); + error = git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free); + if (error < GIT_SUCCESS) { + free(repo); + return NULL; + } if (git_repository__refcache_init(&repo->references) < GIT_SUCCESS) { free(repo); @@ -180,7 +183,7 @@ static git_repository *repository_alloc() static int init_odb(git_repository *repo) { - return git_odb_open(&repo->db, repo->path_odb); + return git_odb_open(&repo->db, repo->path_odb); /* TODO: Move odb.c to new error handling */ } int git_repository_open3(git_repository **repo_out, @@ -195,7 +198,7 @@ int git_repository_open3(git_repository **repo_out, assert(repo_out); if (object_database == NULL) - return GIT_ERROR; + return git__throw(GIT_EINVALIDARGS, "Failed to open repository. `object_database` can't be null"); repo = repository_alloc(); if (repo == NULL) @@ -221,7 +224,7 @@ int git_repository_open3(git_repository **repo_out, cleanup: git_repository_free(repo); - return error; + return git__rethrow(error, "Failed to open repository"); } @@ -262,7 +265,7 @@ int git_repository_open2(git_repository **repo_out, cleanup: git_repository_free(repo); - return error; + return git__rethrow(error, "Failed to open repository"); } int git_repository_open(git_repository **repo_out, const char *path) @@ -293,7 +296,7 @@ int git_repository_open(git_repository **repo_out, const char *path) cleanup: git_repository_free(repo); - return error; + return git__rethrow(error, "Failed to open repository"); } void git_repository_free(git_repository *repo) @@ -312,8 +315,10 @@ void git_repository_free(git_repository *repo) if (repo->db != NULL) git_odb_close(repo->db); - if (repo->index != NULL) + if (repo->index != NULL) { + repo->index->repository = NULL; git_index_free(repo->index); + } free(repo); } @@ -325,7 +330,7 @@ int git_repository_index(git_index **index_out, git_repository *repo) assert(index_out && repo); if (repo->index == NULL) { - error = git_index_open_inrepo(&repo->index, repo); + error = git_index_open_inrepo(&repo->index, repo); /* TODO: move index.c to new error handling */ if (error < GIT_SUCCESS) return error; @@ -352,7 +357,7 @@ static int repo_init_reinit(repo_init *results) static int repo_init_createhead(git_repository *repo) { git_reference *head_reference; - return git_reference_create_symbolic(&head_reference, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_MASTER_FILE); + return git_reference_create_symbolic(&head_reference, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_MASTER_FILE); /* TODO: finalize moving refs.c to new error handling */ } static int repo_init_check_head_existence(char * repository_path) @@ -366,6 +371,7 @@ static int repo_init_check_head_existence(char * repository_path) static int repo_init_structure(repo_init *results) { const int mode = 0755; /* or 0777 ? */ + int error; char temp_path[GIT_PATH_MAX]; char *git_dir = results->path_repository; @@ -375,23 +381,27 @@ static int repo_init_structure(repo_init *results) /* Creates the '/objects/info/' directory */ git__joinpath(temp_path, git_dir, GIT_OBJECTS_INFO_DIR); - if (gitfo_mkdir_recurs(temp_path, mode) < GIT_SUCCESS) - return GIT_ERROR; + error = gitfo_mkdir_recurs(temp_path, mode); + if (error < GIT_SUCCESS) + return error; /* Creates the '/objects/pack/' directory */ git__joinpath(temp_path, git_dir, GIT_OBJECTS_PACK_DIR); - if (gitfo_mkdir(temp_path, mode)) - return GIT_ERROR; + error = gitfo_mkdir(temp_path, mode); + if (error < GIT_SUCCESS) + return git__throw(error, "Unable to create `%s` folder", temp_path); /* Creates the '/refs/heads/' directory */ git__joinpath(temp_path, git_dir, GIT_REFS_HEADS_DIR); - if (gitfo_mkdir_recurs(temp_path, mode)) - return GIT_ERROR; + error = gitfo_mkdir_recurs(temp_path, mode); + if (error < GIT_SUCCESS) + return error; /* Creates the '/refs/tags/' directory */ git__joinpath(temp_path, git_dir, GIT_REFS_TAGS_DIR); - if (gitfo_mkdir(temp_path, mode)) - return GIT_ERROR; + error = gitfo_mkdir(temp_path, mode); + if (error < GIT_SUCCESS) + return git__throw(error, "Unable to create `%s` folder", temp_path); /* TODO: what's left? templates? */ @@ -470,7 +480,7 @@ int git_repository_init(git_repository **repo_out, const char *path, unsigned is cleanup: free(results.path_repository); git_repository_free(repo); - return error; + return git__rethrow(error, "Failed to (re)init the repository `%s`", path); } int git_repository_is_empty(git_repository *repo) @@ -480,10 +490,10 @@ int git_repository_is_empty(git_repository *repo) error = git_reference_lookup(&head, repo, "HEAD"); if (error < GIT_SUCCESS) - return error; + return git__throw(error, "Failed to determine the emptiness of the repository. An error occured while retrieving the HEAD reference"); if (git_reference_type(head) != GIT_REF_SYMBOLIC) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to determine the emptiness of the repository. HEAD is probably in detached state"); return git_reference_resolve(&branch, head) == GIT_SUCCESS ? 0 : 1; } diff --git a/src/revwalk.c b/src/revwalk.c index b62b09961..8163851a3 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -210,7 +210,7 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo git_oid oid; if (git_oid_mkstr(&oid, (char *)buffer + STRLEN("parent ")) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Parent object is corrupted"); commit->parents[i] = commit_lookup(walk, &oid); if (commit->parents[i] == NULL) @@ -222,14 +222,14 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo commit->out_degree = (unsigned short)parents; if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Object is corrupted"); buffer = memchr(buffer, '>', buffer_end - buffer); if (buffer == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't find author"); if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't parse commit time"); commit->time = (time_t)commit_time; commit->parsed = 1; @@ -245,11 +245,11 @@ static int commit_parse(git_revwalk *walk, commit_object *commit) return GIT_SUCCESS; if ((error = git_odb_read(&obj, walk->repo->db, &commit->oid)) < GIT_SUCCESS) - return error; + return git__rethrow(error, "Failed to parse commit. Can't read object"); if (obj->raw.type != GIT_OBJ_COMMIT) { git_odb_object_close(obj); - return GIT_EOBJTYPE; + return git__throw(GIT_EOBJTYPE, "Failed to parse commit. Object is no commit object"); } error = commit_quick_parse(walk, commit, &obj->raw); @@ -305,7 +305,7 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) commit = commit_lookup(walk, oid); if (commit == NULL) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Failed to push commit. Object not found"); commit->uninteresting = uninteresting; @@ -483,7 +483,7 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) void git_revwalk_free(git_revwalk *walk) { unsigned int i; - const void *_unused; + const void *GIT_UNUSED(_unused); commit_object *commit; if (walk == NULL) @@ -558,7 +558,7 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) void git_revwalk_reset(git_revwalk *walk) { - const void *_unused; + const void *GIT_UNUSED(_unused); commit_object *commit; assert(walk); diff --git a/src/signature.c b/src/signature.c index e8014620a..62bd28b9a 100644 --- a/src/signature.c +++ b/src/signature.c @@ -115,22 +115,22 @@ static int parse_timezone_offset(const char *buffer, long *offset_out) } if (offset_start[0] != '-' && offset_start[0] != '+') - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It doesn't start with '+' or '-'"); if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It isn't a number"); if (offset_end - offset_start != 5) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Invalid length"); hours = dec_offset / 100; mins = dec_offset % 100; if (hours > 14) // see http://www.worldtimezone.com/faq.html - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Hour value too large");; if (mins > 59) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Minute value too large"); offset = (hours * 60) + mins; @@ -157,19 +157,19 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, line_end = memchr(buffer, '\n', buffer_end - buffer); if (!line_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. No newline found");; if (buffer + (header_len + 1) > line_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short"); if (memcmp(buffer, header, header_len) != 0) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Expected prefix '%s' doesn't match actual", header); buffer += header_len; /* Parse name */ if ((name_end = memchr(buffer, '<', buffer_end - buffer)) == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Can't find e-mail start"); name_length = name_end - buffer - 1; sig->name = git__malloc(name_length + 1); @@ -181,11 +181,11 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, buffer = name_end + 1; if (buffer >= line_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Ended unexpectedly"); /* Parse email */ if ((email_end = memchr(buffer, '>', buffer_end - buffer)) == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Can't find e-mail end"); email_length = email_end - buffer; sig->email = git__malloc(email_length + 1); @@ -197,10 +197,10 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, buffer = email_end + 1; if (buffer >= line_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Ended unexpectedly"); if (git__strtol32(&time, buffer, &buffer, 10) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Timestamp isn't a number"); sig->when.time = (time_t)time; diff --git a/src/tag.c b/src/tag.c index 148eb280c..849429b7e 100644 --- a/src/tag.c +++ b/src/tag.c @@ -90,13 +90,13 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer int error; if ((error = git__parse_oid(&tag->target, &buffer, buffer_end, "object ")) < 0) - return error; + return git__rethrow(error, "Failed to parse tag. Object field invalid"); if (buffer + 5 >= buffer_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); if (memcmp(buffer, "type ", 5) != 0) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Type field not found"); buffer += 5; tag->type = GIT_OBJ_BAD; @@ -105,7 +105,7 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer size_t type_length = strlen(tag_types[i]); if (buffer + type_length >= buffer_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); if (memcmp(buffer, tag_types[i], type_length) == 0) { tag->type = i; @@ -115,18 +115,19 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer } if (tag->type == GIT_OBJ_BAD) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Invalid object type"); if (buffer + 4 >= buffer_end) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); if (memcmp(buffer, "tag ", 4) != 0) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Tag field not found"); + buffer += 4; search = memchr(buffer, '\n', buffer_end - buffer); if (search == NULL) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); text_len = search - buffer; @@ -218,7 +219,7 @@ static int tag_create( } if (!git_odb_exists(repo->db, target)) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Failed to create tag. Object to tag doesn't exist"); /* Try to find out what the type is */ if (target_type == GIT_OBJ_ANY) { diff --git a/src/tree.c b/src/tree.c index 64f81d780..6f9fc8607 100644 --- a/src/tree.c +++ b/src/tree.c @@ -151,15 +151,15 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf return GIT_ENOMEM; if (git__strtol32((long *)&entry->attr, buffer, &buffer, 8) < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; + return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Can't parse attributes"); if (*buffer++ != ' ') { - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); break; } if (memchr(buffer, 0, buffer_end - buffer) == NULL) { - error = GIT_EOBJCORRUPTED; + error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); break; } @@ -274,7 +274,7 @@ int git_tree_create_fromindex(git_oid *oid, git_index *index) int error; if (index->repository == NULL) - return GIT_EBAREINDEX; + return git__throw(GIT_EBAREINDEX, "Failed to create tree. The index file is not backed up by an existing repository"); error = write_index(oid, index, "", 0, 0, git_index_entrycount(index)); return (error < GIT_SUCCESS) ? error : GIT_SUCCESS; @@ -343,7 +343,7 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con assert(bld && id && filename); if (!valid_attributes(attributes)) - return GIT_ERROR; + return git__throw(GIT_ERROR, "Failed to insert entry. Invalid atrributes"); if ((pos = git_vector_bsearch2(&bld->entries, entry_search_cmp, filename)) != GIT_ENOTFOUND) { entry = git_vector_get(&bld->entries, pos); @@ -400,7 +400,7 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename) git_tree_entry *remove_ptr = (git_tree_entry *)git_treebuilder_get(bld, filename); if (remove_ptr == NULL || remove_ptr->removed) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Failed to remove entry. File isn't in the tree"); remove_ptr->removed = 1; bld->entry_count--; @@ -424,13 +424,14 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b if (entry->removed) continue; - size += (entry->attr > 0x7FF) ? 7 : 6; + snprintf(filemode, sizeof(filemode), "%o ", entry->attr); + size += strlen(filemode); size += entry->filename_len + 1; size += GIT_OID_RAWSZ; } if ((error = git_odb_open_wstream(&stream, git_repository_database(repo), size, GIT_OBJ_TREE)) < GIT_SUCCESS) - return error; + return git__rethrow(error, "Failed to write tree. Can't open write stream"); for (i = 0; i < bld->entries.length; ++i) { git_tree_entry *entry = bld->entries.contents[i]; diff --git a/src/util.c b/src/util.c index 55a7ab2a9..9499ceadf 100644 --- a/src/util.c +++ b/src/util.c @@ -104,6 +104,20 @@ int git__fmt(char *buf, size_t buf_sz, const char *fmt, ...) return r; } +void git__strntolower(char *str, int len) +{ + int i; + + for (i = 0; i < len; ++i) { + str[i] = tolower(str[i]); + } +} + +void git__strtolower(char *str) +{ + git__strntolower(str, strlen(str)); +} + int git__prefixcmp(const char *str, const char *prefix) { for (;;) { diff --git a/src/util.h b/src/util.h index 3c606493f..e5a2ebe5f 100644 --- a/src/util.h +++ b/src/util.h @@ -6,16 +6,60 @@ #define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits)))) /* - * Don't wrap malloc/calloc. - * Use the default versions in glibc, and make - * sure that any methods that allocate memory - * return a GIT_ENOMEM error when allocation - * fails. + * Custom memory allocation wrappers + * that set error code and error message + * on allocation failure */ -#define git__malloc malloc -#define git__calloc calloc -#define git__realloc realloc -#define git__strdup strdup +GIT_INLINE(void *) git__malloc(size_t len) +{ + void *ptr = malloc(len); + if (!ptr) + git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)len); + return ptr; +} + +GIT_INLINE(void *) git__calloc(size_t nelem, size_t elsize) +{ + void *ptr = calloc(nelem, elsize); + if (!ptr) + git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)elsize); + return ptr; +} + +GIT_INLINE(char *) git__strdup(const char *str) +{ + char *ptr = strdup(str); + if (!ptr) + git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + return ptr; +} + +GIT_INLINE(char *) git__strndup(const char *str, size_t n) +{ + size_t length; + char *ptr; + + length = strlen(str); + if (n < length) + length = n; + + ptr = malloc(length + 1); + if (!ptr) + git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + + memcpy(ptr, str, length); + ptr[length] = 0; + + return ptr; +} + +GIT_INLINE(void *) git__realloc(void *ptr, size_t size) +{ + void *new_ptr = realloc(ptr, size); + if (!new_ptr) + git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)size); + return new_ptr; +} extern int git__fmt(char *, size_t, const char *, ...) GIT_FORMAT_PRINTF(3, 4); @@ -98,6 +142,9 @@ GIT_INLINE(int) git__is_sizet(git_off_t p) extern char *git__strtok(char *output, char *src, char *delimit); extern char *git__strtok_keep(char *output, char *src, char *delimit); +extern void git__strntolower(char *str, int len); +extern void git__strtolower(char *str); + #define STRLEN(str) (sizeof(str) - 1) #define GIT_OID_LINE_LENGTH(header) (STRLEN(header) + 1 + GIT_OID_HEXSZ + 1) diff --git a/src/vector.c b/src/vector.c index d0b0c5c56..1ddc26e3e 100644 --- a/src/vector.c +++ b/src/vector.c @@ -106,7 +106,7 @@ int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *ke /* need comparison function to sort the vector */ if (v->_cmp == NULL) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Can't sort vector. No comparison function set"); git_vector_sort(v); @@ -114,7 +114,7 @@ int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *ke if (find != NULL) return (int)(find - v->contents); - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Can't find element"); } int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key) @@ -128,7 +128,7 @@ int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key return i; } - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Can't find element"); } static int strict_comparison(const void *a, const void *b) @@ -143,9 +143,6 @@ int git_vector_search(git_vector *v, const void *entry) int git_vector_bsearch(git_vector *v, const void *key) { - if (v->_cmp == NULL) - return GIT_ENOTFOUND; - return git_vector_bsearch2(v, v->_cmp, key); } @@ -156,7 +153,7 @@ int git_vector_remove(git_vector *v, unsigned int idx) assert(v); if (idx >= v->length || v->length == 0) - return GIT_ENOTFOUND; + return git__throw(GIT_ENOTFOUND, "Can't remove element. Index out of bounds"); for (i = idx; i < v->length - 1; ++i) v->contents[i] = v->contents[i + 1]; diff --git a/src/win32/pthread.c b/src/win32/pthread.c index f47364a76..7e17b6bdf 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -48,16 +48,15 @@ int pthread_join(pthread_t thread, void **value_ptr) int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex, const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr)) { - GIT_UNUSED_ARG(mutexattr); + GIT_UNUSED_ARG(mutexattr); InitializeCriticalSection(mutex); return 0; } int pthread_mutex_destroy(pthread_mutex_t *mutex) { - int ret; - ret = CloseHandle(mutex); - return -(!ret); + DeleteCriticalSection(mutex); + return 0; } int pthread_mutex_lock(pthread_mutex_t *mutex) diff --git a/tests/NAMING b/tests/NAMING index ab425e23e..df07b7d51 100644 --- a/tests/NAMING +++ b/tests/NAMING @@ -23,14 +23,24 @@ Categories 04__: Parsing and loading commit data -05__: To be described +05__: Revision walking -06__: To be described +06__: Index reading, writing and searching -07__: To be described +07__: Tests for the internal hashtable code -08__: To be described +08__: Tag reading and writing -09__: To be described +09__: Reading tree objects -10__: Symbolic, loose and packed references reading and writing. \ No newline at end of file +10__: Symbolic, loose and packed references reading and writing. + +11__: SQLite backend + +12__: Repository init and opening + +13__: Threads, empty as of now + +14__: Redis backend + +15__: Configuration parsing diff --git a/tests/resources/config/config0 b/tests/resources/config/config0 new file mode 100644 index 000000000..85235c501 Binary files /dev/null and b/tests/resources/config/config0 differ diff --git a/tests/resources/config/config1 b/tests/resources/config/config1 new file mode 100644 index 000000000..211dc9e7d Binary files /dev/null and b/tests/resources/config/config1 differ diff --git a/tests/resources/config/config2 b/tests/resources/config/config2 new file mode 100644 index 000000000..60a389827 Binary files /dev/null and b/tests/resources/config/config2 differ diff --git a/tests/resources/config/config3 b/tests/resources/config/config3 new file mode 100644 index 000000000..44a5e50ea Binary files /dev/null and b/tests/resources/config/config3 differ diff --git a/tests/resources/config/config4 b/tests/resources/config/config4 new file mode 100644 index 000000000..741fa0ffd Binary files /dev/null and b/tests/resources/config/config4 differ diff --git a/tests/resources/config/config5 b/tests/resources/config/config5 new file mode 100644 index 000000000..8ab60ccec Binary files /dev/null and b/tests/resources/config/config5 differ diff --git a/tests/t04-commit.c b/tests/t04-commit.c index bcc0417c8..36f3e66b5 100644 --- a/tests/t04-commit.c +++ b/tests/t04-commit.c @@ -368,7 +368,7 @@ BEGIN_TEST(details0, "query the details on a parsed commit") const char *message, *message_short; git_time_t commit_time; unsigned int parents, p; - git_commit *parent; + git_commit *parent = NULL, *old_parent = NULL; git_oid_mkstr(&id, commit_ids[i]); @@ -390,11 +390,19 @@ BEGIN_TEST(details0, "query the details on a parsed commit") must_be_true(commit_time > 0); must_be_true(parents <= 2); for (p = 0;p < parents;p++) { + if (old_parent != NULL) + git_commit_close(old_parent); + + old_parent = parent; must_pass(git_commit_parent(&parent, commit, p)); must_be_true(parent != NULL); must_be_true(git_commit_author(parent) != NULL); // is it really a commit? } + git_commit_close(old_parent); + git_commit_close(parent); + must_fail(git_commit_parent(&parent, commit, parents)); + git_commit_close(commit); } git_repository_free(repo); @@ -462,9 +470,76 @@ BEGIN_TEST(write0, "write a new commit object from memory to disk") must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)commit)); + git_commit_close(commit); git_repository_free(repo); END_TEST +#define ROOT_COMMIT_MESSAGE "This is a root commit\n\ +This is a root commit and should be the only one in this branch\n" + +BEGIN_TEST(root0, "create a root commit") + git_repository *repo; + git_commit *commit; + git_oid tree_id, commit_id; + const git_oid *branch_oid; + const git_signature *author, *committer; + const char *branch_name = "refs/heads/root-commit-branch"; + git_reference *head, *branch; + char *head_old; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + git_oid_mkstr(&tree_id, tree_oid); + + /* create signatures */ + committer = git_signature_new(COMMITTER_NAME, COMMITTER_EMAIL, 123456789, 60); + must_be_true(committer != NULL); + + author = git_signature_new(COMMITTER_NAME, COMMITTER_EMAIL, 987654321, 90); + must_be_true(author != NULL); + + /* First we need to update HEAD so it points to our non-existant branch */ + must_pass(git_reference_lookup(&head, repo, "HEAD")); + must_be_true(git_reference_type(head) == GIT_REF_SYMBOLIC); + head_old = git__strdup(git_reference_target(head)); + must_be_true(head_old != NULL); + + must_pass(git_reference_set_target(head, branch_name)); + + must_pass(git_commit_create_v( + &commit_id, /* out id */ + repo, + "HEAD", + author, + committer, + ROOT_COMMIT_MESSAGE, + &tree_id, + 0)); + + git_signature_free((git_signature *)committer); + git_signature_free((git_signature *)author); + + /* + * The fact that creating a commit works has already been + * tested. Here we just make sure it's our commit and that it was + * written as a root commit. + */ + must_pass(git_commit_lookup(&commit, repo, &commit_id)); + must_be_true(git_commit_parentcount(commit) == 0); + must_pass(git_reference_lookup(&branch, repo, branch_name)); + branch_oid = git_reference_oid(branch); + must_pass(git_oid_cmp(branch_oid, &commit_id)); + must_be_true(!strcmp(git_commit_message(commit), ROOT_COMMIT_MESSAGE)); + + /* Remove the data we just added to the repo */ + must_pass(git_reference_lookup(&head, repo, "HEAD")); + must_pass(git_reference_set_target(head, head_old)); + must_pass(git_reference_delete(branch)); + must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)commit)); + free(head_old); + git_commit_close(commit); + git_repository_free(repo); +END_TEST BEGIN_SUITE(commit) ADD_TEST(parse0); @@ -474,4 +549,6 @@ BEGIN_SUITE(commit) ADD_TEST(write0); //ADD_TEST(write1); + + ADD_TEST(root0); END_SUITE diff --git a/tests/t07-hashtable.c b/tests/t07-hashtable.c index 597136965..0b362cafd 100644 --- a/tests/t07-hashtable.c +++ b/tests/t07-hashtable.c @@ -155,7 +155,7 @@ BEGIN_TEST(tableit0, "iterate through all the contents of the table") const int objects_n = 32; int i; table_item *objects, *ob; - const void *_unused; + const void *GIT_UNUSED(_unused); git_hashtable *table = NULL; diff --git a/tests/t09-tree.c b/tests/t09-tree.c index bd88642fa..af992fdb3 100644 --- a/tests/t09-tree.c +++ b/tests/t09-tree.c @@ -32,6 +32,7 @@ static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92"; static const char *first_tree = "181037049a54a1eb5fab404658a3a250b44335d7"; static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1"; +static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488"; #if 0 static int print_tree(git_repository *repo, const git_oid *tree_oid, int depth) @@ -82,6 +83,7 @@ BEGIN_TEST(read0, "acces randomly the entries on a loaded tree") must_be_true(git_tree_entry_byindex(tree, 3) == NULL); must_be_true(git_tree_entry_byindex(tree, -1) == NULL); + git_tree_close(tree); git_repository_free(repo); END_TEST @@ -102,7 +104,9 @@ BEGIN_TEST(read1, "read a tree from the repository") /* GH-86: git_object_lookup() should also check the type if the object comes from the cache */ must_be_true(git_object_lookup(&obj, repo, &id, GIT_OBJ_TREE) == 0); + git_object_close(obj); must_be_true(git_object_lookup(&obj, repo, &id, GIT_OBJ_BLOB) == GIT_EINVALIDTYPE); + git_object_close(obj); entry = git_tree_entry_byname(tree, "README"); must_be_true(entry != NULL); @@ -111,6 +115,8 @@ BEGIN_TEST(read1, "read a tree from the repository") must_pass(git_tree_entry_2object(&obj, repo, entry)); + git_object_close(obj); + git_tree_close(tree); git_repository_free(repo); END_TEST @@ -148,9 +154,50 @@ BEGIN_TEST(write2, "write a tree from a memory") must_pass(git_treebuilder_write(&rid,repo,builder)); must_be_true(git_oid_cmp(&rid, &id2) == 0); + + git_treebuilder_free(builder); + git_tree_close(tree); close_temp_repo(repo); END_TEST +BEGIN_TEST(write3, "write a hierarchical tree from a memory") + git_repository *repo; + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid, subtree_id, id2, id3; + git_oid id_hiearar; + + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); + git_oid_mkstr(&id, first_tree); + git_oid_mkstr(&id2, second_tree); + git_oid_mkstr(&id3, third_tree); + git_oid_mkstr(&bid, blob_oid); + + //create subtree + must_pass(git_treebuilder_create(&builder, NULL)); + must_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); + must_pass(git_treebuilder_write(&subtree_id,repo,builder)); + git_treebuilder_free(builder); + + // create parent tree + must_pass(git_tree_lookup(&tree, repo, &id)); + must_pass(git_treebuilder_create(&builder, tree)); + must_pass(git_treebuilder_insert(NULL,builder,"new",&subtree_id,040000)); + must_pass(git_treebuilder_write(&id_hiearar,repo,builder)); + git_treebuilder_free(builder); + git_tree_close(tree); + + must_be_true(git_oid_cmp(&id_hiearar, &id3) == 0); + + // check data is correct + must_pass(git_tree_lookup(&tree, repo, &id_hiearar)); + must_be_true(2 == git_tree_entrycount(tree)); + git_tree_close(tree); + + close_temp_repo(repo); + +END_TEST + BEGIN_SUITE(tree) //ADD_TEST(print0); ADD_TEST(read0); @@ -158,5 +205,6 @@ BEGIN_SUITE(tree) //ADD_TEST(write0); //ADD_TEST(write1); ADD_TEST(write2); + ADD_TEST(write3); END_SUITE diff --git a/tests/t10-refs.c b/tests/t10-refs.c index a6a560193..ee006a8ce 100644 --- a/tests/t10-refs.c +++ b/tests/t10-refs.c @@ -51,6 +51,7 @@ BEGIN_TEST(readtag0, "lookup a loose tag reference") git__joinpath(ref_name_from_tag_name, GIT_REFS_TAGS_DIR, git_tag_name((git_tag *)object)); must_be_true(strcmp(ref_name_from_tag_name, loose_tag_ref_name) == 0); + git_object_close(object); git_repository_free(repo); END_TEST @@ -91,6 +92,7 @@ BEGIN_TEST(readsym0, "lookup a symbolic reference") git_oid_mkstr(&id, current_master_tip); must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0); + git_object_close(object); git_repository_free(repo); END_TEST @@ -117,6 +119,7 @@ BEGIN_TEST(readsym1, "lookup a nested symbolic reference") git_oid_mkstr(&id, current_master_tip); must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0); + git_object_close(object); git_repository_free(repo); END_TEST @@ -175,6 +178,7 @@ BEGIN_TEST(readpacked0, "lookup a packed reference") must_be_true(object != NULL); must_be_true(git_object_type(object) == GIT_OBJ_COMMIT); + git_object_close(object); git_repository_free(repo); END_TEST @@ -194,7 +198,7 @@ END_TEST BEGIN_TEST(create0, "create a new symbolic reference") git_reference *new_reference, *looked_up_ref, *resolved_ref; - git_repository *repo; + git_repository *repo, *repo2; git_oid id; char ref_path[GIT_PATH_MAX]; @@ -202,7 +206,7 @@ BEGIN_TEST(create0, "create a new symbolic reference") git_oid_mkstr(&id, current_master_tip); - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); /* Retrieve the physical path to the symbolic ref for further cleaning */ git__joinpath(ref_path, repo->path_repository, new_head_tracker); @@ -226,14 +230,13 @@ BEGIN_TEST(create0, "create a new symbolic reference") git_repository_free(repo); /* Similar test with a fresh new repository */ - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER)); - must_pass(git_reference_lookup(&looked_up_ref, repo, new_head_tracker)); + must_pass(git_reference_lookup(&looked_up_ref, repo2, new_head_tracker)); must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); - git_reference_delete(looked_up_ref); - git_repository_free(repo); + close_temp_repo(repo2); END_TEST BEGIN_TEST(create1, "create a deep symbolic reference") @@ -246,7 +249,7 @@ BEGIN_TEST(create1, "create a deep symbolic reference") git_oid_mkstr(&id, current_master_tip); - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); git__joinpath(ref_path, repo->path_repository, new_head_tracker); must_pass(git_reference_create_symbolic(&new_reference, repo, new_head_tracker, current_head_target)); @@ -254,13 +257,12 @@ BEGIN_TEST(create1, "create a deep symbolic reference") must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); - git_reference_delete(looked_up_ref); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(create2, "create a new OID reference") git_reference *new_reference, *looked_up_ref; - git_repository *repo; + git_repository *repo, *repo2; git_oid id; char ref_path[GIT_PATH_MAX]; @@ -268,7 +270,7 @@ BEGIN_TEST(create2, "create a new OID reference") git_oid_mkstr(&id, current_master_tip); - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); /* Retrieve the physical path to the symbolic ref for further cleaning */ git__joinpath(ref_path, repo->path_repository, new_head); @@ -288,13 +290,12 @@ BEGIN_TEST(create2, "create a new OID reference") git_repository_free(repo); /* Similar test with a fresh new repository */ - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_repository_open(&repo2, TEMP_REPO_FOLDER)); - must_pass(git_reference_lookup(&looked_up_ref, repo, new_head)); + must_pass(git_reference_lookup(&looked_up_ref, repo2, new_head)); must_be_true(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0); - git_reference_delete(looked_up_ref); - git_repository_free(repo); + close_temp_repo(repo2); END_TEST BEGIN_TEST(create3, "Can not create a new OID reference which targets at an unknown id") @@ -325,7 +326,7 @@ BEGIN_TEST(overwrite0, "Overwrite an existing symbolic reference") git_reference *ref, *branch_ref; git_repository *repo; - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); /* The target needds to exist and we need to check the name has changed */ must_pass(git_reference_create_symbolic(&branch_ref, repo, ref_branch_name, ref_master_name)); @@ -344,9 +345,7 @@ BEGIN_TEST(overwrite0, "Overwrite an existing symbolic reference") must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC); must_be_true(!strcmp(git_reference_target(ref), ref_master_name)); - must_pass(git_reference_delete(ref)); - must_pass(git_reference_delete(branch_ref)); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(overwrite1, "Overwrite an existing object id reference") @@ -354,7 +353,7 @@ BEGIN_TEST(overwrite1, "Overwrite an existing object id reference") git_repository *repo; git_oid id; - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); must_pass(git_reference_lookup(&ref, repo, ref_master_name)); must_be_true(ref->type & GIT_REF_OID); @@ -375,8 +374,7 @@ BEGIN_TEST(overwrite1, "Overwrite an existing object id reference") must_pass(git_reference_lookup(&ref, repo, ref_name)); must_be_true(!git_oid_cmp(&id, git_reference_oid(ref))); - git_reference_delete(ref); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(overwrite2, "Overwrite an existing object id reference with a symbolic one") @@ -384,7 +382,7 @@ BEGIN_TEST(overwrite2, "Overwrite an existing object id reference with a symboli git_repository *repo; git_oid id; - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); must_pass(git_reference_lookup(&ref, repo, ref_master_name)); must_be_true(ref->type & GIT_REF_OID); @@ -399,8 +397,7 @@ BEGIN_TEST(overwrite2, "Overwrite an existing object id reference with a symboli must_be_true(git_reference_type(ref) & GIT_REF_SYMBOLIC); must_be_true(!strcmp(git_reference_target(ref), ref_master_name)); - git_reference_delete(ref); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(overwrite3, "Overwrite an existing symbolic reference with an object id one") @@ -408,7 +405,7 @@ BEGIN_TEST(overwrite3, "Overwrite an existing symbolic reference with an object git_repository *repo; git_oid id; - must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); must_pass(git_reference_lookup(&ref, repo, ref_master_name)); must_be_true(ref->type & GIT_REF_OID); @@ -425,8 +422,7 @@ BEGIN_TEST(overwrite3, "Overwrite an existing symbolic reference with an object must_be_true(git_reference_type(ref) & GIT_REF_OID); must_be_true(!git_oid_cmp(git_reference_oid(ref), &id)); - git_reference_delete(ref); - git_repository_free(repo); + close_temp_repo(repo); END_TEST BEGIN_TEST(pack0, "create a packfile for an empty folder") diff --git a/tests/t15-config.c b/tests/t15-config.c new file mode 100644 index 000000000..50d473580 --- /dev/null +++ b/tests/t15-config.c @@ -0,0 +1,170 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +#include "test_lib.h" +#include "test_helpers.h" + +#include + +#define CONFIG_BASE TEST_RESOURCES "/config" + +/* + * This one is so we know the code isn't completely broken + */ +BEGIN_TEST(config0, "read a simple configuration") + git_config *cfg; + int i; + + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config0")); + must_pass(git_config_get_int(cfg, "core.repositoryformatversion", &i)); + must_be_true(i == 0); + must_pass(git_config_get_bool(cfg, "core.filemode", &i)); + must_be_true(i == 1); + must_pass(git_config_get_bool(cfg, "core.bare", &i)); + must_be_true(i == 0); + must_pass(git_config_get_bool(cfg, "core.logallrefupdates", &i)); + must_be_true(i == 1); + + git_config_free(cfg); +END_TEST + +/* + * [this "that"] and [this "That] are different namespaces. Make sure + * each returns the correct one. + */ +BEGIN_TEST(config1, "case sensitivity") + git_config *cfg; + int i; + const char *str; + + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config1")); + + must_pass(git_config_get_string(cfg, "this.that.other", &str)); + must_be_true(!strcmp(str, "true")); + must_pass(git_config_get_string(cfg, "this.That.other", &str)); + must_be_true(!strcmp(str, "yes")); + + must_pass(git_config_get_bool(cfg, "this.that.other", &i)); + must_be_true(i == 1); + must_pass(git_config_get_bool(cfg, "this.That.other", &i)); + must_be_true(i == 1); + + /* This one doesn't exist */ + must_fail(git_config_get_bool(cfg, "this.thaT.other", &i)); + + git_config_free(cfg); +END_TEST + +/* + * If \ is the last non-space character on the line, we read the next + * one, separating each line with SP. + */ +BEGIN_TEST(config2, "parse a multiline value") + git_config *cfg; + const char *str; + + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config2")); + + must_pass(git_config_get_string(cfg, "this.That.and", &str)); + must_be_true(!strcmp(str, "one one one two two three three")); + + git_config_free(cfg); +END_TEST + +/* + * This kind of subsection declaration is case-insensitive + */ +BEGIN_TEST(config3, "parse a [section.subsection] header") + git_config *cfg; + const char *str; + + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config3")); + + must_pass(git_config_get_string(cfg, "section.subsection.var", &str)); + must_be_true(!strcmp(str, "hello")); + + /* Avoid a false positive */ + str = "nohello"; + must_pass(git_config_get_string(cfg, "section.subSectIon.var", &str)); + must_be_true(!strcmp(str, "hello")); + + git_config_free(cfg); +END_TEST + +BEGIN_TEST(config4, "a variable name on its own is valid") + git_config *cfg; +const char *str; +int i; + + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config4")); + + must_pass(git_config_get_string(cfg, "some.section.variable", &str)); + must_be_true(str == NULL); + + must_pass(git_config_get_bool(cfg, "some.section.variable", &i)); + must_be_true(i == 1); + + + git_config_free(cfg); +END_TEST + +BEGIN_TEST(config5, "test number suffixes") + git_config *cfg; + long int i; + + must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config5")); + + must_pass(git_config_get_long(cfg, "number.simple", &i)); + must_be_true(i == 1); + + must_pass(git_config_get_long(cfg, "number.k", &i)); + must_be_true(i == 1 * 1024); + + must_pass(git_config_get_long(cfg, "number.kk", &i)); + must_be_true(i == 1 * 1024); + + must_pass(git_config_get_long(cfg, "number.m", &i)); + must_be_true(i == 1 * 1024 * 1024); + + must_pass(git_config_get_long(cfg, "number.mm", &i)); + must_be_true(i == 1 * 1024 * 1024); + + must_pass(git_config_get_long(cfg, "number.g", &i)); + must_be_true(i == 1 * 1024 * 1024 * 1024); + + must_pass(git_config_get_long(cfg, "number.gg", &i)); + must_be_true(i == 1 * 1024 * 1024 * 1024); + + git_config_free(cfg); +END_TEST + + +BEGIN_SUITE(config) + ADD_TEST(config0); + ADD_TEST(config1); + ADD_TEST(config2); + ADD_TEST(config3); + ADD_TEST(config4); + ADD_TEST(config5); +END_SUITE diff --git a/tests/test_lib.c b/tests/test_lib.c index c9c6141c6..5778404c1 100755 --- a/tests/test_lib.c +++ b/tests/test_lib.c @@ -130,6 +130,7 @@ static void free_suite(git_testsuite *ts) if (ts->list[n]) test_free(ts->list[n]); + free(ts->name); free(ts); } diff --git a/tests/test_main.c b/tests/test_main.c index 102688ce1..0f5f16a26 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -44,6 +44,7 @@ DECLARE_SUITE(sqlite); DECLARE_SUITE(hiredis); DECLARE_SUITE(repository); DECLARE_SUITE(threads); +DECLARE_SUITE(config); static libgit2_suite suite_methods[]= { SUITE_NAME(core), @@ -60,7 +61,8 @@ static libgit2_suite suite_methods[]= { SUITE_NAME(sqlite), SUITE_NAME(repository), SUITE_NAME(threads), - SUITE_NAME(hiredis) + SUITE_NAME(hiredis), + SUITE_NAME(config), }; #define GIT_SUITE_COUNT (ARRAY_SIZE(suite_methods)) diff --git a/wscript b/wscript index d97538ab0..f4f8da989 100644 --- a/wscript +++ b/wscript @@ -65,6 +65,9 @@ def configure(conf): else: conf.env.PLATFORM = 'unix' + if conf.env.DEST_OS == 'sunos': + conf.env.DEFINES += ['NO_VIZ'] + if conf.options.threadsafe: if conf.env.PLATFORM == 'unix': conf.check_cc(lib='pthread', uselib_store='pthread')