diff options
115 files changed, 2544 insertions, 846 deletions
diff --git a/.travis.yml b/.travis.yml index fcae726dd..bab02bb44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,10 @@ language: c +os: + - linux + - osx + compiler: - gcc - clang @@ -17,17 +21,21 @@ env: matrix: fast_finish: true + exclude: + - os: osx + compiler: gcc include: - compiler: i586-mingw32msvc-gcc env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON -DUSE_SSH=OFF" + os: linux - compiler: gcc env: COVERITY=1 + os: linux allow_failures: - env: COVERITY=1 install: - - sudo apt-get -qq update - - sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server + - ./script/install-deps-${TRAVIS_OS_NAME}.sh # Run the Build script and tests script: @@ -35,8 +43,8 @@ script: # Run Tests after_success: - - sudo apt-get -qq install valgrind - - valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install valgrind; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline; fi # Only watch the development branch branches: diff --git a/PROJECTS.md b/PROJECTS.md index 9472fb43c..d17214471 100644 --- a/PROJECTS.md +++ b/PROJECTS.md @@ -39,12 +39,6 @@ These are good small projects to get started with libgit2. the data is available, you would just need to add the code into the `print_commit()` routine (along with a way of passing the option into that function). - * For `examples/log.c`, implement any one of `--author=<...>`, - `--committer=<...>`, or `--grep=<...>` but just use simple string - match with `strstr()` instead of full regular expression - matching. (I.e. I'm suggesting implementing this as if - `--fixed-strings` was always turned on, because it will be a simpler - project.) * As an extension to the matching idea for `examples/log.c`, add the `-i` option to use `strcasestr()` for matches. * For `examples/log.c`, implement the `--first-parent` option now that @@ -189,6 +189,8 @@ Here are the bindings to libgit2 that are currently available: * GitPowerShell <https://github.com/ethomson/gitpowershell> * Python * pygit2 <https://github.com/libgit2/pygit2> +* R + * git2r <https://github.com/ropensci/git2r> * Ruby * Rugged <https://github.com/libgit2/rugged> * Vala diff --git a/examples/log.c b/examples/log.c index 471c5ff96..d5f75a297 100644 --- a/examples/log.c +++ b/examples/log.c @@ -54,8 +54,9 @@ struct log_options { int min_parents, max_parents; git_time_t before; git_time_t after; - char *author; - char *committer; + const char *author; + const char *committer; + const char *grep; }; /** utility functions that parse options and help with log output */ @@ -65,6 +66,9 @@ static void print_time(const git_time *intime, const char *prefix); static void print_commit(git_commit *commit); static int match_with_parent(git_commit *commit, int i, git_diff_options *); +/** utility functions for filtering */ +static int signature_matches(const git_signature *sig, const char *filter); +static int log_message_matches(const git_commit *commit, const char *filter); int main(int argc, char *argv[]) { @@ -128,6 +132,15 @@ int main(int argc, char *argv[]) continue; } + if (!signature_matches(git_commit_author(commit), opt.author)) + continue; + + if (!signature_matches(git_commit_committer(commit), opt.committer)) + continue; + + if (!log_message_matches(commit, opt.grep)) + continue; + if (count++ < opt.skip) continue; if (opt.limit != -1 && printed++ >= opt.limit) { @@ -172,6 +185,32 @@ int main(int argc, char *argv[]) return 0; } +/** Determine if the given git_signature does not contain the filter text. */ +static int signature_matches(const git_signature *sig, const char *filter) { + if (filter == NULL) + return 1; + + if (sig != NULL && + (strstr(sig->name, filter) != NULL || + strstr(sig->email, filter) != NULL)) + return 1; + + return 0; +} + +static int log_message_matches(const git_commit *commit, const char *filter) { + const char *message = NULL; + + if (filter == NULL) + return 1; + + if ((message = git_commit_message(commit)) != NULL && + strstr(message, filter) != NULL) + return 1; + + return 0; +} + /** Push object (for hide or show) onto revwalker. */ static void push_rev(struct log_state *s, git_object *obj, int hide) { @@ -401,6 +440,12 @@ static int parse_options( set_sorting(s, GIT_SORT_TOPOLOGICAL); else if (!strcmp(a, "--reverse")) set_sorting(s, GIT_SORT_REVERSE); + else if (match_str_arg(&opt->author, &args, "--author")) + /** Found valid --author */; + else if (match_str_arg(&opt->committer, &args, "--committer")) + /** Found valid --committer */; + else if (match_str_arg(&opt->grep, &args, "--grep")) + /** Found valid --grep */; else if (match_str_arg(&s->repodir, &args, "--git-dir")) /** Found git-dir. */; else if (match_int_arg(&opt->skip, &args, "--skip", 0)) diff --git a/examples/status.c b/examples/status.c index 9c99744cb..a59f34454 100644 --- a/examples/status.c +++ b/examples/status.c @@ -14,9 +14,10 @@ #include "common.h" #ifdef _WIN32 -#define sleep(a) Sleep(a * 1000) +# include <Windows.h> +# define sleep(a) Sleep(a * 1000) #else -#include <unistd.h> +# include <unistd.h> #endif /** diff --git a/include/git2/blob.h b/include/git2/blob.h index 1b6583309..c24ff7e7f 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -210,7 +210,7 @@ GIT_EXTERN(int) git_blob_create_frombuffer( * * The heuristic used to guess if a file is binary is taken from core git: * Searching for NUL bytes and looking for a reasonable ratio of printable - * to non-printable characters among the first 4000 bytes. + * to non-printable characters among the first 8000 bytes. * * @param blob The blob which content should be analyzed * @return 1 if the content of the blob is detected diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 494f67456..ad44173e6 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -43,17 +43,17 @@ GIT_BEGIN_DECL * In between those are `GIT_CHECKOUT_SAFE` and `GIT_CHECKOUT_SAFE_CREATE` * both of which only make modifications that will not lose changes. * - * | target == baseline | target != baseline | - * ---------------------|-----------------------|----------------------| - * workdir == baseline | no action | create, update, or | - * | | delete file | - * ---------------------|-----------------------|----------------------| - * workdir exists and | no action | conflict (notify | - * is != baseline | notify dirty MODIFIED | and cancel checkout) | - * ---------------------|-----------------------|----------------------| - * workdir missing, | create if SAFE_CREATE | create file | - * baseline present | notify dirty DELETED | | - * ---------------------|-----------------------|----------------------| + * | target == baseline | target != baseline | + * ---------------------|-----------------------|----------------------| + * workdir == baseline | no action | create, update, or | + * | | delete file | + * ---------------------|-----------------------|----------------------| + * workdir exists and | no action | conflict (notify | + * is != baseline | notify dirty MODIFIED | and cancel checkout) | + * ---------------------|-----------------------|----------------------| + * workdir missing, | create if SAFE_CREATE | create file | + * baseline present | notify dirty DELETED | | + * ---------------------|-----------------------|----------------------| * * The only difference between SAFE and SAFE_CREATE is that SAFE_CREATE * will cause a file to be checked out if it is missing from the working @@ -106,7 +106,7 @@ GIT_BEGIN_DECL * target contains that file. */ typedef enum { - GIT_CHECKOUT_NONE = 0, /** default is a dry run, no actual updates */ + GIT_CHECKOUT_NONE = 0, /**< default is a dry run, no actual updates */ /** Allow safe updates that cannot overwrite uncommitted data */ GIT_CHECKOUT_SAFE = (1u << 0), diff --git a/include/git2/cherrypick.h b/include/git2/cherrypick.h index e998d325f..9eccb0af9 100644 --- a/include/git2/cherrypick.h +++ b/include/git2/cherrypick.h @@ -56,7 +56,7 @@ GIT_EXTERN(int) git_cherry_pick_init_options( * @param cherry_pick_commit the commit to cherry-pick * @param our_commit the commit to revert against (eg, HEAD) * @param mainline the parent of the revert commit, if it is a merge - * @param merge_tree_opts the merge tree options (or null for defaults) + * @param merge_options the merge options (or null for defaults) * @return zero on success, -1 on failure. */ GIT_EXTERN(int) git_cherry_pick_commit( diff --git a/include/git2/clone.h b/include/git2/clone.h index 985c04bf6..b2c944a78 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -23,6 +23,13 @@ */ GIT_BEGIN_DECL +typedef enum { + GIT_CLONE_LOCAL_AUTO, + GIT_CLONE_LOCAL, + GIT_CLONE_NO_LOCAL, + GIT_CLONE_LOCAL_NO_LINKS, +} git_clone_local_t; + /** * Clone options structure * @@ -57,6 +64,7 @@ typedef struct git_clone_options { int bare; int ignore_cert_errors; + git_clone_local_t local; const char *remote_name; const char* checkout_branch; git_signature *signature; @@ -123,6 +131,35 @@ GIT_EXTERN(int) git_clone_into( const char *branch, const git_signature *signature); +/** + * Perform a local clone into a repository + * + * A "local clone" bypasses any git-aware protocols and simply copies + * over the object database from the source repository. It is often + * faster than a git-aware clone, but no verification of the data is + * performed, and can copy over too much data. + * + * @param repo the repository to use + * @param remote the remote repository to clone from + * @param co_opts options to use during checkout + * @param branch the branch to checkout after the clone, pass NULL for the + * remote's default branch + * @param link wether to use hardlinks instead of copying + * objects. This is only possible if both repositories are on the same + * filesystem. + * @param signature the identity used when updating the reflog + * @return 0 on success, any non-zero return value from a callback + * function, or a negative value to indicate an error (use + * `giterr_last` for a detailed error message) + */ +GIT_EXTERN(int) git_clone_local_into( + git_repository *repo, + git_remote *remote, + const git_checkout_options *co_opts, + const char *branch, + int link, + const git_signature *signature); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/diff.h b/include/git2/diff.h index db2233f82..f126453f4 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -218,9 +218,9 @@ typedef struct git_diff git_diff; * considered reserved for internal or future use. */ typedef enum { - GIT_DIFF_FLAG_BINARY = (1u << 0), /** file(s) treated as binary data */ - GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /** file(s) treated as text data */ - GIT_DIFF_FLAG_VALID_ID = (1u << 2), /** `id` value is known correct */ + GIT_DIFF_FLAG_BINARY = (1u << 0), /**< file(s) treated as binary data */ + GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /**< file(s) treated as text data */ + GIT_DIFF_FLAG_VALID_ID = (1u << 2), /**< `id` value is known correct */ } git_diff_flag_t; /** @@ -234,16 +234,16 @@ typedef enum { * DELETED pairs). */ typedef enum { - GIT_DELTA_UNMODIFIED = 0, /** no changes */ - GIT_DELTA_ADDED = 1, /** entry does not exist in old version */ - GIT_DELTA_DELETED = 2, /** entry does not exist in new version */ - GIT_DELTA_MODIFIED = 3, /** entry content changed between old and new */ - GIT_DELTA_RENAMED = 4, /** entry was renamed between old and new */ - GIT_DELTA_COPIED = 5, /** entry was copied from another old entry */ - GIT_DELTA_IGNORED = 6, /** entry is ignored item in workdir */ - GIT_DELTA_UNTRACKED = 7, /** entry is untracked item in workdir */ - GIT_DELTA_TYPECHANGE = 8, /** type of entry changed between old and new */ - GIT_DELTA_UNREADABLE = 9, /** entry is unreadable */ + GIT_DELTA_UNMODIFIED = 0, /**< no changes */ + GIT_DELTA_ADDED = 1, /**< entry does not exist in old version */ + GIT_DELTA_DELETED = 2, /**< entry does not exist in new version */ + GIT_DELTA_MODIFIED = 3, /**< entry content changed between old and new */ + GIT_DELTA_RENAMED = 4, /**< entry was renamed between old and new */ + GIT_DELTA_COPIED = 5, /**< entry was copied from another old entry */ + GIT_DELTA_IGNORED = 6, /**< entry is ignored item in workdir */ + GIT_DELTA_UNTRACKED = 7, /**< entry is untracked item in workdir */ + GIT_DELTA_TYPECHANGE = 8, /**< type of entry changed between old and new */ + GIT_DELTA_UNREADABLE = 9, /**< entry is unreadable */ } git_delta_t; /** @@ -423,12 +423,12 @@ typedef int (*git_diff_file_cb)( */ typedef struct git_diff_hunk git_diff_hunk; struct git_diff_hunk { - int old_start; /** Starting line number in old_file */ - int old_lines; /** Number of lines in old_file */ - int new_start; /** Starting line number in new_file */ - int new_lines; /** Number of lines in new_file */ - size_t header_len; /** Number of bytes in header text */ - char header[128]; /** Header text, NUL-byte terminated */ + int old_start; /**< Starting line number in old_file */ + int old_lines; /**< Number of lines in old_file */ + int new_start; /**< Starting line number in new_file */ + int new_lines; /**< Number of lines in new_file */ + size_t header_len; /**< Number of bytes in header text */ + char header[128]; /**< Header text, NUL-byte terminated */ }; /** @@ -471,13 +471,13 @@ typedef enum { */ typedef struct git_diff_line git_diff_line; struct git_diff_line { - char origin; /** A git_diff_line_t value */ - int old_lineno; /** Line number in old file or -1 for added line */ - int new_lineno; /** Line number in new file or -1 for deleted line */ - int num_lines; /** Number of newline characters in content */ - size_t content_len; /** Number of bytes of data */ - git_off_t content_offset; /** Offset in the original file to the content */ - const char *content; /** Pointer to diff text, not NUL-byte terminated */ + char origin; /**< A git_diff_line_t value */ + int old_lineno; /**< Line number in old file or -1 for added line */ + int new_lineno; /**< Line number in new file or -1 for deleted line */ + int num_lines; /**< Number of newline characters in content */ + size_t content_len; /**< Number of bytes of data */ + git_off_t content_offset; /**< Offset in the original file to the content */ + const char *content; /**< Pointer to diff text, not NUL-byte terminated */ }; /** @@ -489,10 +489,10 @@ struct git_diff_line { * of lines of file and hunk headers. */ typedef int (*git_diff_line_cb)( - const git_diff_delta *delta, /** delta that contains this data */ - const git_diff_hunk *hunk, /** hunk containing this data */ - const git_diff_line *line, /** line data */ - void *payload); /** user reference data */ + const git_diff_delta *delta, /**< delta that contains this data */ + const git_diff_hunk *hunk, /**< hunk containing this data */ + const git_diff_line *line, /**< line data */ + void *payload); /**< user reference data */ /** * Flags to control the behavior of diff rename/copy detection. diff --git a/include/git2/errors.h b/include/git2/errors.h index e22f0d86d..c914653fc 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -19,13 +19,13 @@ GIT_BEGIN_DECL /** Generic return codes */ typedef enum { - GIT_OK = 0, /*< No error */ + GIT_OK = 0, /**< No error */ - GIT_ERROR = -1, /*< Generic error */ - GIT_ENOTFOUND = -3, /*< Requested object could not be found */ - GIT_EEXISTS = -4, /*< Object exists preventing operation */ - GIT_EAMBIGUOUS = -5, /*< More than one object matches */ - GIT_EBUFS = -6, /*< Output buffer too short to hold data */ + GIT_ERROR = -1, /**< Generic error */ + GIT_ENOTFOUND = -3, /**< Requested object could not be found */ + GIT_EEXISTS = -4, /**< Object exists preventing operation */ + GIT_EAMBIGUOUS = -5, /**< More than one object matches */ + GIT_EBUFS = -6, /**< Output buffer too short to hold data */ /* GIT_EUSER is a special error that is never generated by libgit2 * code. You can return it from a callback (e.g to stop an iteration) @@ -33,17 +33,17 @@ typedef enum { */ GIT_EUSER = -7, - GIT_EBAREREPO = -8, /*< Operation not allowed on bare repository */ - GIT_EUNBORNBRANCH = -9, /*< HEAD refers to branch with no commits */ - GIT_EUNMERGED = -10, /*< Merge in progress prevented operation */ - GIT_ENONFASTFORWARD = -11, /*< Reference was not fast-forwardable */ - GIT_EINVALIDSPEC = -12, /*< Name/ref spec was not in a valid format */ - GIT_EMERGECONFLICT = -13, /*< Merge conflicts prevented operation */ - GIT_ELOCKED = -14, /*< Lock file prevented operation */ - GIT_EMODIFIED = -15, /*< Reference value does not match expected */ + GIT_EBAREREPO = -8, /**< Operation not allowed on bare repository */ + GIT_EUNBORNBRANCH = -9, /**< HEAD refers to branch with no commits */ + GIT_EUNMERGED = -10, /**< Merge in progress prevented operation */ + GIT_ENONFASTFORWARD = -11, /**< Reference was not fast-forwardable */ + GIT_EINVALIDSPEC = -12, /**< Name/ref spec was not in a valid format */ + GIT_EMERGECONFLICT = -13, /**< Merge conflicts prevented operation */ + GIT_ELOCKED = -14, /**< Lock file prevented operation */ + GIT_EMODIFIED = -15, /**< Reference value does not match expected */ - GIT_PASSTHROUGH = -30, /*< Internal only */ - GIT_ITEROVER = -31, /*< Signals end of iteration with iterator */ + GIT_PASSTHROUGH = -30, /**< Internal only */ + GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */ } git_error_code; /** diff --git a/include/git2/index.h b/include/git2/index.h index cdb87282c..0b4476b4e 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -73,10 +73,13 @@ typedef struct git_index_entry { */ #define GIT_IDXENTRY_NAMEMASK (0x0fff) #define GIT_IDXENTRY_STAGEMASK (0x3000) -#define GIT_IDXENTRY_EXTENDED (0x4000) -#define GIT_IDXENTRY_VALID (0x8000) #define GIT_IDXENTRY_STAGESHIFT 12 +typedef enum { + GIT_IDXENTRY_EXTENDED = (0x4000), + GIT_IDXENTRY_VALID = (0x8000), +} git_indxentry_flag_t; + #define GIT_IDXENTRY_STAGE(E) \ (((E)->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT) @@ -92,36 +95,36 @@ typedef struct git_index_entry { * in-memory only and used by libgit2. Only the flags in * `GIT_IDXENTRY_EXTENDED_FLAGS` will get saved on-disk. * - * These bitmasks match the three fields in the `git_index_entry` - * `flags_extended` value that belong on disk. You can use them to - * interpret the data in the `flags_extended`. + * Thee first three bitmasks match the three fields in the + * `git_index_entry` `flags_extended` value that belong on disk. You + * can use them to interpret the data in the `flags_extended`. + * + * The rest of the bitmasks match the other fields in the `git_index_entry` + * `flags_extended` value that are only used in-memory by libgit2. + * You can use them to interpret the data in the `flags_extended`. + * */ -#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13) -#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14) -/* GIT_IDXENTRY_EXTENDED2 is reserved for future extension */ -#define GIT_IDXENTRY_EXTENDED2 (1 << 15) +typedef enum { -#define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) + GIT_IDXENTRY_INTENT_TO_ADD = (1 << 13), + GIT_IDXENTRY_SKIP_WORKTREE = (1 << 14), + /** Reserved for future extension */ + GIT_IDXENTRY_EXTENDED2 = (1 << 15), -/** - * Bitmasks for in-memory only fields of `git_index_entry`'s `flags_extended` - * - * These bitmasks match the other fields in the `git_index_entry` - * `flags_extended` value that are only used in-memory by libgit2. You - * can use them to interpret the data in the `flags_extended`. - */ -#define GIT_IDXENTRY_UPDATE (1 << 0) -#define GIT_IDXENTRY_REMOVE (1 << 1) -#define GIT_IDXENTRY_UPTODATE (1 << 2) -#define GIT_IDXENTRY_ADDED (1 << 3) + GIT_IDXENTRY_EXTENDED_FLAGS = (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE), + GIT_IDXENTRY_UPDATE = (1 << 0), + GIT_IDXENTRY_REMOVE = (1 << 1), + GIT_IDXENTRY_UPTODATE = (1 << 2), + GIT_IDXENTRY_ADDED = (1 << 3), -#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) + GIT_IDXENTRY_HASHED = (1 << 4), + GIT_IDXENTRY_UNHASHED = (1 << 5), + GIT_IDXENTRY_WT_REMOVE = (1 << 6), /**< remove in work directory */ + GIT_IDXENTRY_CONFLICTED = (1 << 7), -#define GIT_IDXENTRY_UNPACKED (1 << 8) -#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9) + GIT_IDXENTRY_UNPACKED = (1 << 8), + GIT_IDXENTRY_NEW_SKIP_WORKTREE = (1 << 9), +} git_idxentry_extended_flag_t; /** Capabilities of system that affect index actions. */ typedef enum { @@ -412,10 +415,10 @@ GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_en * * This entry is calculated from the entry's flag attribute like this: * - * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT + * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT * * @param entry The entry - * @returns the stage number + * @return the stage number */ GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); diff --git a/include/git2/merge.h b/include/git2/merge.h index abbc3a5bb..9eb14ccb1 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -268,6 +268,26 @@ typedef enum { GIT_MERGE_ANALYSIS_UNBORN = (1 << 3), } git_merge_analysis_t; +typedef enum { + /* + * No configuration was found that suggests a preferred behavior for + * merge. + */ + GIT_MERGE_PREFERENCE_NONE = 0, + + /** + * There is a `merge.ff=false` configuration setting, suggesting that + * the user does not want to allow a fast-forward merge. + */ + GIT_MERGE_PREFERENCE_NO_FASTFORWARD = (1 << 0), + + /** + * There is a `merge.ff=only` configuration setting, suggesting that + * the user only wants fast-forward merges. + */ + GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY = (1 << 1), +} git_merge_preference_t; + /** * Analyzes the given branch(es) and determines the opportunities for * merging them into the HEAD of the repository. @@ -280,6 +300,7 @@ typedef enum { */ GIT_EXTERN(int) git_merge_analysis( git_merge_analysis_t *analysis_out, + git_merge_preference_t *preference_out, git_repository *repo, const git_merge_head **their_heads, size_t their_heads_len); @@ -378,8 +399,8 @@ GIT_EXTERN(int) git_merge_head_from_id( /** * Gets the commit ID that the given `git_merge_head` refers to. * - * @param id pointer to commit id to be filled in * @param head the given merge head + * @return commit id */ GIT_EXTERN(const git_oid *) git_merge_head_id( const git_merge_head *head); @@ -424,8 +445,8 @@ GIT_EXTERN(int) git_merge_file( * @param out The git_merge_file_result to be filled in * @param repo The repository * @param ancestor The index entry for the ancestor file (stage level 1) - * @param our_path The index entry for our file (stage level 2) - * @param their_path The index entry for their file (stage level 3) + * @param ours The index entry for our file (stage level 2) + * @param theirs The index entry for their file (stage level 3) * @param opts The merge file options or NULL * @return 0 on success or error code */ @@ -497,8 +518,8 @@ GIT_EXTERN(int) git_merge_commits( * completes, resolve any conflicts and prepare a commit. * * @param repo the repository to merge - * @param merge_heads the heads to merge into - * @param merge_heads_len the number of heads to merge + * @param their_heads the heads to merge into + * @param their_heads_len the number of heads to merge * @param merge_opts merge options * @param checkout_opts checkout options * @return 0 on success or error code diff --git a/include/git2/message.h b/include/git2/message.h index bcdb72f6a..d78b1dce5 100644 --- a/include/git2/message.h +++ b/include/git2/message.h @@ -29,12 +29,14 @@ GIT_BEGIN_DECL * * @param message The message to be prettified. * - * @param strip_comments Non-zero to remove lines starting with "#", 0 to - * leave them in. + * @param strip_comments Non-zero to remove comment lines, 0 to leave them in. + * + * @param comment_char Comment character. Lines starting with this character + * are considered to be comments and removed if `strip_comments` is non-zero. * * @return 0 or an error code. */ -GIT_EXTERN(int) git_message_prettify(git_buf *out, const char *message, int strip_comments); +GIT_EXTERN(int) git_message_prettify(git_buf *out, const char *message, int strip_comments, char comment_char); /** @} */ GIT_END_DECL diff --git a/include/git2/net.h b/include/git2/net.h index e70ba1f71..a727696a2 100644 --- a/include/git2/net.h +++ b/include/git2/net.h @@ -41,6 +41,11 @@ struct git_remote_head { git_oid oid; git_oid loid; char *name; + /** + * If the server send a symref mapping for this ref, this will + * point to the target. + */ + char *symref_target; }; /** diff --git a/include/git2/reflog.h b/include/git2/reflog.h index df06e1b8e..ac42a231c 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -69,7 +69,7 @@ GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *id, const g * * @param repo the repository * @param old_name the old name of the reference - * @param new_name the new name of the reference + * @param name the new name of the reference * @return 0 on success, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_reflog_rename(git_repository *repo, const char *old_name, const char *name); diff --git a/include/git2/refs.h b/include/git2/refs.h index ae2d379d9..e5bb15c7c 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -178,7 +178,6 @@ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repositor * @param name The name of the reference * @param id The object id pointed to by the reference. * @param force Overwrite existing references - * @param force Overwrite existing references * @param signature The identity that will used to populate the reflog entry * @param log_message The one line long message to be appended to the reflog * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code @@ -221,7 +220,6 @@ GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, * @param name The name of the reference * @param id The object id pointed to by the reference. * @param force Overwrite existing references - * @param force Overwrite existing references * @param current_id The expected value of the reference at the time of update * @param signature The identity that will used to populate the reflog entry * @param log_message The one line long message to be appended to the reflog @@ -415,7 +413,7 @@ GIT_EXTERN(int) git_reference_delete(git_reference *ref); * This method removes the named reference from the repository without * looking at its old value. * - * @param ref The reference to remove + * @param name The reference to remove * @return 0 or an error code */ GIT_EXTERN(int) git_reference_remove(git_repository *repo, const char *name); diff --git a/include/git2/remote.h b/include/git2/remote.h index 3633501e2..28771ac42 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -610,6 +610,37 @@ GIT_EXTERN(void) git_remote_set_update_fetchhead(git_remote *remote, int value); */ GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name); +/** +* Delete an existing persisted remote. +* +* All remote-tracking branches and configuration settings +* for the remote will be removed. +* +* once deleted, the passed remote object will be freed and invalidated. +* +* @param remote A valid remote +* @return 0 on success, or an error code. +*/ +GIT_EXTERN(int) git_remote_delete(git_remote *remote); + +/** + * Retrieve the name of the remote's default branch + * + * The default branch of a repository is the branch which HEAD points + * to. If the remote does not support reporting this information + * directly, it performs the guess as git does; that is, if there are + * multiple branches which point to the same commit, the first one is + * chosen. If the master branch is a candidate, it wins. + * + * This function must only be called after connecting. + * + * @param out the buffern in which to store the reference name + * @param remote the remote + * @return 0, GIT_ENOTFOUND if the remote does not have any references + * or none of them point to HEAD's commit, or an error message. + */ +GIT_EXTERN(int) git_remote_default_branch(git_buf *out, git_remote *remote); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/repository.h b/include/git2/repository.h index 037cb3f96..6a8ff4545 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -649,7 +649,7 @@ GIT_EXTERN(int) git_repository_set_head_detached( * * @param repo Repository pointer * @param signature The identity that will used to populate the reflog entry - * @param log_message The one line long message to be appended to the reflog + * @param reflog_message The one line long message to be appended to the reflog * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing * branch or an error code */ diff --git a/include/git2/reset.h b/include/git2/reset.h index 1759cc036..b8c580339 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -19,9 +19,9 @@ GIT_BEGIN_DECL * Kinds of reset operation */ typedef enum { - GIT_RESET_SOFT = 1, /** Move the head to the given commit */ - GIT_RESET_MIXED = 2, /** SOFT plus reset index to the commit */ - GIT_RESET_HARD = 3, /** MIXED plus changes in working tree discarded */ + GIT_RESET_SOFT = 1, /**< Move the head to the given commit */ + GIT_RESET_MIXED = 2, /**< SOFT plus reset index to the commit */ + GIT_RESET_HARD = 3, /**< MIXED plus changes in working tree discarded */ } git_reset_t; /** diff --git a/include/git2/revert.h b/include/git2/revert.h index da37fbe7b..fc1767c93 100644 --- a/include/git2/revert.h +++ b/include/git2/revert.h @@ -56,7 +56,7 @@ GIT_EXTERN(int) git_revert_init_options( * @param revert_commit the commit to revert * @param our_commit the commit to revert against (eg, HEAD) * @param mainline the parent of the revert commit, if it is a merge - * @param merge_tree_opts the merge tree options (or null for defaults) + * @param merge_options the merge options (or null for defaults) * @return zero on success, -1 on failure. */ int git_revert_commit( @@ -71,9 +71,8 @@ int git_revert_commit( * Reverts the given commit, producing changes in the working directory. * * @param repo the repository to revert - * @param commits the commits to revert - * @param commits_len the number of commits to revert - * @param flags merge flags + * @param commit the commit to revert + * @param given_opts merge flags * @return zero on success, -1 on failure. */ GIT_EXTERN(int) git_revert( diff --git a/include/git2/signature.h b/include/git2/signature.h index a1dd1ec7a..feb1b4073 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -69,7 +69,7 @@ GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo) * Call `git_signature_free()` to free the data. * * @param dest pointer where to store the copy - * @param entry signature to duplicate + * @param sig signature to duplicate * @return 0 or an error code */ GIT_EXTERN(int) git_signature_dup(git_signature **dest, const git_signature *sig); diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 28e235725..864d1c58c 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -283,7 +283,7 @@ GIT_EXTERN(const char *) git_submodule_url(git_submodule *submodule); * Resolve a submodule url relative to the given repository. * * @param out buffer to store the absolute submodule url in - * @param repository Pointer to repository object + * @param repo Pointer to repository object * @param url Relative url * @return 0 or an error code */ diff --git a/include/git2/tree.h b/include/git2/tree.h index 6669652ae..56922d40b 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -151,7 +151,7 @@ GIT_EXTERN(int) git_tree_entry_bypath( * and must be freed explicitly with `git_tree_entry_free()`. * * @param dest pointer where to store the copy - * @param entry tree entry to duplicate + * @param source tree entry to duplicate * @return 0 or an error code */ GIT_EXTERN(int) git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source); diff --git a/include/git2/types.h b/include/git2/types.h index 46dfd8b75..3019c4677 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -154,15 +154,15 @@ typedef struct git_packbuilder git_packbuilder; /** Time in a signature */ typedef struct git_time { - git_time_t time; /** time in seconds from epoch */ - int offset; /** timezone offset, in minutes */ + git_time_t time; /**< time in seconds from epoch */ + int offset; /**< timezone offset, in minutes */ } git_time; /** An action signature (e.g. for committers, taggers, etc) */ typedef struct git_signature { - char *name; /** full name of the author */ - char *email; /** email of the author */ - git_time when; /** time when the action happened */ + char *name; /**< full name of the author */ + char *email; /**< email of the author */ + git_time when; /**< time when the action happened */ } git_signature; /** In-memory representation of a reference. */ @@ -183,9 +183,9 @@ typedef struct git_status_list git_status_list; /** Basic type of any Git reference. */ typedef enum { - GIT_REF_INVALID = 0, /** Invalid reference */ - GIT_REF_OID = 1, /** A reference which points at an object id */ - GIT_REF_SYMBOLIC = 2, /** A reference which points at another reference */ + GIT_REF_INVALID = 0, /**< Invalid reference */ + GIT_REF_OID = 1, /**< A reference which points at an object id */ + GIT_REF_SYMBOLIC = 2, /**< A reference which points at another reference */ GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC, } git_ref_t; @@ -314,12 +314,12 @@ typedef enum { * when we don't want any particular ignore rule to be specified. */ typedef enum { - GIT_SUBMODULE_IGNORE_RESET = -1, /* reset to on-disk value */ + GIT_SUBMODULE_IGNORE_RESET = -1, /**< reset to on-disk value */ - GIT_SUBMODULE_IGNORE_NONE = 1, /* any change or untracked == dirty */ - GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ - GIT_SUBMODULE_IGNORE_DIRTY = 3, /* only dirty if HEAD moved */ - GIT_SUBMODULE_IGNORE_ALL = 4, /* never dirty */ + GIT_SUBMODULE_IGNORE_NONE = 1, /**< any change or untracked == dirty */ + GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /**< dirty if tracked files change */ + GIT_SUBMODULE_IGNORE_DIRTY = 3, /**< only dirty if HEAD moved */ + GIT_SUBMODULE_IGNORE_ALL = 4, /**< never dirty */ GIT_SUBMODULE_IGNORE_DEFAULT = 0 } git_submodule_ignore_t; diff --git a/script/install-deps-linux.sh b/script/install-deps-linux.sh new file mode 100755 index 000000000..347922b89 --- /dev/null +++ b/script/install-deps-linux.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -x + +sudo apt-get -qq update && +sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server diff --git a/script/install-deps-osx.sh b/script/install-deps-osx.sh new file mode 100755 index 000000000..c2e0162d8 --- /dev/null +++ b/script/install-deps-osx.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -x + +brew install libssh2 cmake diff --git a/src/attr_file.c b/src/attr_file.c index 156a23d91..3e95a2134 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -281,7 +281,7 @@ uint32_t git_attr_file__name_hash(const char *name) int git_attr_file__lookup_one( git_attr_file *file, - const git_attr_path *path, + git_attr_path *path, const char *attr, const char **value) { @@ -342,14 +342,11 @@ int git_attr_file__load_standalone(git_attr_file **out, const char *path) bool git_attr_fnmatch__match( git_attr_fnmatch *match, - const git_attr_path *path) + git_attr_path *path) { const char *filename; int flags = 0; - if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) - return false; - if (match->flags & GIT_ATTR_FNMATCH_ICASE) flags |= FNM_CASEFOLD; if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR) @@ -365,12 +362,28 @@ bool git_attr_fnmatch__match( flags |= FNM_LEADING_DIR; } + if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { + int matchval; + + /* for attribute checks or root ignore checks, fail match */ + if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || + path->basename == path->path) + return false; + + /* for ignore checks, use container of current item for check */ + path->basename[-1] = '\0'; + flags |= FNM_LEADING_DIR; + matchval = p_fnmatch(match->pattern, path->path, flags); + path->basename[-1] = '/'; + return (matchval != FNM_NOMATCH); + } + return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH); } bool git_attr_rule__match( git_attr_rule *rule, - const git_attr_path *path) + git_attr_path *path) { bool matched = git_attr_fnmatch__match(&rule->match, path); diff --git a/src/attr_file.h b/src/attr_file.h index e50aec07c..87cde7e35 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -138,7 +138,7 @@ int git_attr_file__clear_rules( int git_attr_file__lookup_one( git_attr_file *file, - const git_attr_path *path, + git_attr_path *path, const char *attr, const char **value); @@ -162,13 +162,13 @@ extern int git_attr_fnmatch__parse( extern bool git_attr_fnmatch__match( git_attr_fnmatch *rule, - const git_attr_path *path); + git_attr_path *path); extern void git_attr_rule__free(git_attr_rule *rule); extern bool git_attr_rule__match( git_attr_rule *rule, - const git_attr_path *path); + git_attr_path *path); extern git_attr_assignment *git_attr_rule__lookup_assignment( git_attr_rule *rule, const char *name); diff --git a/src/attrcache.c b/src/attrcache.c index f1bc70467..b4579bfc0 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -53,7 +53,7 @@ int git_attr_cache__alloc_file_entry( cachesize++; } - ce = git_pool_mallocz(pool, cachesize); + ce = git_pool_mallocz(pool, (uint32_t)cachesize); GITERR_CHECK_ALLOC(ce); if (baselen) { @@ -349,14 +349,11 @@ int git_attr_cache__do_init(git_repository *repo) { int ret = 0; git_attr_cache *cache = git_repository_attr_cache(repo); - git_config *cfg; + git_config *cfg = NULL; if (cache) return 0; - if ((ret = git_repository_config__weakptr(&cfg, repo)) < 0) - return ret; - cache = git__calloc(1, sizeof(git_attr_cache)); GITERR_CHECK_ALLOC(cache); @@ -367,6 +364,9 @@ int git_attr_cache__do_init(git_repository *repo) return -1; } + if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0) + goto cancel; + /* cache config settings for attributes and ignores */ ret = attr_cache__lookup_path( &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG); @@ -390,11 +390,14 @@ int git_attr_cache__do_init(git_repository *repo) if (cache) goto cancel; /* raced with another thread, free this but no error */ + git_config_free(cfg); + /* insert default macros */ return git_attr_add_macro(repo, "binary", "-diff -crlf -text"); cancel: attr_cache__free(cache); + git_config_free(cfg); return ret; } diff --git a/src/blob.c b/src/blob.c index ab7dec67f..30d5b705b 100644 --- a/src/blob.c +++ b/src/blob.c @@ -334,7 +334,8 @@ int git_blob_is_binary(const git_blob *blob) assert(blob); content.ptr = blob->odb_object->buffer; - content.size = min(blob->odb_object->cached.size, 4000); + content.size = + min(blob->odb_object->cached.size, GIT_FILTER_BYTES_TO_CHECK_NUL); content.asize = 0; return git_buf_text_is_binary(&content); diff --git a/src/clone.c b/src/clone.c index c6be00f0e..6c4fb6727 100644 --- a/src/clone.c +++ b/src/clone.c @@ -22,6 +22,7 @@ #include "refs.h" #include "path.h" #include "repository.h" +#include "odb.h" static int create_branch( git_reference **branch, @@ -105,54 +106,6 @@ static int create_tracking_branch( git_reference_name(*branch)); } -struct head_info { - git_repository *repo; - git_oid remote_head_oid; - git_buf branchname; - const git_refspec *refspec; - bool found; -}; - -static int reference_matches_remote_head( - const char *reference_name, - void *payload) -{ - struct head_info *head_info = (struct head_info *)payload; - git_oid oid; - int error; - - /* TODO: Should we guard against references - * which name doesn't start with refs/heads/ ? - */ - - error = git_reference_name_to_id(&oid, head_info->repo, reference_name); - if (error == GIT_ENOTFOUND) { - /* If the reference doesn't exists, it obviously cannot match the - * expected oid. */ - giterr_clear(); - return 0; - } - - if (!error && !git_oid__cmp(&head_info->remote_head_oid, &oid)) { - /* Determine the local reference name from the remote tracking one */ - error = git_refspec_rtransform( - &head_info->branchname, head_info->refspec, reference_name); - - if (!error && - git_buf_len(&head_info->branchname) > 0 && - !(error = git_buf_sets( - &head_info->branchname, - git_buf_cstr(&head_info->branchname) + - strlen(GIT_REFS_HEADS_DIR)))) - { - head_info->found = true; - error = GIT_ITEROVER; - } - } - - return error; -} - static int update_head_to_new_branch( git_repository *repo, const git_oid *target, @@ -161,7 +114,12 @@ static int update_head_to_new_branch( const char *reflog_message) { git_reference *tracking_branch = NULL; - int error = create_tracking_branch(&tracking_branch, repo, target, name, + int error; + + if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) + name += strlen(GIT_REFS_HEADS_DIR); + + error = create_tracking_branch(&tracking_branch, repo, target, name, signature, reflog_message); if (!error) @@ -171,6 +129,10 @@ static int update_head_to_new_branch( git_reference_free(tracking_branch); + /* if it already existed, then the user's refspec created it for us, ignore it' */ + if (error == GIT_EEXISTS) + error = 0; + return error; } @@ -180,12 +142,13 @@ static int update_head_to_remote( const git_signature *signature, const char *reflog_message) { - int error = 0; + int error = 0, found_branch = 0; size_t refs_len; - git_refspec dummy_spec; + git_refspec dummy_spec, *refspec; const git_remote_head *remote_head, **refs; - struct head_info head_info; + const git_oid *remote_head_id; git_buf remote_master_name = GIT_BUF_INIT; + git_buf branch = GIT_BUF_INIT; if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) return error; @@ -195,63 +158,45 @@ static int update_head_to_remote( return setup_tracking_config( repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE); + error = git_remote_default_branch(&branch, remote); + if (error == GIT_ENOTFOUND) { + git_buf_puts(&branch, GIT_REFS_HEADS_MASTER_FILE); + } else { + found_branch = 1; + } + /* Get the remote's HEAD. This is always the first ref in the list. */ remote_head = refs[0]; assert(remote_head); - memset(&head_info, 0, sizeof(head_info)); - git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); - head_info.repo = repo; - head_info.refspec = - git_remote__matching_refspec(remote, GIT_REFS_HEADS_MASTER_FILE); + remote_head_id = &remote_head->oid; + refspec = git_remote__matching_refspec(remote, git_buf_cstr(&branch)); - if (head_info.refspec == NULL) { + if (refspec == NULL) { memset(&dummy_spec, 0, sizeof(git_refspec)); - head_info.refspec = &dummy_spec; + refspec = &dummy_spec; } /* Determine the remote tracking reference name from the local master */ if ((error = git_refspec_transform( &remote_master_name, - head_info.refspec, - GIT_REFS_HEADS_MASTER_FILE)) < 0) + refspec, + git_buf_cstr(&branch))) < 0) return error; - /* Check to see if the remote HEAD points to the remote master */ - error = reference_matches_remote_head( - git_buf_cstr(&remote_master_name), &head_info); - if (error < 0 && error != GIT_ITEROVER) - goto cleanup; - - if (head_info.found) { - error = update_head_to_new_branch( - repo, - &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname), - signature, reflog_message); - goto cleanup; - } - - /* Not master. Check all the other refs. */ - error = git_reference_foreach_name( - repo, reference_matches_remote_head, &head_info); - if (error < 0 && error != GIT_ITEROVER) - goto cleanup; - - if (head_info.found) { + if (found_branch) { error = update_head_to_new_branch( repo, - &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname), + remote_head_id, + git_buf_cstr(&branch), signature, reflog_message); } else { error = git_repository_set_head_detached( - repo, &head_info.remote_head_oid, signature, reflog_message); + repo, remote_head_id, signature, reflog_message); } -cleanup: git_buf_free(&remote_master_name); - git_buf_free(&head_info.branchname); + git_buf_free(&branch); return error; } @@ -297,6 +242,15 @@ static int create_and_configure_origin( int error; git_remote *origin = NULL; const char *name; + char buf[GIT_PATH_MAX]; + + /* If the path exists and is a dir, the url should be the absolute path */ + if (git_path_root(url) < 0 && git_path_exists(url) && git_path_isdir(url)) { + if (p_realpath(url, buf) == NULL) + return -1; + + url = buf; + } name = options->remote_name ? options->remote_name : "origin"; if ((error = git_remote_create(&origin, repo, name, url)) < 0) @@ -336,59 +290,86 @@ static bool should_checkout( return !git_repository_head_unborn(repo); } -int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) +static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature, const char *reflog_message) +{ + int error; + + if (branch) + error = update_head_to_branch(repo, git_remote_name(remote), branch, + signature, reflog_message); + /* Point HEAD to the same ref as the remote's head */ + else + error = update_head_to_remote(repo, remote, signature, reflog_message); + + if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) + error = git_checkout_head(repo, co_opts); + + return error; +} + +int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) { - int error = 0, old_fetchhead; - git_strarray refspecs; + int error; git_buf reflog_message = GIT_BUF_INIT; + git_remote *remote; + const git_remote_callbacks *callbacks; - assert(repo && remote); + assert(repo && _remote); if (!git_repository_is_empty(repo)) { giterr_set(GITERR_INVALID, "the repository is not empty"); return -1; } - - if ((error = git_remote_get_fetch_refspecs(&refspecs, remote)) < 0) + if ((error = git_remote_dup(&remote, _remote)) < 0) return error; + callbacks = git_remote_get_callbacks(_remote); + if (!giterr__check_version(callbacks, 1, "git_remote_callbacks") && + (error = git_remote_set_callbacks(remote, callbacks)) < 0) + goto cleanup; + if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0) - return error; + goto cleanup; - old_fetchhead = git_remote_update_fetchhead(remote); git_remote_set_update_fetchhead(remote, 0); git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0) goto cleanup; - if (branch) - error = update_head_to_branch(repo, git_remote_name(remote), branch, - signature, git_buf_cstr(&reflog_message)); - /* Point HEAD to the same ref as the remote's head */ - else - error = update_head_to_remote(repo, remote, signature, git_buf_cstr(&reflog_message)); - - if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) - error = git_checkout_head(repo, co_opts); + error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message)); cleanup: - git_remote_set_update_fetchhead(remote, old_fetchhead); - - /* Go back to the original refspecs */ - { - int error_alt = git_remote_set_fetch_refspecs(remote, &refspecs); - if (!error) - error = error_alt; - } - - git_strarray_free(&refspecs); + git_remote_free(remote); git_buf_free(&reflog_message); return error; } +int git_clone__should_clone_local(const char *url, git_clone_local_t local) +{ + const char *path; + int is_url; + + if (local == GIT_CLONE_NO_LOCAL) + return false; + + is_url = !git__prefixcmp(url, "file://"); + + if (is_url && local != GIT_CLONE_LOCAL && local != GIT_CLONE_LOCAL_NO_LINKS ) + return false; + + path = url; + if (is_url) + path = url + strlen("file://"); + + if ((git_path_exists(path) && git_path_isdir(path)) && local != GIT_CLONE_NO_LOCAL) + return true; + + return false; +} + int git_clone( git_repository **out, const char *url, @@ -423,8 +404,16 @@ int git_clone( return error; if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - error = git_clone_into( - repo, origin, &options.checkout_opts, options.checkout_branch, options.signature); + if (git_clone__should_clone_local(url, options.local)) { + int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; + error = git_clone_local_into( + repo, origin, &options.checkout_opts, + options.checkout_branch, link, options.signature); + } else { + error = git_clone_into( + repo, origin, &options.checkout_opts, + options.checkout_branch, options.signature); + } git_remote_free(origin); } @@ -451,3 +440,91 @@ int git_clone_init_options(git_clone_options *opts, unsigned int version) opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT); return 0; } + +static const char *repository_base(git_repository *repo) +{ + if (git_repository_is_bare(repo)) + return git_repository_path(repo); + + return git_repository_workdir(repo); +} + +static bool can_link(const char *src, const char *dst, int link) +{ +#ifdef GIT_WIN32 + return false; +#else + + struct stat st_src, st_dst; + + if (!link) + return false; + + if (p_stat(src, &st_src) < 0) + return false; + + if (p_stat(dst, &st_dst) < 0) + return false; + + return st_src.st_dev == st_dst.st_dev; +#endif +} + +int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature) +{ + int error, flags; + git_repository *src; + git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT; + git_buf reflog_message = GIT_BUF_INIT; + + assert(repo && remote); + + if (!git_repository_is_empty(repo)) { + giterr_set(GITERR_INVALID, "the repository is not empty"); + return -1; + } + + /* + * Let's figure out what path we should use for the source + * repo, if it's not rooted, the path should be relative to + * the repository's worktree/gitdir. + */ + if ((error = git_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) + return error; + + /* Copy .git/objects/ from the source to the target */ + if ((error = git_repository_open(&src, git_buf_cstr(&src_path))) < 0) { + git_buf_free(&src_path); + return error; + } + + git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR); + git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR); + if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) { + error = -1; + goto cleanup; + } + + flags = 0; + if (can_link(git_repository_path(src), git_repository_path(repo), link)) + flags |= GIT_CPDIR_LINK_FILES; + + if ((error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb), + flags, GIT_OBJECT_DIR_MODE)) < 0) + goto cleanup; + + git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); + + if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0) + goto cleanup; + + error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message)); + +cleanup: + git_buf_free(&reflog_message); + git_buf_free(&src_path); + git_buf_free(&src_odb); + git_buf_free(&dst_odb); + git_repository_free(src); + return error; +} diff --git a/src/clone.h b/src/clone.h new file mode 100644 index 000000000..14ca5d44c --- /dev/null +++ b/src/clone.h @@ -0,0 +1,12 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_clone_h__ +#define INCLUDE_clone_h__ + +extern int git_clone__should_clone_local(const char *url, git_clone_local_t local); + +#endif diff --git a/src/config.c b/src/config.c index 757ff0387..8a0fb653c 100644 --- a/src/config.c +++ b/src/config.c @@ -139,7 +139,7 @@ int git_config_open_ondisk(git_config **out, const char *path) int git_config_snapshot(git_config **out, git_config *in) { - int error; + int error = 0; size_t i; file_internal *internal; git_config *config; @@ -153,19 +153,19 @@ int git_config_snapshot(git_config **out, git_config *in) git_config_backend *b; if ((error = internal->file->snapshot(&b, internal->file)) < 0) - goto on_error; + break; if ((error = git_config_add_backend(config, b, internal->level, 0)) < 0) { b->free(b); - goto on_error; + break; } } - *out = config; - return error; + if (error < 0) + git_config_free(config); + else + *out = config; -on_error: - git_config_free(config); return error; } diff --git a/src/config.h b/src/config.h index 00b6063e7..b0dcb49ac 100644 --- a/src/config.h +++ b/src/config.h @@ -76,4 +76,10 @@ extern int git_config__get_bool_force( extern int git_config__get_int_force( const git_config *cfg, const char *key, int fallback_value); +/* API for repository cvar-style lookups from config - not cached, but + * uses cvar value maps and fallbacks + */ +extern int git_config__cvar( + int *out, git_config *config, git_cvar_cached cvar); + #endif diff --git a/src/config_cache.c b/src/config_cache.c index 4bcbf02bf..45c39ce17 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -7,11 +7,11 @@ #include "common.h" #include "fileops.h" +#include "repository.h" #include "config.h" #include "git2/config.h" #include "vector.h" #include "filter.h" -#include "repository.h" struct map_data { const char *cvar_name; @@ -51,6 +51,12 @@ static git_cvar_map _cvar_map_autocrlf[] = { {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT} }; +static git_cvar_map _cvar_map_safecrlf[] = { + {GIT_CVAR_FALSE, NULL, GIT_SAFE_CRLF_FALSE}, + {GIT_CVAR_TRUE, NULL, GIT_SAFE_CRLF_FAIL}, + {GIT_CVAR_STRING, "warn", GIT_SAFE_CRLF_WARN} +}; + /* * Generic map for integer values */ @@ -68,33 +74,39 @@ static struct map_data _cvar_maps[] = { {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT }, {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, - {"core.safecrlf", NULL, 0, GIT_SAFE_CRLF_DEFAULT}, + {"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT}, + {"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT }, }; +int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar) +{ + int error = 0; + struct map_data *data = &_cvar_maps[(int)cvar]; + const git_config_entry *entry; + + git_config__lookup_entry(&entry, config, data->cvar_name, false); + + if (!entry) + *out = data->default_value; + else if (data->maps) + error = git_config_lookup_map_value( + out, data->maps, data->map_count, entry->value); + else + error = git_config_parse_bool(out, entry->value); + + return error; +} + int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) { *out = repo->cvar_cache[(int)cvar]; if (*out == GIT_CVAR_NOT_CACHED) { - struct map_data *data = &_cvar_maps[(int)cvar]; - git_config *config; int error; - const git_config_entry *entry; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - git_config__lookup_entry(&entry, config, data->cvar_name, false); - - if (!entry) - *out = data->default_value; - else if (data->maps) - error = git_config_lookup_map_value( - out, data->maps, data->map_count, entry->value); - else - error = git_config_parse_bool(out, entry->value); + git_config *config; - if (error < 0) + if ((error = git_repository_config__weakptr(&config, repo)) < 0 || + (error = git_config__cvar(out, config, cvar)) < 0) return error; repo->cvar_cache[(int)cvar] = *out; diff --git a/src/config_file.c b/src/config_file.c index b00d0827d..56271144b 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -270,7 +270,6 @@ static int config_open(git_config_backend *cfg, git_config_level_t level) if ((res = refcounted_strmap_alloc(&b->header.values)) < 0) return res; - git_mutex_init(&b->header.values_mutex); git_array_init(b->readers); reader = git_array_alloc(b->readers); if (!reader) { @@ -313,6 +312,7 @@ static int config__refresh(git_config_backend *cfg) goto out; reader = git_array_get(b->readers, git_array_size(b->readers) - 1); + GITERR_CHECK_ALLOC(reader); if ((error = config_parse(values->values, b, reader, b->level, 0)) < 0) goto out; @@ -327,7 +327,8 @@ static int config__refresh(git_config_backend *cfg) out: refcounted_strmap_free(values); - git_buf_free(&reader->buffer); + if (reader) + git_buf_free(&reader->buffer); return error; } @@ -344,8 +345,8 @@ static int config_refresh(git_config_backend *cfg) &reader->buffer, reader->file_path, &reader->file_mtime, &reader->file_size, &updated); - if (error < 0) - return (error == GIT_ENOTFOUND) ? 0 : error; + if (error < 0 && error != GIT_ENOTFOUND) + return error; if (updated) any_updated = 1; @@ -373,6 +374,7 @@ static void backend_free(git_config_backend *_backend) git__free(backend->file_path); refcounted_strmap_free(backend->header.values); + git_mutex_free(&backend->header.values_mutex); git__free(backend); } @@ -684,6 +686,7 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) GITERR_CHECK_ALLOC(backend); backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->header.values_mutex); backend->file_path = git__strdup(path); GITERR_CHECK_ALLOC(backend->file_path); @@ -756,6 +759,7 @@ static void backend_readonly_free(git_config_backend *_backend) return; refcounted_strmap_free(backend->header.values); + git_mutex_free(&backend->header.values_mutex); git__free(backend); } @@ -782,6 +786,7 @@ int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in) GITERR_CHECK_ALLOC(backend); backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->header.values_mutex); backend->snapshot_from = in; diff --git a/src/crlf.c b/src/crlf.c index dad3ecebc..821e04eb2 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -138,6 +138,10 @@ static int crlf_apply_to_odb( if (git_buf_text_gather_stats(&stats, from, false)) return GIT_PASSTHROUGH; + /* If there are no CR characters to filter out, then just pass */ + if (!stats.cr) + return GIT_PASSTHROUGH; + /* If safecrlf is enabled, sanity-check the result. */ if (stats.cr != stats.crlf || stats.lf != stats.crlf) { switch (ca->safe_crlf) { diff --git a/src/diff.c b/src/diff.c index b55d123c1..2691d7ca0 100644 --- a/src/diff.c +++ b/src/diff.c @@ -390,7 +390,7 @@ static int diff_list_apply_options( git_diff *diff, const git_diff_options *opts) { - git_config *cfg; + git_config *cfg = NULL; git_repository *repo = diff->repo; git_pool *pool = &diff->pool; int val; @@ -415,20 +415,20 @@ static int diff_list_apply_options( diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; /* load config values that affect diff behavior */ - if ((val = git_repository_config__weakptr(&cfg, repo)) < 0) + if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) return val; - if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; - if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORESTAT) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT; if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && - !git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE) && val) + !git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS; - if (!git_repository__cvar(&val, repo, GIT_CVAR_TRUSTCTIME) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ @@ -490,8 +490,6 @@ static int diff_list_apply_options( /* strdup prefix from pool so we're not dependent on external data */ diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix); diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix); - if (!diff->opts.old_prefix || !diff->opts.new_prefix) - return -1; if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { const char *tmp_prefix = diff->opts.old_prefix; @@ -499,7 +497,10 @@ static int diff_list_apply_options( diff->opts.new_prefix = tmp_prefix; } - return 0; + git_config_free(cfg); + + /* check strdup results for error */ + return (!diff->opts.old_prefix || !diff->opts.new_prefix) ? -1 : 0; } static void diff_list_free(git_diff *diff) @@ -642,7 +643,6 @@ typedef struct { git_iterator *new_iter; const git_index_entry *oitem; const git_index_entry *nitem; - git_buf ignore_prefix; } diff_in_progress; #define MODE_BITS_MASK 0000777 @@ -858,24 +858,13 @@ static int handle_unmatched_new_item( /* check if this is a prefix of the other side */ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); - /* check if this is contained in an ignored parent directory */ - if (git_buf_len(&info->ignore_prefix)) { - if (diff->pfxcomp(nitem->path, git_buf_cstr(&info->ignore_prefix)) == 0) - delta_type = GIT_DELTA_IGNORED; - else - git_buf_clear(&info->ignore_prefix); - } + /* update delta_type if this item is ignored */ + if (git_iterator_current_is_ignored(info->new_iter)) + delta_type = GIT_DELTA_IGNORED; if (nitem->mode == GIT_FILEMODE_TREE) { bool recurse_into_dir = contains_oitem; - /* if not already inside an ignored dir, check if this is ignored */ - if (delta_type != GIT_DELTA_IGNORED && - git_iterator_current_is_ignored(info->new_iter)) { - delta_type = GIT_DELTA_IGNORED; - git_buf_sets(&info->ignore_prefix, nitem->path); - } - /* check if user requests recursion into this type of dir */ recurse_into_dir = contains_oitem || (delta_type == GIT_DELTA_UNTRACKED && @@ -952,27 +941,12 @@ static int handle_unmatched_new_item( } } - /* In core git, the next two checks are effectively reversed -- - * i.e. when an file contained in an ignored directory is explicitly - * ignored, it shows up as an ignored file in the diff list, even though - * other untracked files in the same directory are skipped completely. - * - * To me, this seems odd. If the directory is ignored and the file is - * untracked, we should skip it consistently, regardless of whether it - * happens to match a pattern in the ignore file. - * - * To match the core git behavior, reverse the following two if checks - * so that individual file ignores are checked before container - * directory exclusions are used to skip the file. - */ else if (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && + git_iterator_current_tree_is_ignored(info->new_iter)) /* item contained in ignored directory, so skip over it */ return git_iterator_advance(&info->nitem, info->new_iter); - else if (git_iterator_current_is_ignored(info->new_iter)) - delta_type = GIT_DELTA_IGNORED; - else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) delta_type = GIT_DELTA_ADDED; @@ -1089,7 +1063,6 @@ int git_diff__from_iterators( info.repo = repo; info.old_iter = old_iter; info.new_iter = new_iter; - git_buf_init(&info.ignore_prefix, 0); /* make iterators have matching icase behavior */ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { @@ -1144,8 +1117,6 @@ cleanup: else git_diff_free(diff); - git_buf_free(&info.ignore_prefix); - return error; } diff --git a/src/diff_driver.c b/src/diff_driver.c index fc1354f36..c3c5f365b 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -233,17 +233,17 @@ static int git_diff_driver_load( return 0; } + drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1); + GITERR_CHECK_ALLOC(drv); + drv->type = DIFF_DRIVER_AUTO; + memcpy(drv->name, driver_name, namelen); + /* if you can't read config for repo, just use default driver */ if (git_repository_config_snapshot(&cfg, repo) < 0) { giterr_clear(); goto done; } - drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1); - GITERR_CHECK_ALLOC(drv); - drv->type = DIFF_DRIVER_AUTO; - memcpy(drv->name, driver_name, namelen); - if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0) goto done; @@ -397,7 +397,11 @@ void git_diff_driver_update_options( int git_diff_driver_content_is_binary( git_diff_driver *driver, const char *content, size_t content_len) { - const git_buf search = { (char *)content, 0, min(content_len, 4000) }; + git_buf search; + + search.ptr = (char *)content; + search.size = min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL); + search.asize = 0; GIT_UNUSED(driver); diff --git a/src/diff_print.c b/src/diff_print.c index 964c49540..32b109bb2 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -287,33 +287,46 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) { git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT, *out = NULL; const void *old_data, *new_data; - size_t old_data_len, new_data_len, delta_data_len, inflated_len, remain; + git_off_t old_data_len, new_data_len; + unsigned long delta_data_len, inflated_len; const char *out_type = "literal"; - char *ptr; + char *scan, *end; int error; old_data = old ? git_blob_rawcontent(old) : NULL; new_data = new ? git_blob_rawcontent(new) : NULL; - old_data_len = old ? (size_t)git_blob_rawsize(old) : 0; - new_data_len = new ? (size_t)git_blob_rawsize(new) : 0; + old_data_len = old ? git_blob_rawsize(old) : 0; + new_data_len = new ? git_blob_rawsize(new) : 0; + + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong(old_data_len) || !git__is_ulong(new_data_len)) + return GIT_EBUFS; out = &deflate; - inflated_len = new_data_len; + inflated_len = (unsigned long)new_data_len; if ((error = git_zstream_deflatebuf( - &deflate, new_data, new_data_len)) < 0) + out, new_data, (size_t)new_data_len)) < 0) goto done; - if (old && new) { - void *delta_data; + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong((git_off_t)deflate.size)) { + error = GIT_EBUFS; + goto done; + } - delta_data = git_delta(old_data, old_data_len, new_data, - new_data_len, &delta_data_len, deflate.size); + if (old && new) { + void *delta_data = git_delta( + old_data, (unsigned long)old_data_len, + new_data, (unsigned long)new_data_len, + &delta_data_len, (unsigned long)deflate.size); if (delta_data) { - error = git_zstream_deflatebuf(&delta, delta_data, delta_data_len); - free(delta_data); + error = git_zstream_deflatebuf( + &delta, delta_data, (size_t)delta_data_len); + + git__free(delta_data); if (error < 0) goto done; @@ -326,18 +339,20 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) } } - git_buf_printf(pi->buf, "%s %" PRIuZ "\n", out_type, inflated_len); + git_buf_printf(pi->buf, "%s %lu\n", out_type, inflated_len); pi->line.num_lines++; - for (ptr = out->ptr, remain = out->size; remain > 0; ) { - size_t chunk_len = (52 < remain) ? 52 : remain; + for (scan = out->ptr, end = out->ptr + out->size; scan < end; ) { + size_t chunk_len = end - scan; + if (chunk_len > 52) + chunk_len = 52; if (chunk_len <= 26) - git_buf_putc(pi->buf, chunk_len + 'A' - 1); + git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1); else - git_buf_putc(pi->buf, chunk_len - 26 + 'a' - 1); + git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); - git_buf_put_base85(pi->buf, ptr, chunk_len); + git_buf_put_base85(pi->buf, scan, chunk_len); git_buf_putc(pi->buf, '\n'); if (git_buf_oom(pi->buf)) { @@ -345,8 +360,7 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) goto done; } - ptr += chunk_len; - remain -= chunk_len; + scan += chunk_len; pi->line.num_lines++; } @@ -365,26 +379,33 @@ static int diff_print_patch_file_binary( git_blob *old = NULL, *new = NULL; const git_oid *old_id, *new_id; int error; + size_t pre_binary_size; - if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) { - pi->line.num_lines = 1; - return diff_delta_format_with_paths( - pi->buf, delta, oldpfx, newpfx, - "Binary files %s%s and %s%s differ\n"); - } + if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) + goto noshow; + pre_binary_size = pi->buf->size; git_buf_printf(pi->buf, "GIT binary patch\n"); pi->line.num_lines++; old_id = (delta->status != GIT_DELTA_ADDED) ? &delta->old_file.id : NULL; new_id = (delta->status != GIT_DELTA_DELETED) ? &delta->new_file.id : NULL; - if ((old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0) || - (new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0) || - (error = print_binary_hunk(pi, old, new)) < 0 || + if (old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0) + goto done; + if (new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0) + goto done; + + if ((error = print_binary_hunk(pi, old, new)) < 0 || (error = git_buf_putc(pi->buf, '\n')) < 0 || (error = print_binary_hunk(pi, new, old)) < 0) - goto done; + { + if (error == GIT_EBUFS) { + giterr_clear(); + git_buf_truncate(pi->buf, pre_binary_size); + goto noshow; + } + } pi->line.num_lines++; @@ -393,6 +414,12 @@ done: git_blob_free(new); return error; + +noshow: + pi->line.num_lines = 1; + return diff_delta_format_with_paths( + pi->buf, delta, oldpfx, newpfx, + "Binary files %s%s and %s%s differ\n"); } static int diff_print_patch_file( diff --git a/src/fileops.c b/src/fileops.c index 13b8f6a39..bebbae4f9 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -740,9 +740,11 @@ static int _cp_r_callback(void *ref, git_buf *from) return error; /* make symlink or regular file */ - if (S_ISLNK(from_st.st_mode)) + if (info->flags & GIT_CPDIR_LINK_FILES) { + error = p_link(from->ptr, info->to.ptr); + } else if (S_ISLNK(from_st.st_mode)) { error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); - else { + } else { mode_t usemode = from_st.st_mode; if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) diff --git a/src/fileops.h b/src/fileops.h index 62227abae..4f5700a99 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -173,6 +173,7 @@ extern int git_futils_cp( * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the * source file to the target; with this flag, always use 0666 (or 0777 if * source has exec bits set) for target. + * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files */ typedef enum { GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), @@ -181,6 +182,7 @@ typedef enum { GIT_CPDIR_OVERWRITE = (1u << 3), GIT_CPDIR_CHMOD_DIRS = (1u << 4), GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), + GIT_CPDIR_LINK_FILES = (1u << 6), } git_futils_cpdir_flags; /** diff --git a/src/filter.h b/src/filter.h index d0ace0f9a..5a366108b 100644 --- a/src/filter.h +++ b/src/filter.h @@ -10,6 +10,10 @@ #include "common.h" #include "git2/filter.h" +/* Amount of file to examine for NUL byte when checking binary-ness */ +#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000 + +/* Possible CRLF values */ typedef enum { GIT_CRLF_GUESS = -1, GIT_CRLF_BINARY = 0, diff --git a/src/fnmatch.c b/src/fnmatch.c index d8a83a8ed..d7899e3e6 100644 --- a/src/fnmatch.c +++ b/src/fnmatch.c @@ -6,6 +6,40 @@ */ /* + * This file contains code originally derrived from OpenBSD fnmatch.c + * + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. * Compares a filename or pathname to a pattern. */ diff --git a/src/fnmatch.h b/src/fnmatch.h index 920e7de4d..88af45939 100644 --- a/src/fnmatch.h +++ b/src/fnmatch.h @@ -1,8 +1,29 @@ /* - * Copyright (C) the libgit2 contributors. All rights reserved. + * Copyright (C) 2008 The Android Open Source Project + * All rights reserved. * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. */ #ifndef INCLUDE_fnmatch__compat_h__ #define INCLUDE_fnmatch__compat_h__ diff --git a/src/global.c b/src/global.c index 4dfdcf438..7da31853e 100644 --- a/src/global.c +++ b/src/global.c @@ -76,7 +76,7 @@ static void git__shutdown(void) #if defined(GIT_THREADS) && defined(GIT_WIN32) static DWORD _tls_index; -static DWORD _mutex = 0; +static volatile LONG _mutex = 0; static int synchronized_threads_init() { diff --git a/src/ignore.c b/src/ignore.c index f373c9482..78f01ac44 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -248,14 +248,15 @@ void git_ignore__free(git_ignores *ignores) } static bool ignore_lookup_in_rules( - git_attr_file *file, git_attr_path *path, int *ignored) + int *ignored, git_attr_file *file, git_attr_path *path) { size_t j; git_attr_fnmatch *match; git_vector_rforeach(&file->rules, j, match) { if (git_attr_fnmatch__match(match, path)) { - *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ? + GIT_IGNORE_TRUE : GIT_IGNORE_FALSE; return true; } } @@ -264,34 +265,34 @@ static bool ignore_lookup_in_rules( } int git_ignore__lookup( - git_ignores *ignores, const char *pathname, int *ignored) + int *out, git_ignores *ignores, const char *pathname) { unsigned int i; git_attr_file *file; git_attr_path path; + *out = GIT_IGNORE_NOTFOUND; + if (git_attr_path__init( &path, pathname, git_repository_workdir(ignores->repo)) < 0) return -1; /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(ignores->ign_internal, &path, ignored)) + if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) goto cleanup; /* next process files in the path */ git_vector_foreach(&ignores->ign_path, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(out, file, &path)) goto cleanup; } /* last process global ignores */ git_vector_foreach(&ignores->ign_global, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(out, file, &path)) goto cleanup; } - *ignored = 0; - cleanup: git_attr_path__free(&path); return 0; @@ -335,8 +336,6 @@ int git_ignore_path_is_ignored( int error; const char *workdir; git_attr_path path; - char *tail, *end; - bool full_is_dir; git_ignores ignores; unsigned int i; git_attr_file *file; @@ -345,55 +344,42 @@ int git_ignore_path_is_ignored( workdir = repo ? git_repository_workdir(repo) : NULL; - if ((error = git_attr_path__init(&path, pathname, workdir)) < 0) - return error; + memset(&path, 0, sizeof(path)); + memset(&ignores, 0, sizeof(ignores)); - tail = path.path; - end = &path.full.ptr[path.full.size]; - full_is_dir = path.is_dir; + if ((error = git_attr_path__init(&path, pathname, workdir)) < 0 || + (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) + goto cleanup; while (1) { - /* advance to next component of path */ - path.basename = tail; - - while (tail < end && *tail != '/') tail++; - *tail = '\0'; - - path.full.size = (tail - path.full.ptr); - path.is_dir = (tail == end) ? full_is_dir : true; - - /* initialize ignores the first time through */ - if (path.basename == path.path && - (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) - break; - /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(ignores.ign_internal, &path, ignored)) + if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path)) goto cleanup; /* next process files in the path */ git_vector_foreach(&ignores.ign_path, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(ignored, file, &path)) goto cleanup; } /* last process global ignores */ git_vector_foreach(&ignores.ign_global, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(ignored, file, &path)) goto cleanup; } - /* if we found no rules before reaching the end, we're done */ - if (tail == end) + /* move up one directory */ + if (path.basename == path.path) break; - - /* now add this directory to list of ignores */ - if ((error = git_ignore__push_dir(&ignores, path.path)) < 0) + path.basename[-1] = '\0'; + while (path.basename > path.path && *path.basename != '/') + path.basename--; + if (path.basename > path.path) + path.basename++; + path.is_dir = 1; + + if ((error = git_ignore__pop_dir(&ignores)) < 0) break; - - /* reinstate divider in path */ - *tail = '/'; - while (*tail == '/') tail++; } *ignored = 0; @@ -404,7 +390,6 @@ cleanup: return error; } - int git_ignore__check_pathspec_for_exact_ignores( git_repository *repo, git_vector *vspec, diff --git a/src/ignore.h b/src/ignore.h index ff9369000..77668c661 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -42,7 +42,14 @@ extern int git_ignore__pop_dir(git_ignores *ign); extern void git_ignore__free(git_ignores *ign); -extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored); +enum { + GIT_IGNORE_UNCHECKED = -2, + GIT_IGNORE_NOTFOUND = -1, + GIT_IGNORE_FALSE = 0, + GIT_IGNORE_TRUE = 1, +}; + +extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path); /* command line Git sometimes generates an error message if given a * pathspec that contains an exact match to an ignored file (provided diff --git a/src/index.c b/src/index.c index 8a7f29279..b63a0bec6 100644 --- a/src/index.c +++ b/src/index.c @@ -1104,6 +1104,15 @@ int git_index_remove_bypath(git_index *index, const char *path) return 0; } +static bool valid_filemode(const int filemode) +{ + return (filemode == GIT_FILEMODE_BLOB || + filemode == GIT_FILEMODE_BLOB_EXECUTABLE || + filemode == GIT_FILEMODE_LINK || + filemode == GIT_FILEMODE_COMMIT); +} + + int git_index_add(git_index *index, const git_index_entry *source_entry) { git_index_entry *entry = NULL; @@ -1111,6 +1120,11 @@ int git_index_add(git_index *index, const git_index_entry *source_entry) assert(index && source_entry && source_entry->path); + if (!valid_filemode(source_entry->mode)) { + giterr_set(GITERR_INDEX, "invalid filemode"); + return -1; + } + if ((ret = index_entry_dup(&entry, source_entry)) < 0 || (ret = index_insert(index, &entry, 1)) < 0) return ret; diff --git a/src/indexer.c b/src/indexer.c index 3c8415c7c..25c3d0537 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -34,7 +34,6 @@ struct git_indexer { have_delta :1; struct git_pack_header hdr; struct git_pack_file *pack; - git_filebuf pack_file; unsigned int mode; git_off_t off; git_off_t entry_start; @@ -67,33 +66,18 @@ const git_oid *git_indexer_hash(const git_indexer *idx) return &idx->hash; } -static int open_pack(struct git_pack_file **out, const char *filename) -{ - struct git_pack_file *pack; - - if (git_packfile_alloc(&pack, filename) < 0) - return -1; - - if ((pack->mwf.fd = p_open(pack->pack_name, O_RDONLY)) < 0) { - giterr_set(GITERR_OS, "Failed to open packfile."); - git_packfile_free(pack); - return -1; - } - - *out = pack; - return 0; -} - static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) { int error; + git_map map; - /* Verify we recognize this pack file format. */ - if ((error = p_read(pack->mwf.fd, hdr, sizeof(*hdr))) < 0) { - giterr_set(GITERR_OS, "Failed to read in pack header"); + if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0) return error; - } + memcpy(hdr, map.data, sizeof(*hdr)); + p_munmap(&map); + + /* Verify we recognize this pack file format. */ if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { giterr_set(GITERR_INDEXER, "Wrong pack signature"); return -1; @@ -124,9 +108,9 @@ int git_indexer_new( void *progress_payload) { git_indexer *idx; - git_buf path = GIT_BUF_INIT; + git_buf path = GIT_BUF_INIT, tmp_path = GIT_BUF_INIT; static const char suff[] = "/pack"; - int error; + int error, fd = -1; idx = git__calloc(1, sizeof(git_indexer)); GITERR_CHECK_ALLOC(idx); @@ -140,19 +124,30 @@ int git_indexer_new( if (error < 0) goto cleanup; - error = git_filebuf_open(&idx->pack_file, path.ptr, - GIT_FILEBUF_TEMPORARY | GIT_FILEBUF_DO_NOT_BUFFER, - idx->mode); + fd = git_futils_mktmp(&tmp_path, git_buf_cstr(&path), idx->mode); git_buf_free(&path); + if (fd < 0) + goto cleanup; + + error = git_packfile_alloc(&idx->pack, git_buf_cstr(&tmp_path)); + git_buf_free(&tmp_path); + if (error < 0) goto cleanup; + idx->pack->mwf.fd = fd; + if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) + goto cleanup; + *out = idx; return 0; cleanup: + if (fd != -1) + p_close(fd); + git_buf_free(&path); - git_filebuf_cleanup(&idx->pack_file); + git_buf_free(&tmp_path); git__free(idx); return -1; } @@ -429,6 +424,42 @@ static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) idx->inbuf_len += size - to_expell; } +static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t size) +{ + git_file fd = idx->pack->mwf.fd; + long page_size = git__page_size(); + git_off_t page_start, page_offset; + unsigned char *map_data; + git_map map; + int error; + + /* the offset needs to be at the beginning of the a page boundary */ + page_start = (offset / page_size) * page_size; + page_offset = offset - page_start; + + if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0) + return error; + + map_data = (unsigned char *)map.data; + memcpy(map_data + page_offset, data, size); + p_munmap(&map); + + return 0; +} + +static int append_to_pack(git_indexer *idx, const void *data, size_t size) +{ + git_off_t current_size = idx->pack->mwf.size; + + /* add the extra space we need at the end */ + if (p_ftruncate(idx->pack->mwf.fd, current_size + size) < 0) { + giterr_system_set(errno); + return -1; + } + + return write_at(idx, data, idx->pack->mwf.size, size); +} + int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats) { int error = -1; @@ -440,22 +471,13 @@ int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_tran processed = stats->indexed_objects; - if ((error = git_filebuf_write(&idx->pack_file, data, size)) < 0) + if ((error = append_to_pack(idx, data, size)) < 0) return error; hash_partially(idx, data, (int)size); /* Make sure we set the new size of the pack */ - if (idx->opened_pack) { - idx->pack->mwf.size += size; - } else { - if ((error = open_pack(&idx->pack, idx->pack_file.path_lock)) < 0) - return error; - idx->opened_pack = 1; - mwf = &idx->pack->mwf; - if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) - return error; - } + idx->pack->mwf.size += size; if (!idx->parsed_header) { unsigned int total_objects; @@ -616,17 +638,10 @@ static int index_path(git_buf *path, git_indexer *idx, const char *suffix) * Rewind the packfile by the trailer, as we might need to fix the * packfile by injecting objects at the tail and must overwrite it. */ -static git_off_t seek_back_trailer(git_indexer *idx) +static void seek_back_trailer(git_indexer *idx) { - git_off_t off; - - if ((off = p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_CUR)) < 0) - return -1; - idx->pack->mwf.size -= GIT_OID_RAWSZ; git_mwindow_free_all(&idx->pack->mwf); - - return off; } static int inject_object(git_indexer *idx, git_oid *id) @@ -642,7 +657,8 @@ static int inject_object(git_indexer *idx, git_oid *id) size_t len, hdr_len; int error; - entry_start = seek_back_trailer(idx); + seek_back_trailer(idx); + entry_start = idx->pack->mwf.size; if (git_odb_read(&obj, idx->odb, id) < 0) return -1; @@ -657,7 +673,9 @@ static int inject_object(git_indexer *idx, git_oid *id) /* Write out the object header */ hdr_len = git_packfile__object_header(hdr, len, git_odb_object_type(obj)); - git_filebuf_write(&idx->pack_file, hdr, hdr_len); + if ((error = append_to_pack(idx, hdr, hdr_len)) < 0) + goto cleanup; + idx->pack->mwf.size += hdr_len; entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len); @@ -665,13 +683,16 @@ static int inject_object(git_indexer *idx, git_oid *id) goto cleanup; /* And then the compressed object */ - git_filebuf_write(&idx->pack_file, buf.ptr, buf.size); + if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0) + goto cleanup; + idx->pack->mwf.size += buf.size; entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size)); git_buf_free(&buf); /* Write a fake trailer so the pack functions play ball */ - if ((error = git_filebuf_write(&idx->pack_file, &foo, GIT_OID_RAWSZ)) < 0) + + if ((error = append_to_pack(idx, &foo, GIT_OID_RAWSZ)) < 0) goto cleanup; idx->pack->mwf.size += GIT_OID_RAWSZ; @@ -703,7 +724,7 @@ static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats) size_t size; git_otype type; git_mwindow *w = NULL; - git_off_t curpos; + git_off_t curpos = 0; unsigned char *base_info; unsigned int left = 0; git_oid base; @@ -817,19 +838,12 @@ static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *sta ctx = &idx->trailer; git_hash_ctx_init(ctx); - git_mwindow_free_all(mwf); + /* Update the header to include the numer of local objects we injected */ idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); - if (p_lseek(idx->pack_file.fd, 0, SEEK_SET) < 0) { - giterr_set(GITERR_OS, "failed to seek to the beginning of the pack"); + if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0) return -1; - } - - if (p_write(idx->pack_file.fd, &idx->hdr, sizeof(struct git_pack_header)) < 0) { - giterr_set(GITERR_OS, "failed to update the pack header"); - return -1; - } /* * We now use the same technique as before to determine the @@ -837,6 +851,7 @@ static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *sta * hash_partially() keep the existing trailer out of the * calculation. */ + git_mwindow_free_all(mwf); idx->inbuf_len = 0; while (hashed < mwf->size) { ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); @@ -906,13 +921,7 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) return -1; git_hash_final(&trailer_hash, &idx->trailer); - if (p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_END) < 0) - return -1; - - if (p_write(idx->pack_file.fd, &trailer_hash, GIT_OID_RAWSZ) < 0) { - giterr_set(GITERR_OS, "failed to update pack trailer"); - return -1; - } + write_at(idx, &trailer_hash, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ); } git_vector_sort(&idx->objects); @@ -995,14 +1004,18 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) git_mwindow_free_all(&idx->pack->mwf); /* We need to close the descriptor here so Windows doesn't choke on commit_at */ - p_close(idx->pack->mwf.fd); + if (p_close(idx->pack->mwf.fd) < 0) { + giterr_set(GITERR_OS, "failed to close packfile"); + goto on_error; + } + idx->pack->mwf.fd = -1; if (index_path(&filename, idx, ".pack") < 0) goto on_error; + /* And don't forget to rename the packfile to its new place. */ - if (git_filebuf_commit_at(&idx->pack_file, filename.ptr) < 0) - return -1; + p_rename(idx->pack->pack_name, git_buf_cstr(&filename)); git_buf_free(&filename); return 0; @@ -1022,7 +1035,7 @@ void git_indexer_free(git_indexer *idx) git_vector_free_deep(&idx->objects); - if (idx->pack) { + if (idx->pack && idx->pack->idx_cache) { struct git_pack_entry *pentry; kh_foreach_value( idx->pack->idx_cache, pentry, { git__free(pentry); }); @@ -1032,6 +1045,5 @@ void git_indexer_free(git_indexer *idx) git_vector_free_deep(&idx->deltas); git_packfile_free(idx->pack); - git_filebuf_cleanup(&idx->pack_file); git__free(idx); } diff --git a/src/iterator.c b/src/iterator.c index 4f8087c8d..c664f17cd 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -897,6 +897,7 @@ struct fs_iterator_frame { fs_iterator_frame *next; git_vector entries; size_t index; + int is_ignored; }; typedef struct fs_iterator fs_iterator; @@ -1290,16 +1291,28 @@ GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path) static int workdir_iterator__enter_dir(fs_iterator *fi) { + workdir_iterator *wi = (workdir_iterator *)fi; fs_iterator_frame *ff = fi->stack; size_t pos; git_path_with_stat *entry; bool found_submodules = false; - /* only push new ignores if this is not top level directory */ + /* check if this directory is ignored */ + if (git_ignore__lookup( + &ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len) < 0) { + giterr_clear(); + ff->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* if this is not the top level directory... */ if (ff->next != NULL) { - workdir_iterator *wi = (workdir_iterator *)fi; ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/'); + /* inherit ignored from parent if no rule specified */ + if (ff->is_ignored <= GIT_IGNORE_NOTFOUND) + ff->is_ignored = ff->next->is_ignored; + + /* push new ignores for files in this directory */ (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]); } @@ -1342,7 +1355,7 @@ static int workdir_iterator__update_entry(fs_iterator *fi) return GIT_ENOTFOUND; /* reset is_ignored since we haven't checked yet */ - wi->is_ignored = -1; + wi->is_ignored = GIT_IGNORE_UNCHECKED; return 0; } @@ -1487,6 +1500,19 @@ int git_iterator_current_parent_tree( return 0; } +static void workdir_iterator_update_is_ignored(workdir_iterator *wi) +{ + if (git_ignore__lookup( + &wi->is_ignored, &wi->ignores, wi->fi.entry.path) < 0) { + giterr_clear(); + wi->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* use ignore from containing frame stack */ + if (wi->is_ignored <= GIT_IGNORE_NOTFOUND) + wi->is_ignored = wi->fi.stack->is_ignored; +} + bool git_iterator_current_is_ignored(git_iterator *iter) { workdir_iterator *wi = (workdir_iterator *)iter; @@ -1494,14 +1520,22 @@ bool git_iterator_current_is_ignored(git_iterator *iter) if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) return false; - if (wi->is_ignored != -1) - return (bool)(wi->is_ignored != 0); + if (wi->is_ignored != GIT_IGNORE_UNCHECKED) + return (bool)(wi->is_ignored == GIT_IGNORE_TRUE); - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; + workdir_iterator_update_is_ignored(wi); + + return (bool)(wi->is_ignored == GIT_IGNORE_TRUE); +} + +bool git_iterator_current_tree_is_ignored(git_iterator *iter) +{ + workdir_iterator *wi = (workdir_iterator *)iter; + + if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) + return false; - return (bool)wi->is_ignored; + return (bool)(wi->fi.stack->is_ignored == GIT_IGNORE_TRUE); } int git_iterator_cmp(git_iterator *iter, const char *path_prefix) @@ -1549,10 +1583,8 @@ int git_iterator_advance_over_with_status( return error; if (!S_ISDIR(entry->mode)) { - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; - if (wi->is_ignored) + workdir_iterator_update_is_ignored(wi); + if (wi->is_ignored == GIT_IGNORE_TRUE) *status = GIT_ITERATOR_STATUS_IGNORED; return git_iterator_advance(entryptr, iter); } @@ -1564,14 +1596,12 @@ int git_iterator_advance_over_with_status( /* scan inside directory looking for a non-ignored item */ while (entry && !iter->prefixcomp(entry->path, base)) { - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; + workdir_iterator_update_is_ignored(wi); /* if we found an explicitly ignored item, then update from * EMPTY to IGNORED */ - if (wi->is_ignored) + if (wi->is_ignored == GIT_IGNORE_TRUE) *status = GIT_ITERATOR_STATUS_IGNORED; else if (S_ISDIR(entry->mode)) { error = git_iterator_advance_into(&entry, iter); @@ -1580,7 +1610,7 @@ int git_iterator_advance_over_with_status( continue; else if (error == GIT_ENOTFOUND) { error = 0; - wi->is_ignored = true; /* mark empty directories as ignored */ + wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */ } else break; /* real error, stop here */ } else { diff --git a/src/iterator.h b/src/iterator.h index f67830212..d88ad5191 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -245,6 +245,8 @@ extern int git_iterator_current_parent_tree( extern bool git_iterator_current_is_ignored(git_iterator *iter); +extern bool git_iterator_current_tree_is_ignored(git_iterator *iter); + extern int git_iterator_cmp( git_iterator *iter, const char *path_prefix); @@ -42,5 +42,6 @@ typedef struct { /* memory mapped buffer */ extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); extern int p_munmap(git_map *map); +extern long git__page_size(void); #endif /* INCLUDE_map_h__ */ diff --git a/src/merge.c b/src/merge.c index 6a8e5874f..a279d31d4 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2564,8 +2564,42 @@ done: return error; } +static int merge_preference(git_merge_preference_t *out, git_repository *repo) +{ + git_config *config; + const char *value; + int bool_value, error = 0; + + *out = GIT_MERGE_PREFERENCE_NONE; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0) + goto done; + + if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + + goto done; + } + + if (git_config_parse_bool(&bool_value, value) == 0) { + if (!bool_value) + *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD; + } else { + if (strcasecmp(value, "only") == 0) + *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY; + } + +done: + git_config_free(config); + return error; +} + int git_merge_analysis( - git_merge_analysis_t *out, + git_merge_analysis_t *analysis_out, + git_merge_preference_t *preference_out, git_repository *repo, const git_merge_head **their_heads, size_t their_heads_len) @@ -2573,14 +2607,7 @@ int git_merge_analysis( git_merge_head *ancestor_head = NULL, *our_head = NULL; int error = 0; - assert(out && repo && their_heads); - - *out = GIT_MERGE_ANALYSIS_NONE; - - if (git_repository_head_unborn(repo)) { - *out = GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; - goto done; - } + assert(analysis_out && preference_out && repo && their_heads); if (their_heads_len != 1) { giterr_set(GITERR_MERGE, "Can only merge a single branch"); @@ -2588,20 +2615,30 @@ int git_merge_analysis( goto done; } + *analysis_out = GIT_MERGE_ANALYSIS_NONE; + + if ((error = merge_preference(preference_out, repo)) < 0) + goto done; + + if (git_repository_head_unborn(repo)) { + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; + goto done; + } + if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0) goto done; /* We're up-to-date if we're trying to merge our own common ancestor. */ if (ancestor_head && git_oid_equal(&ancestor_head->oid, &their_heads[0]->oid)) - *out = GIT_MERGE_ANALYSIS_UP_TO_DATE; + *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; /* We're fastforwardable if we're our own common ancestor. */ else if (ancestor_head && git_oid_equal(&ancestor_head->oid, &our_head->oid)) - *out = GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; /* Otherwise, just a normal merge is possible. */ else - *out = GIT_MERGE_ANALYSIS_NORMAL; + *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL; done: git_merge_head_free(ancestor_head); diff --git a/src/message.c b/src/message.c index 07b2569ad..6c5a2379f 100644 --- a/src/message.c +++ b/src/message.c @@ -21,7 +21,7 @@ static size_t line_length_without_trailing_spaces(const char *line, size_t len) /* Greatly inspired from git.git "stripspace" */ /* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ -int git_message_prettify(git_buf *message_out, const char *message, int strip_comments) +int git_message_prettify(git_buf *message_out, const char *message, int strip_comments, char comment_char) { const size_t message_len = strlen(message); @@ -40,7 +40,7 @@ int git_message_prettify(git_buf *message_out, const char *message, int strip_co line_length = message_len - i; } - if (strip_comments && line_length && message[i] == '#') + if (strip_comments && line_length && message[i] == comment_char) continue; rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); diff --git a/src/netops.c b/src/netops.c index 24092c17f..a0193a022 100644 --- a/src/netops.c +++ b/src/netops.c @@ -206,6 +206,8 @@ static int gitno_ssl_teardown(gitno_ssl *ssl) return ret; } +#endif + /* Match host names according to RFC 2818 rules */ int gitno__match_host(const char *pattern, const char *host) { @@ -256,6 +258,8 @@ static int check_host_name(const char *name, const char *host) return 0; } +#ifdef GIT_SSL + static int verify_server_cert(gitno_ssl *ssl, const char *host) { X509 *cert; @@ -783,6 +783,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) return error; } + giterr_clear(); if ((object = odb_object__alloc(id, &raw)) == NULL) return -1; diff --git a/src/pack.c b/src/pack.c index de038a45c..ace7abb58 100644 --- a/src/pack.c +++ b/src/pack.c @@ -83,16 +83,12 @@ static void cache_free(git_pack_cache *cache) } git_offmap_free(cache->entries); - git_mutex_free(&cache->lock); + cache->entries = NULL; } - - memset(cache, 0, sizeof(*cache)); } static int cache_init(git_pack_cache *cache) { - memset(cache, 0, sizeof(*cache)); - cache->entries = git_offmap_alloc(); GITERR_CHECK_ALLOC(cache->entries); @@ -514,72 +510,102 @@ int git_packfile_resolve_header( return error; } -static int packfile_unpack_delta( - git_rawobj *obj, - struct git_pack_file *p, - git_mwindow **w_curs, - git_off_t *curpos, - size_t delta_size, - git_otype delta_type, - git_off_t obj_offset) +#define SMALL_STACK_SIZE 64 + +/** + * Generate the chain of dependencies which we need to get to the + * object at `off`. `chain` is used a stack, popping gives the right + * order to apply deltas on. If an object is found in the pack's base + * cache, we stop calculating there. + */ +static int pack_dependency_chain(git_dependency_chain *chain_out, + git_pack_cache_entry **cached_out, git_off_t *cached_off, + struct pack_chain_elem *small_stack, size_t *stack_sz, + struct git_pack_file *p, git_off_t obj_offset) { - git_off_t base_offset, base_key; - git_rawobj base, delta; - git_pack_cache_entry *cached = NULL; - int error, found_base = 0; + git_dependency_chain chain = GIT_ARRAY_INIT; + git_mwindow *w_curs = NULL; + git_off_t curpos = obj_offset, base_offset; + int error = 0, use_heap = 0; + size_t size, elem_pos; + git_otype type; - base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset); - git_mwindow_close(w_curs); - if (base_offset == 0) - return packfile_error("delta offset is zero"); - if (base_offset < 0) /* must actually be an error code */ - return (int)base_offset; + elem_pos = 0; + while (true) { + struct pack_chain_elem *elem; + git_pack_cache_entry *cached = NULL; - if (!p->bases.entries && (cache_init(&p->bases) < 0)) - return -1; + /* if we have a base cached, we can stop here instead */ + if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { + *cached_out = cached; + *cached_off = obj_offset; + break; + } - base_key = base_offset; /* git_packfile_unpack modifies base_offset */ - if ((cached = cache_get(&p->bases, base_offset)) != NULL) { - memcpy(&base, &cached->raw, sizeof(git_rawobj)); - found_base = 1; - } + /* if we run out of space on the small stack, use the array */ + if (elem_pos == SMALL_STACK_SIZE) { + git_array_init_to_size(chain, elem_pos); + GITERR_CHECK_ARRAY(chain); + memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem)); + chain.size = elem_pos; + use_heap = 1; + } + + curpos = obj_offset; + if (!use_heap) { + elem = &small_stack[elem_pos]; + } else { + elem = git_array_alloc(chain); + if (!elem) { + error = -1; + goto on_error; + } + } - if (!cached) { /* have to inflate it */ - error = git_packfile_unpack(&base, p, &base_offset); + elem->base_key = obj_offset; + + error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); + git_mwindow_close(&w_curs); - /* - * TODO: git.git tries to load the base from other packfiles - * or loose objects. - * - * We'll need to do this in order to support thin packs. - */ if (error < 0) - return error; - } + goto on_error; - error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type); - git_mwindow_close(w_curs); + elem->offset = curpos; + elem->size = size; + elem->type = type; + elem->base_key = obj_offset; - if (error < 0) { - if (!found_base) - git__free(base.data); - return error; - } + if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA) + break; - obj->type = base.type; - error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); - if (error < 0) - goto on_error; + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + git_mwindow_close(&w_curs); - if (found_base) - git_atomic_dec(&cached->refcount); - else if (cache_add(&p->bases, &base, base_key) < 0) - git__free(base.data); + if (base_offset == 0) { + error = packfile_error("delta offset is zero"); + goto on_error; + } + if (base_offset < 0) { /* must actually be an error code */ + error = (int)base_offset; + goto on_error; + } -on_error: - git__free(delta.data); + /* we need to pass the pos *after* the delta-base bit */ + elem->offset = curpos; + + /* go through the loop again, but with the new object */ + obj_offset = base_offset; + elem_pos++; + } + + + *stack_sz = elem_pos + 1; + *chain_out = chain; + return error; - return error; /* error set by git__delta_apply */ +on_error: + git_array_clear(chain); + return error; } int git_packfile_unpack( @@ -589,48 +615,138 @@ int git_packfile_unpack( { git_mwindow *w_curs = NULL; git_off_t curpos = *obj_offset; - int error; - - size_t size = 0; - git_otype type; + int error, free_base = 0; + git_dependency_chain chain = GIT_ARRAY_INIT; + struct pack_chain_elem *elem = NULL, *stack; + git_pack_cache_entry *cached = NULL; + struct pack_chain_elem small_stack[SMALL_STACK_SIZE]; + size_t stack_size, elem_pos; + git_otype base_type; /* * TODO: optionally check the CRC on the packfile */ + error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset); + if (error < 0) + return error; + obj->data = NULL; obj->len = 0; obj->type = GIT_OBJ_BAD; - error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); - git_mwindow_close(&w_curs); + /* let's point to the right stack */ + stack = chain.ptr ? chain.ptr : small_stack; - if (error < 0) - return error; + elem_pos = stack_size; + if (cached) { + memcpy(obj, &cached->raw, sizeof(git_rawobj)); + base_type = obj->type; + elem_pos--; /* stack_size includes the base, which isn't actually there */ + } else { + elem = &stack[--elem_pos]; + base_type = elem->type; + } - switch (type) { - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: - error = packfile_unpack_delta( - obj, p, &w_curs, &curpos, - size, type, *obj_offset); - break; + if (error < 0) + goto cleanup; + switch (base_type) { case GIT_OBJ_COMMIT: case GIT_OBJ_TREE: case GIT_OBJ_BLOB: case GIT_OBJ_TAG: - error = packfile_unpack_compressed( - obj, p, &w_curs, &curpos, - size, type); + if (!cached) { + curpos = elem->offset; + error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + base_type = elem->type; + } + if (error < 0) + goto cleanup; break; - + case GIT_OBJ_OFS_DELTA: + case GIT_OBJ_REF_DELTA: + error = packfile_error("dependency chain ends in a delta"); + goto cleanup; default: - error = packfile_error("invalid packfile type in header");; - break; + error = packfile_error("invalid packfile type in header"); + goto cleanup; + } + + /* + * Finding the object we want a cached base element is + * problematic, as we need to make sure we don't accidentally + * give the caller the cached object, which it would then feel + * free to free, so we need to copy the data. + */ + if (cached && stack_size == 1) { + void *data = obj->data; + obj->data = git__malloc(obj->len + 1); + GITERR_CHECK_ALLOC(obj->data); + memcpy(obj->data, data, obj->len + 1); + git_atomic_dec(&cached->refcount); + goto cleanup; } - *obj_offset = curpos; + /* we now apply each consecutive delta until we run out */ + while (elem_pos > 0 && !error) { + git_rawobj base, delta; + + /* + * We can now try to add the base to the cache, as + * long as it's not already the cached one. + */ + if (!cached) + free_base = !!cache_add(&p->bases, obj, elem->base_key); + + elem = &stack[elem_pos - 1]; + curpos = elem->offset; + error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + + if (error < 0) + break; + + /* the current object becomes the new base, on which we apply the delta */ + base = *obj; + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJ_BAD; + + error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); + obj->type = base_type; + /* + * We usually don't want to free the base at this + * point, as we put it into the cache in the previous + * iteration. free_base lets us know that we got the + * base object directly from the packfile, so we can free it. + */ + git__free(delta.data); + if (free_base) { + free_base = 0; + git__free(base.data); + } + + if (cached) { + git_atomic_dec(&cached->refcount); + cached = NULL; + } + + if (error < 0) + break; + + elem_pos--; + } + +cleanup: + if (error < 0) + git__free(obj->data); + + if (elem) + *obj_offset = elem->offset; + + git_array_clear(chain); return error; } @@ -660,7 +776,7 @@ int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, st = inflateInit(&obj->zstream); if (st != Z_OK) { git__free(obj); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "failed to init packfile stream"); return -1; } @@ -691,7 +807,7 @@ ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t written = len - obj->zstream.avail_out; if (st != Z_OK && st != Z_STREAM_END) { - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "error reading from the zlib stream"); return -1; } @@ -736,7 +852,7 @@ int packfile_unpack_compressed( st = inflateInit(&stream); if (st != Z_OK) { git__free(buffer); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "failed to init zlib stream on unpack"); return -1; } @@ -763,7 +879,7 @@ int packfile_unpack_compressed( if ((st != Z_STREAM_END) || stream.total_out != size) { git__free(buffer); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "error inflating zlib stream"); return -1; } @@ -862,6 +978,7 @@ void git_packfile_free(struct git_pack_file *p) git__free(p->bad_object_sha1); git_mutex_free(&p->lock); + git_mutex_free(&p->bases.lock); git__free(p); } @@ -997,6 +1114,11 @@ int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) return -1; } + if (cache_init(&p->bases) < 0) { + git__free(p); + return -1; + } + *pack_out = p; return 0; diff --git a/src/pack.h b/src/pack.h index 58f81e2f0..610e70c18 100644 --- a/src/pack.h +++ b/src/pack.h @@ -17,6 +17,7 @@ #include "mwindow.h" #include "odb.h" #include "oidmap.h" +#include "array.h" #define GIT_PACK_FILE_MODE 0444 @@ -60,6 +61,15 @@ typedef struct git_pack_cache_entry { git_rawobj raw; } git_pack_cache_entry; +struct pack_chain_elem { + git_off_t base_key; + git_off_t offset; + size_t size; + git_otype type; +}; + +typedef git_array_t(struct pack_chain_elem) git_dependency_chain; + #include "offmap.h" GIT__USE_OFFMAP; diff --git a/src/path.c b/src/path.c index 0c1013fa0..377f396d3 100644 --- a/src/path.c +++ b/src/path.c @@ -1135,3 +1135,21 @@ int git_path_dirload_with_stat( return error; } + +int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) +{ + int error; + + /* If url_or_path begins with file:// treat it as a URL */ + if (!git__prefixcmp(url_or_path, "file://")) { + if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) { + return error; + } + } else { /* We assume url_or_path is already a path */ + if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) { + return error; + } + } + + return 0; +} diff --git a/src/path.h b/src/path.h index 3213c5104..3e6efe3de 100644 --- a/src/path.h +++ b/src/path.h @@ -438,4 +438,7 @@ extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen); extern bool git_path_does_fs_decompose_unicode(const char *root); +/* Used for paths to repositories on the filesystem */ +extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path); + #endif diff --git a/src/posix.c b/src/posix.c index 7484ac0d8..7aeb0e6c1 100644 --- a/src/posix.c +++ b/src/posix.c @@ -207,6 +207,13 @@ int p_write(git_file fd, const void *buf, size_t cnt) #include "map.h" +long git__page_size(void) +{ + /* dummy; here we don't need any alignment anyway */ + return 4096; +} + + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { GIT_MMAP_VALIDATE(out, len, prot, flags); diff --git a/src/posix.h b/src/posix.h index f85b1aebd..965cd98d5 100644 --- a/src/posix.h +++ b/src/posix.h @@ -73,6 +73,7 @@ extern int p_rename(const char *from, const char *to); #define p_rmdir(p) rmdir(p) #define p_chmod(p,m) chmod(p, m) #define p_access(p,m) access(p,m) +#define p_ftruncate(fd, sz) ftruncate(fd, sz) #define p_recv(s,b,l,f) recv(s,b,l,f) #define p_send(s,b,l,f) send(s,b,l,f) typedef int GIT_SOCKET; diff --git a/src/refdb_fs.c b/src/refdb_fs.c index f9bd4eab5..0e36ca8ac 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -458,6 +458,7 @@ typedef struct { git_pool pool; git_vector loose; + git_sortedcache *cache; size_t loose_pos; size_t packed_pos; } refdb_fs_iter; @@ -468,6 +469,7 @@ static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) git_vector_free(&iter->loose); git_pool_clear(&iter->pool); + git_sortedcache_free(iter->cache); git__free(iter); } @@ -539,10 +541,14 @@ static int refdb_fs_backend__iterator_next( giterr_clear(); } - git_sortedcache_rlock(backend->refcache); + if (!iter->cache) { + if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) + return error; + } - while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { - ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); if (!ref) /* stop now if another thread deleted refs and we past end */ break; @@ -556,7 +562,6 @@ static int refdb_fs_backend__iterator_next( break; } - git_sortedcache_runlock(backend->refcache); return error; } @@ -579,10 +584,14 @@ static int refdb_fs_backend__iterator_next_name( giterr_clear(); } - git_sortedcache_rlock(backend->refcache); + if (!iter->cache) { + if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) + return error; + } - while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { - ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); if (!ref) /* stop now if another thread deleted refs and we past end */ break; @@ -596,7 +605,6 @@ static int refdb_fs_backend__iterator_next_name( break; } - git_sortedcache_runlock(backend->refcache); return error; } @@ -927,19 +935,15 @@ static int has_reflog(git_repository *repo, const char *name); /* We only write if it's under heads/, remotes/ or notes/ or if it already has a log */ static int should_write_reflog(int *write, git_repository *repo, const char *name) { - git_config *config; - int error, logall, is_bare; - - /* Defaults to the opposite of the repo being bare */ - is_bare = git_repository_is_bare(repo); - logall = !is_bare; + int error, logall; - if ((error = git_repository_config__weakptr(&config, repo)) < 0) + error = git_repository__cvar(&logall, repo, GIT_CVAR_LOGALLREFUPDATES); + if (error < 0) return error; - error = git_config_get_bool(&logall, config, "core.logallrefupdates"); - if (error < 0 && error != GIT_ENOTFOUND) - return error; + /* Defaults to the opposite of the repo being bare */ + if (logall == GIT_LOGALLREFUPDATES_UNSET) + logall = !git_repository_is_bare(repo); if (!logall) { *write = 0; diff --git a/src/refs.c b/src/refs.c index 9428f617d..1603876da 100644 --- a/src/refs.c +++ b/src/refs.c @@ -159,8 +159,7 @@ int git_reference_name_to_id( } static int reference_normalize_for_repo( - char *out, - size_t out_size, + git_refname_t out, git_repository *repo, const char *name) { @@ -171,7 +170,7 @@ static int reference_normalize_for_repo( precompose) flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE; - return git_reference_normalize_name(out, out_size, name, flags); + return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags); } int git_reference_lookup_resolved( @@ -180,7 +179,7 @@ int git_reference_lookup_resolved( const char *name, int max_nesting) { - char scan_name[GIT_REFNAME_MAX]; + git_refname_t scan_name; git_ref_t scan_type; int error = 0, nesting; git_reference *ref = NULL; @@ -197,8 +196,7 @@ int git_reference_lookup_resolved( scan_type = GIT_REF_SYMBOLIC; - if ((error = reference_normalize_for_repo( - scan_name, sizeof(scan_name), repo, name)) < 0) + if ((error = reference_normalize_for_repo(scan_name, repo, name)) < 0) return error; if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) @@ -354,7 +352,7 @@ static int reference__create( const git_oid *old_id, const char *old_target) { - char normalized[GIT_REFNAME_MAX]; + git_refname_t normalized; git_refdb *refdb; git_reference *ref = NULL; int error = 0; @@ -365,7 +363,7 @@ static int reference__create( if (ref_out) *ref_out = NULL; - error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name); + error = reference_normalize_for_repo(normalized, repo, name); if (error < 0) return error; @@ -388,15 +386,14 @@ static int reference__create( return -1; } - ref = git_reference__alloc(name, oid, NULL); + ref = git_reference__alloc(normalized, oid, NULL); } else { - char normalized_target[GIT_REFNAME_MAX]; + git_refname_t normalized_target; - if ((error = git_reference__normalize_name_lax( - normalized_target, sizeof(normalized_target), symbolic)) < 0) + if ((error = reference_normalize_for_repo(normalized_target, repo, symbolic)) < 0) return error; - ref = git_reference__alloc_symbolic(name, normalized_target); + ref = git_reference__alloc_symbolic(normalized, normalized_target); } GITERR_CHECK_ALLOC(ref); @@ -569,18 +566,14 @@ int git_reference_symbolic_set_target( static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force, const git_signature *signature, const char *message) { - unsigned int normalization_flags; - char normalized[GIT_REFNAME_MAX]; + git_refname_t normalized; bool should_head_be_updated = false; int error = 0; assert(ref && new_name && signature); - normalization_flags = ref->type == GIT_REF_SYMBOLIC ? - GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL; - - if ((error = git_reference_normalize_name( - normalized, sizeof(normalized), new_name, normalization_flags)) < 0) + if ((error = reference_normalize_for_repo( + normalized, git_reference_owner(ref), new_name)) < 0) return error; @@ -590,12 +583,12 @@ static int reference__rename(git_reference **out, git_reference *ref, const char should_head_be_updated = (error > 0); - if ((error = git_refdb_rename(out, ref->db, ref->name, new_name, force, signature, message)) < 0) + if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0) return error; /* Update HEAD it was pointing to the reference being renamed */ if (should_head_be_updated && - (error = git_repository_set_head(ref->db->repo, new_name, signature, message)) < 0) { + (error = git_repository_set_head(ref->db->repo, normalized, signature, message)) < 0) { giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); return error; } @@ -1018,17 +1011,6 @@ cleanup: return error; } -int git_reference__normalize_name_lax( - char *buffer_out, - size_t out_size, - const char *name) -{ - return git_reference_normalize_name( - buffer_out, - out_size, - name, - GIT_REF_FORMAT_ALLOW_ONELEVEL); -} #define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC) int git_reference_cmp( diff --git a/src/refs.h b/src/refs.h index d57d67026..f75a4bf7e 100644 --- a/src/refs.h +++ b/src/refs.h @@ -51,6 +51,8 @@ #define GIT_REFNAME_MAX 1024 +typedef char git_refname_t[GIT_REFNAME_MAX]; + struct git_reference { git_refdb *db; git_ref_t type; @@ -66,7 +68,6 @@ struct git_reference { git_reference *git_reference__set_name(git_reference *ref, const char *name); -int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags); int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid, const git_signature *signature, const char *log_message); int git_reference__is_valid_name(const char *refname, unsigned int flags); diff --git a/src/remote.c b/src/remote.c index 31ee97795..0c82433d1 100644 --- a/src/remote.c +++ b/src/remote.c @@ -403,6 +403,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) if (!optional_setting_found) { error = GIT_ENOTFOUND; + giterr_set(GITERR_CONFIG, "Remote '%s' does not exist.", name); goto cleanup; } @@ -1304,13 +1305,14 @@ static int rename_remote_config_section( if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0) goto cleanup; - if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0) - goto cleanup; + if (new_name && + (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0)) + goto cleanup; error = git_config_rename_section( repo, git_buf_cstr(&old_section_name), - git_buf_cstr(&new_section_name)); + new_name ? git_buf_cstr(&new_section_name) : NULL); cleanup: git_buf_free(&old_section_name); @@ -1743,3 +1745,217 @@ int git_remote_init_callbacks(git_remote_callbacks *opts, unsigned int version) opts, version, git_remote_callbacks, GIT_REMOTE_CALLBACKS_INIT); return 0; } + +/* asserts a branch.<foo>.remote format */ +static const char *name_offset(size_t *len_out, const char *name) +{ + size_t prefix_len; + const char *dot; + + prefix_len = strlen("remote."); + dot = strchr(name + prefix_len, '.'); + + assert(dot); + + *len_out = dot - name - prefix_len; + return name + prefix_len; +} + +static int remove_branch_config_related_entries( + git_repository *repo, + const char *remote_name) +{ + int error; + git_config *config; + git_config_entry *entry; + git_config_iterator *iter; + git_buf buf = GIT_BUF_INIT; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0) + return error; + + /* find any branches with us as upstream and remove that config */ + while ((error = git_config_next(&entry, iter)) == 0) { + const char *branch; + size_t branch_len; + + if (strcmp(remote_name, entry->value)) + continue; + + branch = name_offset(&branch_len, entry->name); + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch) < 0) + break; + + if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0) + break; + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch) < 0) + break; + + if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0) + break; + } + + if (error == GIT_ITEROVER) + error = 0; + + git_buf_free(&buf); + git_config_iterator_free(iter); + return error; +} + +static int remove_refs(git_repository *repo, const git_refspec *spec) +{ + git_reference_iterator *iter = NULL; + git_vector refs; + const char *name; + char *dup; + int error; + size_t i; + + if ((error = git_vector_init(&refs, 8, NULL)) < 0) + return error; + + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + goto cleanup; + + while ((error = git_reference_next_name(&name, iter)) == 0) { + if (!git_refspec_dst_matches(spec, name)) + continue; + + dup = git__strdup(name); + if (!dup) { + error = -1; + goto cleanup; + } + + if ((error = git_vector_insert(&refs, dup)) < 0) + goto cleanup; + } + if (error == GIT_ITEROVER) + error = 0; + if (error < 0) + goto cleanup; + + git_vector_foreach(&refs, i, name) { + if ((error = git_reference_remove(repo, name)) < 0) + break; + } + +cleanup: + git_reference_iterator_free(iter); + git_vector_foreach(&refs, i, dup) { + git__free(dup); + } + git_vector_free(&refs); + return error; +} + +static int remove_remote_tracking(git_repository *repo, const char *remote_name) +{ + git_remote *remote; + int error; + size_t i, count; + + /* we want to use what's on the config, regardless of changes to the instance in memory */ + if ((error = git_remote_load(&remote, repo, remote_name)) < 0) + return error; + + count = git_remote_refspec_count(remote); + for (i = 0; i < count; i++) { + const git_refspec *refspec = git_remote_get_refspec(remote, i); + + /* shouldn't ever actually happen */ + if (refspec == NULL) + continue; + + if ((error = remove_refs(repo, refspec)) < 0) + break; + } + + git_remote_free(remote); + return error; +} + +int git_remote_delete(git_remote *remote) +{ + int error; + git_repository *repo; + + assert(remote); + + if (!remote->name) { + giterr_set(GITERR_INVALID, "Can't delete an anonymous remote."); + return -1; + } + + repo = git_remote_owner(remote); + + if ((error = remove_branch_config_related_entries(repo, + git_remote_name(remote))) < 0) + return error; + + if ((error = remove_remote_tracking(repo, git_remote_name(remote))) < 0) + return error; + + if ((error = rename_remote_config_section( + repo, git_remote_name(remote), NULL)) < 0) + return error; + + git_remote_free(remote); + + return 0; +} + +int git_remote_default_branch(git_buf *out, git_remote *remote) +{ + const git_remote_head **heads; + const git_remote_head *guess = NULL; + const git_oid *head_id; + size_t heads_len, i; + int error; + + if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0) + return error; + + if (heads_len == 0) + return GIT_ENOTFOUND; + + git_buf_sanitize(out); + /* the first one must be HEAD so if that has the symref info, we're done */ + if (heads[0]->symref_target) + return git_buf_puts(out, heads[0]->symref_target); + + /* + * If there's no symref information, we have to look over them + * and guess. We return the first match unless the master + * branch is a candidate. Then we return the master branch. + */ + head_id = &heads[0]->oid; + + for (i = 1; i < heads_len; i++) { + if (git_oid_cmp(head_id, &heads[i]->oid)) + continue; + + if (!guess) { + guess = heads[i]; + continue; + } + + if (!git__strcmp(GIT_REFS_HEADS_MASTER_FILE, heads[i]->name)) { + guess = heads[i]; + break; + } + } + + if (!guess) + return GIT_ENOTFOUND; + + return git_buf_puts(out, guess->name); +} diff --git a/src/repository.c b/src/repository.c index 7d055e28e..e8d50aed3 100644 --- a/src/repository.c +++ b/src/repository.c @@ -443,7 +443,6 @@ int git_repository_open_ext( int error; git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT; git_repository *repo; - git_config *config; if (repo_ptr) *repo_ptr = NULL; @@ -458,23 +457,24 @@ int git_repository_open_ext( repo->path_repository = git_buf_detach(&path); GITERR_CHECK_ALLOC(repo->path_repository); - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - return error; - if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) repo->is_bare = 1; - else if ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &parent)) < 0) - { + else { + git_config *config = NULL; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0 || + (error = load_config_data(repo, config)) < 0 || + (error = load_workdir(repo, config, &parent)) < 0) + git_repository_free(repo); + git_config_free(config); - git_repository_free(repo); - return error; } - git_config_free(config); + if (!error) + *repo_ptr = repo; git_buf_free(&parent); - *repo_ptr = repo; - return 0; + + return error; } int git_repository_open(git_repository **repo_out, const char *path) @@ -1190,6 +1190,7 @@ static int repo_init_structure( bool external_tpl = ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); mode_t dmode = pick_dir_mode(opts); + bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK; /* Hide the ".git" directory */ #ifdef GIT_WIN32 @@ -1230,10 +1231,12 @@ static int repo_init_structure( default_template = true; } - if (tdir) - error = git_futils_cp_r(tdir, repo_dir, - GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS | - GIT_CPDIR_SIMPLE_TO_MODE, dmode); + if (tdir) { + uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_SIMPLE_TO_MODE; + if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK) + cpflags |= GIT_CPDIR_CHMOD_DIRS; + error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode); + } git_buf_free(&template_buf); git_config_free(cfg); @@ -1254,9 +1257,14 @@ static int repo_init_structure( * - only create files if no external template was specified */ for (tpl = repo_template; !error && tpl->path; ++tpl) { - if (!tpl->content) + if (!tpl->content) { + uint32_t mkdir_flags = GIT_MKDIR_PATH; + if (chmod) + mkdir_flags |= GIT_MKDIR_CHMOD; + error = git_futils_mkdir( - tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD); + tpl->path, repo_dir, dmode, mkdir_flags); + } else if (!external_tpl) { const char *content = tpl->content; diff --git a/src/repository.h b/src/repository.h index 27eec9dd8..aba16a016 100644 --- a/src/repository.h +++ b/src/repository.h @@ -39,6 +39,7 @@ typedef enum { GIT_CVAR_ABBREV, /* core.abbrev */ GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */ GIT_CVAR_SAFE_CRLF, /* core.safecrlf */ + GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */ GIT_CVAR_CACHE_MAX } git_cvar_cached; @@ -92,6 +93,9 @@ typedef enum { GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE, /* core.safecrlf */ GIT_SAFE_CRLF_DEFAULT = GIT_CVAR_FALSE, + /* core.logallrefupdates */ + GIT_LOGALLREFUPDATES_UNSET = 2, + GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET, } git_cvar_value; /* internal repository init flags */ @@ -363,20 +363,22 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu } /* write the buffer */ - if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0) - return -1; + if ((error = git_odb_open_wstream( + &stream, odb, strlen(buffer), GIT_OBJ_TAG)) < 0) + return error; - git_odb_stream_write(stream, buffer, strlen(buffer)); + if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer)))) + error = git_odb_stream_finalize_write(oid, stream); - error = git_odb_stream_finalize_write(oid, stream); git_odb_stream_free(stream); if (error < 0) { git_buf_free(&ref_name); - return -1; + return error; } - error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL, NULL); + error = git_reference_create( + &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL, NULL); git_reference_free(new_ref); git_buf_free(&ref_name); diff --git a/src/transports/local.c b/src/transports/local.c index 2c17e6271..f859f0b70 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -40,17 +40,29 @@ typedef struct { have_refs : 1; } transport_local; +static void free_head(git_remote_head *head) +{ + git__free(head->name); + git__free(head->symref_target); + git__free(head); +} + static int add_ref(transport_local *t, const char *name) { const char peeled[] = "^{}"; - git_oid head_oid; + git_reference *ref, *resolved; git_remote_head *head; + git_oid obj_id; git_object *obj = NULL, *target = NULL; git_buf buf = GIT_BUF_INIT; int error; - error = git_reference_name_to_id(&head_oid, t->repo, name); + if ((error = git_reference_lookup(&ref, t->repo, name)) < 0) + return error; + + error = git_reference_resolve(&resolved, ref); if (error < 0) { + git_reference_free(ref); if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) { /* This is actually okay. Empty repos often have a HEAD that * points to a nonexistent "refs/heads/master". */ @@ -60,17 +72,25 @@ static int add_ref(transport_local *t, const char *name) return error; } + git_oid_cpy(&obj_id, git_reference_target(resolved)); + git_reference_free(resolved); + head = git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); head->name = git__strdup(name); GITERR_CHECK_ALLOC(head->name); - git_oid_cpy(&head->oid, &head_oid); + git_oid_cpy(&head->oid, &obj_id); + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + head->symref_target = git__strdup(git_reference_symbolic_target(ref)); + GITERR_CHECK_ALLOC(head->symref_target); + } + git_reference_free(ref); if ((error = git_vector_insert(&t->refs, head)) < 0) { - git__free(head->name); - git__free(head); + free_head(head); return error; } @@ -103,8 +123,7 @@ static int add_ref(transport_local *t, const char *name) git_oid_cpy(&head->oid, git_object_id(target)); if ((error = git_vector_insert(&t->refs, head)) < 0) { - git__free(head->name); - git__free(head); + free_head(head); } } @@ -156,27 +175,9 @@ on_error: return -1; } -static int path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) -{ - int error; - - /* If url_or_path begins with file:// treat it as a URL */ - if (!git__prefixcmp(url_or_path, "file://")) { - if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) { - return error; - } - } else { /* We assume url_or_path is already a path */ - if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) { - return error; - } - } - - return 0; -} - /* * Try to open the url as a git directory. The direction doesn't - * matter in this case because we're calulating the heads ourselves. + * matter in this case because we're calculating the heads ourselves. */ static int local_connect( git_transport *transport, @@ -203,7 +204,7 @@ static int local_connect( t->flags = flags; /* 'url' may be a url or path; convert to a path */ - if ((error = path_from_url_or_path(&buf, url)) < 0) { + if ((error = git_path_from_url_or_path(&buf, url)) < 0) { git_buf_free(&buf); return error; } @@ -367,7 +368,7 @@ static int local_push( size_t j; /* 'push->remote->url' may be a url or path; convert to a path */ - if ((error = path_from_url_or_path(&buf, push->remote->url)) < 0) { + if ((error = git_path_from_url_or_path(&buf, push->remote->url)) < 0) { git_buf_free(&buf); return error; } @@ -626,10 +627,8 @@ static void local_free(git_transport *transport) size_t i; git_remote_head *head; - git_vector_foreach(&t->refs, i, head) { - git__free(head->name); - git__free(head); - } + git_vector_foreach(&t->refs, i, head) + free_head(head); git_vector_free(&t->refs); diff --git a/src/transports/smart.c b/src/transports/smart.c index 69eaf9b78..a5c3e82dc 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -7,6 +7,7 @@ #include "git2.h" #include "smart.h" #include "refs.h" +#include "refspec.h" static int git_smart__recv_cb(gitno_buffer *buf) { @@ -63,7 +64,7 @@ static int git_smart__set_callbacks( return 0; } -int git_smart__update_heads(transport_smart *t) +int git_smart__update_heads(transport_smart *t, git_vector *symrefs) { size_t i; git_pkt *pkt; @@ -74,6 +75,25 @@ int git_smart__update_heads(transport_smart *t) if (pkt->type != GIT_PKT_REF) continue; + if (symrefs) { + git_refspec *spec; + git_buf buf = GIT_BUF_INIT; + size_t j; + int error = 0; + + git_vector_foreach(symrefs, j, spec) { + git_buf_clear(&buf); + if (git_refspec_src_matches(spec, ref->head.name) && + !(error = git_refspec_transform(&buf, spec, ref->head.name))) + ref->head.symref_target = git_buf_detach(&buf); + } + + git_buf_free(&buf); + + if (error < 0) + return error; + } + if (git_vector_insert(&t->heads, &ref->head) < 0) return -1; } @@ -81,6 +101,19 @@ int git_smart__update_heads(transport_smart *t) return 0; } +static void free_symrefs(git_vector *symrefs) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(symrefs, i, spec) { + git_refspec__free(spec); + git__free(spec); + } + + git_vector_free(symrefs); +} + static int git_smart__connect( git_transport *transport, const char *url, @@ -94,6 +127,7 @@ static int git_smart__connect( int error; git_pkt *pkt; git_pkt_ref *first; + git_vector symrefs; git_smart_service_t service; if (git_smart__reset_stream(t, true) < 0) @@ -147,8 +181,11 @@ static int git_smart__connect( first = (git_pkt_ref *)git_vector_get(&t->refs, 0); + if ((error = git_vector_init(&symrefs, 1, NULL)) < 0) + return error; + /* Detect capabilities */ - if (git_smart__detect_caps(first, &t->caps) < 0) + if (git_smart__detect_caps(first, &t->caps, &symrefs) < 0) return -1; /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */ @@ -159,7 +196,9 @@ static int git_smart__connect( } /* Keep a list of heads for _ls */ - git_smart__update_heads(t); + git_smart__update_heads(t, &symrefs); + + free_symrefs(&symrefs); if (t->rpc && git_smart__reset_stream(t, false) < 0) return -1; @@ -272,6 +311,18 @@ static int git_smart__close(git_transport *transport) unsigned int i; git_pkt *p; int ret; + git_smart_subtransport_stream *stream; + const char flush[] = "0000"; + + /* + * If we're still connected at this point and not using RPC, + * we should say goodbye by sending a flush, or git-daemon + * will complain that we disconnected unexpectedly. + */ + if (t->connected && !t->rpc && + !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) { + t->current_stream->write(t->current_stream, flush, 4); + } ret = git_smart__reset_stream(t, true); diff --git a/src/transports/smart.h b/src/transports/smart.h index a2b6b2a71..f1fc29520 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -23,6 +23,7 @@ #define GIT_CAP_DELETE_REFS "delete-refs" #define GIT_CAP_REPORT_STATUS "report-status" #define GIT_CAP_THIN_PACK "thin-pack" +#define GIT_CAP_SYMREF "symref" enum git_pkt_type { GIT_PKT_CMD, @@ -154,7 +155,7 @@ typedef struct { /* smart_protocol.c */ int git_smart__store_refs(transport_smart *t, int flushes); -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps); +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs); int git_smart__push(git_transport *transport, git_push *push); int git_smart__negotiate_fetch( @@ -174,7 +175,7 @@ int git_smart__download_pack( int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); -int git_smart__update_heads(transport_smart *t); +int git_smart__update_heads(transport_smart *t, git_vector *symrefs); /* smart_pkt.c */ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index e9376ae6f..b5f9d6dbe 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -433,6 +433,7 @@ void git_pkt_free(git_pkt *pkt) if (pkt->type == GIT_PKT_REF) { git_pkt_ref *p = (git_pkt_ref *) pkt; git__free(p->head.name); + git__free(p->head.symref_target); } if (pkt->type == GIT_PKT_OK) { diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 5dd6bab24..a52aacc60 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -26,17 +26,16 @@ int git_smart__store_refs(transport_smart *t, int flushes) int error, flush = 0, recvd; const char *line_end = NULL; git_pkt *pkt = NULL; - git_pkt_ref *ref; size_t i; /* Clear existing refs in case git_remote_connect() is called again * after git_remote_disconnect(). */ - git_vector_foreach(refs, i, ref) { - git__free(ref->head.name); - git__free(ref); + git_vector_foreach(refs, i, pkt) { + git_pkt_free(pkt); } git_vector_clear(refs); + pkt = NULL; do { if (buf->offset > 0) @@ -78,7 +77,52 @@ int git_smart__store_refs(transport_smart *t, int flushes) return flush; } -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) +static int append_symref(const char **out, git_vector *symrefs, const char *ptr) +{ + int error; + const char *end; + git_buf buf = GIT_BUF_INIT; + git_refspec *mapping; + + ptr += strlen(GIT_CAP_SYMREF); + if (*ptr != '=') + goto on_invalid; + + ptr++; + if (!(end = strchr(ptr, ' ')) && + !(end = strchr(ptr, '\0'))) + goto on_invalid; + + if ((error = git_buf_put(&buf, ptr, end - ptr)) < 0) + return error; + + /* symref mapping has refspec format */ + mapping = git__malloc(sizeof(git_refspec)); + GITERR_CHECK_ALLOC(mapping); + + error = git_refspec__parse(mapping, git_buf_cstr(&buf), true); + git_buf_free(&buf); + + /* if the error isn't OOM, then it's a parse error; let's use a nicer message */ + if (error < 0) { + if (giterr_last()->klass != GITERR_NOMEMORY) + goto on_invalid; + + return error; + } + + if ((error = git_vector_insert(symrefs, mapping)) < 0) + return error; + + *out = end; + return 0; + +on_invalid: + giterr_set(GITERR_NET, "remote sent invalid symref"); + return -1; +} + +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs) { const char *ptr; @@ -141,6 +185,15 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) continue; } + if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) { + int error; + + if ((error = append_symref(&ptr, symrefs, ptr)) < 0) + return error; + + continue; + } + /* We don't know this capability, so skip it */ ptr = strchr(ptr, ' '); } @@ -969,7 +1022,7 @@ int git_smart__push(git_transport *transport, git_push *push) if (error < 0) goto done; - error = git_smart__update_heads(t); + error = git_smart__update_heads(t, NULL); } done: diff --git a/src/unix/map.c b/src/unix/map.c index e62ab3e76..3d0cbbaf8 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -10,8 +10,14 @@ #include "map.h" #include <sys/mman.h> +#include <unistd.h> #include <errno.h> +long git__page_size(void) +{ + return sysconf(_SC_PAGE_SIZE); +} + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { int mprot = 0; diff --git a/src/util.h b/src/util.h index 6fb2dc0f4..ca676c039 100644 --- a/src/util.h +++ b/src/util.h @@ -133,6 +133,13 @@ GIT_INLINE(int) git__is_uint32(size_t p) return p == (size_t)r; } +/** @return true if p fits into the range of an unsigned long */ +GIT_INLINE(int) git__is_ulong(git_off_t p) +{ + unsigned long r = (unsigned long)p; + return p == (git_off_t)r; +} + /* 32-bit cross-platform rotl */ #ifdef _MSC_VER /* use built-in method in MSVC */ # define git__rotl(v, s) (uint32_t)_rotl(v, s) diff --git a/src/win32/map.c b/src/win32/map.c index 902ea3994..ef83f882e 100644 --- a/src/win32/map.c +++ b/src/win32/map.c @@ -23,6 +23,11 @@ static DWORD get_page_size(void) return page_size; } +long git__page_size(void) +{ + return (long)get_page_size(); +} + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { HANDLE fh = (HANDLE)_get_osfhandle(fd); diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 8f51d6f5a..059e39cbc 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -10,6 +10,7 @@ #if defined(__MINGW32__) /* use a 64-bit file offset type */ +# undef lseek # define lseek _lseeki64 # undef stat # define stat _stati64 diff --git a/src/win32/posix.h b/src/win32/posix.h index 7f9d57cc3..2cbea1807 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -19,6 +19,12 @@ # define EAFNOSUPPORT (INT_MAX-1) #endif +#ifdef _MSC_VER +# define p_ftruncate(fd, sz) _chsize_s(fd, sz) +#else /* MinGW */ +# define p_ftruncate(fd, sz) _chsize(fd, sz) +#endif + GIT_INLINE(int) p_link(const char *old, const char *new) { GIT_UNUSED(old); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 0d070f6b5..34938431a 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -19,6 +19,15 @@ # define FILE_NAME_NORMALIZED 0 #endif +/* Options which we always provide to _wopen. + * + * _O_BINARY - Raw access; no translation of CR or LF characters + * _O_NOINHERIT - Do not mark the created handle as inheritable by child processes. + * The Windows default is 'not inheritable', but the CRT's default (following + * POSIX convention) is 'inheritable'. We have no desire for our handles to be + * inheritable on Windows, so specify the flag to get default behavior back. */ +#define STANDARD_OPEN_FLAGS (_O_BINARY | _O_NOINHERIT) + /* GetFinalPathNameByHandleW signature */ typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD); @@ -317,7 +326,7 @@ int p_open(const char *path, int flags, ...) va_end(arg_list); } - return _wopen(buf, flags | _O_BINARY, mode); + return _wopen(buf, flags | STANDARD_OPEN_FLAGS, mode); } int p_creat(const char *path, mode_t mode) @@ -327,7 +336,7 @@ int p_creat(const char *path, mode_t mode) if (utf8_to_16_with_errno(buf, path) < 0) return -1; - return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); + return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS, mode); } int p_getcwd(char *buffer_out, size_t size) @@ -569,7 +578,7 @@ int p_mkstemp(char *tmp_path) return -1; #endif - return p_creat(tmp_path, 0744); //-V536 + return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536 } int p_access(const char* path, mode_t mode) diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c index b2730f4d1..0a4c3e8e5 100644 --- a/tests/clar_libgit2.c +++ b/tests/clar_libgit2.c @@ -518,3 +518,16 @@ void cl_fake_home(void) GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); git_buf_free(&path); } + +void cl_sandbox_set_search_path_defaults(void) +{ + const char *sandbox_path = clar_sandbox_path(); + + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, sandbox_path); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, sandbox_path); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, sandbox_path); +} + diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h index f84d9e353..da37bd655 100644 --- a/tests/clar_libgit2.h +++ b/tests/clar_libgit2.h @@ -139,4 +139,6 @@ void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value void cl_fake_home(void); void cl_fake_home_cleanup(void *); +void cl_sandbox_set_search_path_defaults(void); + #endif diff --git a/tests/clone/local.c b/tests/clone/local.c new file mode 100644 index 000000000..a4406c1cc --- /dev/null +++ b/tests/clone/local.c @@ -0,0 +1,105 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "clone.h" +#include "buffer.h" +#include "path.h" +#include "posix.h" +#include "fileops.h" + +void test_clone_local__should_clone_local(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *path; + + /* we use a fixture path because it needs to exist for us to want to clone */ + + cl_git_pass(git_buf_printf(&buf, "file://%s", cl_fixture("testrepo.git"))); + cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(true, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(true, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + git_buf_free(&buf); + + path = cl_fixture("testrepo.git"); + cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL)); + cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS)); + cl_assert_equal_i(false, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL)); +} + +void test_clone_local__hardlinks(void) +{ + git_repository *repo; + git_remote *remote; + git_signature *sig; + git_buf buf = GIT_BUF_INIT; + struct stat st; + + + /* + * In this first clone, we just copy over, since the temp dir + * will often be in a different filesystem, so we cannot + * link. It also allows us to control the number of links + */ + cl_git_pass(git_repository_init(&repo, "./clone.git", true)); + cl_git_pass(git_remote_create(&remote, repo, "origin", cl_fixture("testrepo.git"))); + cl_git_pass(git_signature_now(&sig, "foo", "bar")); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, false, sig)); + + git_remote_free(remote); + git_repository_free(repo); + + /* This second clone is in the same filesystem, so we can hardlink */ + + cl_git_pass(git_repository_init(&repo, "./clone2.git", true)); + cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git"))); + cl_git_pass(git_remote_create(&remote, repo, "origin", buf.ptr)); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, true, sig)); + +#ifndef GIT_WIN32 + git_buf_clear(&buf); + cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(2, st.st_nlink); +#endif + + git_remote_free(remote); + git_repository_free(repo); + git_buf_clear(&buf); + + cl_git_pass(git_repository_init(&repo, "./clone3.git", true)); + cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git"))); + cl_git_pass(git_remote_create(&remote, repo, "origin", buf.ptr)); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, false, sig)); + + git_buf_clear(&buf); + cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(1, st.st_nlink); + + git_remote_free(remote); + git_repository_free(repo); + + /* this one should automatically use links */ + cl_git_pass(git_clone(&repo, "./clone.git", "./clone4.git", NULL)); + +#ifndef GIT_WIN32 + git_buf_clear(&buf); + cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(3, st.st_nlink); +#endif + + git_buf_free(&buf); + git_signature_free(sig); + git_repository_free(repo); + + cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone2.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone3.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone4.git", NULL, GIT_RMDIR_REMOVE_FILES)); +} diff --git a/tests/config/config_helpers.c b/tests/config/config_helpers.c index 53bd945a0..35da720e0 100644 --- a/tests/config/config_helpers.c +++ b/tests/config/config_helpers.c @@ -35,3 +35,31 @@ void assert_config_entry_value( cl_assert_equal_s(expected_value, out); } + +static int count_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + int *how_many = (int *)payload; + + GIT_UNUSED(entry); + + (*how_many)++; + + return 0; +} + +int count_config_entries_match(git_repository *repo, const char *pattern) +{ + git_config *config; + int how_many = 0; + + cl_git_pass(git_repository_config(&config, repo)); + + cl_assert_equal_i(0, git_config_foreach_match( + config, pattern, count_config_entries_cb, &how_many)); + + git_config_free(config); + + return how_many; +} diff --git a/tests/config/config_helpers.h b/tests/config/config_helpers.h index b887b3d38..440645730 100644 --- a/tests/config/config_helpers.h +++ b/tests/config/config_helpers.h @@ -7,3 +7,7 @@ extern void assert_config_entry_value( git_repository *repo, const char *name, const char *expected_value); + +extern int count_config_entries_match( + git_repository *repo, + const char *pattern); diff --git a/tests/config/global.c b/tests/config/global.c index 006b34628..fc471f90d 100644 --- a/tests/config/global.c +++ b/tests/config/global.c @@ -2,25 +2,10 @@ #include "buffer.h" #include "fileops.h" -static git_config_level_t setting[3] = { - GIT_CONFIG_LEVEL_GLOBAL, - GIT_CONFIG_LEVEL_XDG, - GIT_CONFIG_LEVEL_SYSTEM -}; -static char *restore[3]; - void test_config_global__initialize(void) { - int i; git_buf path = GIT_BUF_INIT; - /* snapshot old settings to restore later */ - for (i = 0; i < 3; ++i) { - cl_git_pass( - git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, setting[i], &path)); - restore[i] = git_buf_detach(&path); - } - cl_git_pass(git_futils_mkdir_r("home", NULL, 0777)); cl_git_pass(git_path_prettify(&path, "home", NULL)); cl_git_pass(git_libgit2_opts( @@ -41,18 +26,7 @@ void test_config_global__initialize(void) void test_config_global__cleanup(void) { - int i; - - for (i = 0; i < 3; ++i) { - cl_git_pass( - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, setting[i], restore[i])); - git__free(restore[i]); - restore[i] = NULL; - } - - cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("xdg", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("etc", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_sandbox_set_search_path_defaults(); } void test_config_global__open_global(void) diff --git a/tests/config/include.c b/tests/config/include.c index 535573808..58bc690ff 100644 --- a/tests/config/include.c +++ b/tests/config/include.c @@ -47,6 +47,8 @@ void test_config_include__homedir(void) cl_assert_equal_s(str, "huzzah"); git_config_free(cfg); + + cl_sandbox_set_search_path_defaults(); } void test_config_include__refresh(void) diff --git a/tests/core/copy.c b/tests/core/copy.c index c0c59c056..04b2dfab5 100644 --- a/tests/core/copy.c +++ b/tests/core/copy.c @@ -45,6 +45,16 @@ void test_core_copy__file_in_dir(void) cl_assert(!git_path_isdir("an_dir")); } +void assert_hard_link(const char *path) +{ + /* we assert this by checking that there's more than one link to the file */ + struct stat st; + + cl_assert(git_path_isfile(path)); + cl_git_pass(p_stat(path, &st)); + cl_assert(st.st_nlink > 1); +} + void test_core_copy__tree(void) { struct stat st; @@ -122,5 +132,21 @@ void test_core_copy__tree(void) cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES)); cl_assert(!git_path_isdir("t2")); +#ifndef GIT_WIN32 + cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0)); + cl_assert(git_path_isdir("t3")); + + cl_assert(git_path_isdir("t3")); + cl_assert(git_path_isdir("t3/b")); + cl_assert(git_path_isdir("t3/c")); + cl_assert(git_path_isdir("t3/c/d")); + cl_assert(git_path_isdir("t3/c/e")); + + assert_hard_link("t3/f1"); + assert_hard_link("t3/b/f2"); + assert_hard_link("t3/c/f3"); + assert_hard_link("t3/c/d/f4"); +#endif + cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES)); } diff --git a/tests/core/env.c b/tests/core/env.c index df1d92a02..293b786db 100644 --- a/tests/core/env.c +++ b/tests/core/env.c @@ -40,12 +40,12 @@ void test_core_env__initialize(void) } } -static void reset_global_search_path(void) +static void set_global_search_path_from_env(void) { cl_git_pass(git_sysdir_set(GIT_SYSDIR_GLOBAL, NULL)); } -static void reset_system_search_path(void) +static void set_system_search_path_from_env(void) { cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, NULL)); } @@ -69,9 +69,7 @@ void test_core_env__cleanup(void) (void)p_rmdir(*val); } - /* reset search paths to default */ - reset_global_search_path(); - reset_system_search_path(); + cl_sandbox_set_search_path_defaults(); } static void setenv_and_check(const char *name, const char *value) @@ -124,12 +122,12 @@ void test_core_env__0(void) GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); setenv_and_check("HOME", path.ptr); - reset_global_search_path(); + set_global_search_path_from_env(); cl_git_pass(git_sysdir_find_global_file(&found, testfile)); cl_setenv("HOME", env_save[0]); - reset_global_search_path(); + set_global_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); @@ -138,7 +136,7 @@ void test_core_env__0(void) setenv_and_check("HOMEDRIVE", NULL); setenv_and_check("HOMEPATH", NULL); setenv_and_check("USERPROFILE", path.ptr); - reset_global_search_path(); + set_global_search_path_from_env(); cl_git_pass(git_sysdir_find_global_file(&found, testfile)); @@ -148,7 +146,7 @@ void test_core_env__0(void) if (root >= 0) { setenv_and_check("USERPROFILE", NULL); - reset_global_search_path(); + set_global_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); @@ -158,7 +156,7 @@ void test_core_env__0(void) setenv_and_check("HOMEDRIVE", path.ptr); path.ptr[root] = old; setenv_and_check("HOMEPATH", &path.ptr[root]); - reset_global_search_path(); + set_global_search_path_from_env(); cl_git_pass(git_sysdir_find_global_file(&found, testfile)); } @@ -185,7 +183,7 @@ void test_core_env__1(void) cl_git_pass(cl_setenv("HOMEPATH", "doesnotexist")); cl_git_pass(cl_setenv("USERPROFILE", "doesnotexist")); #endif - reset_global_search_path(); + set_global_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); @@ -195,8 +193,8 @@ void test_core_env__1(void) cl_git_pass(cl_setenv("HOMEPATH", NULL)); cl_git_pass(cl_setenv("USERPROFILE", NULL)); #endif - reset_global_search_path(); - reset_system_search_path(); + set_global_search_path_from_env(); + set_system_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); @@ -206,7 +204,7 @@ void test_core_env__1(void) #ifdef GIT_WIN32 cl_git_pass(cl_setenv("PROGRAMFILES", NULL)); - reset_system_search_path(); + set_system_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_system_file(&path, "nonexistentfile")); diff --git a/tests/diff/iterator.c b/tests/diff/iterator.c index cdc64eb1d..a2df1c7a7 100644 --- a/tests/diff/iterator.c +++ b/tests/diff/iterator.c @@ -647,7 +647,7 @@ static void workdir_iterator_test( void test_diff_iterator__workdir_0(void) { - workdir_iterator_test("attr", NULL, NULL, 27, 1, NULL, "ign"); + workdir_iterator_test("attr", NULL, NULL, 23, 5, NULL, "ign"); } static const char *status_paths[] = { diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index a6d48abc6..963be9481 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -1580,3 +1580,117 @@ void test_diff_workdir__can_update_index(void) git_diff_free(diff); } + +#define STR7 "0123456" +#define STR8 "01234567" +#define STR40 STR8 STR8 STR8 STR8 STR8 +#define STR200 STR40 STR40 STR40 STR40 STR40 +#define STR999Z STR200 STR200 STR200 STR200 STR40 STR40 STR40 STR40 \ + STR8 STR8 STR8 STR8 STR7 "\0" +#define STR1000 STR200 STR200 STR200 STR200 STR200 +#define STR3999Z STR1000 STR1000 STR1000 STR999Z +#define STR4000 STR1000 STR1000 STR1000 STR1000 + +static void assert_delta_binary(git_diff *diff, size_t idx, int is_binary) +{ + git_patch *patch; + const git_diff_delta *delta; + + cl_git_pass(git_patch_from_diff(&patch, diff, idx)); + delta = git_patch_get_delta(patch); + cl_assert_equal_b((delta->flags & GIT_DIFF_FLAG_BINARY), is_binary); + git_patch_free(patch); +} + +void test_diff_workdir__binary_detection(void) +{ + git_index *idx; + git_diff *diff = NULL; + git_buf b = GIT_BUF_INIT; + int i; + git_buf data[10] = { + { "1234567890", 0, 0 }, /* 0 - all ascii text control */ + { "\xC3\x85\xC3\xBC\xE2\x80\xA0\x48\xC3\xB8\xCF\x80\xCE\xA9", 0, 0 }, /* 1 - UTF-8 multibyte text */ + { "\xEF\xBB\xBF\xC3\x9C\xE2\xA4\x92\xC6\x92\x38\xC2\xA3\xE2\x82\xAC", 0, 0 }, /* 2 - UTF-8 with BOM */ + { STR999Z, 0, 1000 }, /* 3 - ASCII with NUL at 1000 */ + { STR3999Z, 0, 4000 }, /* 4 - ASCII with NUL at 4000 */ + { STR4000 STR3999Z "x", 0, 8001 }, /* 5 - ASCII with NUL at 8000 */ + { STR4000 STR4000 "\0", 0, 8001 }, /* 6 - ASCII with NUL at 8001 */ + { "\x00\xDC\x00\x6E\x21\x39\xFE\x0E\x00\x63\x00\xF8" + "\x00\x64\x00\x65\x20\x48", 0, 18 }, /* 7 - UTF-16 text */ + { "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d", + 0, 26 }, /* 8 - All non-printable characters (no NUL) */ + { "Hello \x01\x02\x03\x04\x05\x06 World!\x01\x02\x03\x04" + "\x05\x06\x07", 0, 26 }, /* 9 - 50-50 non-printable (no NUL) */ + }; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* We start with ASCII in index and test data in workdir, + * then we will try with test data in index and ASCII in workdir. + */ + + cl_git_pass(git_buf_sets(&b, "empty_standard_repo/0")); + for (i = 0; i < 10; ++i) { + b.ptr[b.size - 1] = '0' + i; + cl_git_mkfile(b.ptr, "baseline"); + cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1])); + + if (data[i].size == 0) + data[i].size = strlen(data[i].ptr); + cl_git_write2file( + b.ptr, data[i].ptr, data[i].size, O_WRONLY|O_TRUNC, 0664); + } + git_index_write(idx); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + cl_assert_equal_i(10, git_diff_num_deltas(diff)); + + /* using diff binary detection (i.e. looking for NUL byte) */ + assert_delta_binary(diff, 0, false); + assert_delta_binary(diff, 1, false); + assert_delta_binary(diff, 2, false); + assert_delta_binary(diff, 3, true); + assert_delta_binary(diff, 4, true); + assert_delta_binary(diff, 5, true); + assert_delta_binary(diff, 6, false); + assert_delta_binary(diff, 7, true); + assert_delta_binary(diff, 8, false); + assert_delta_binary(diff, 9, false); + /* The above have been checked to match command-line Git */ + + git_diff_free(diff); + + cl_git_pass(git_buf_sets(&b, "empty_standard_repo/0")); + for (i = 0; i < 10; ++i) { + b.ptr[b.size - 1] = '0' + i; + cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1])); + + cl_git_write2file(b.ptr, "baseline\n", 9, O_WRONLY|O_TRUNC, 0664); + } + git_index_write(idx); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + cl_assert_equal_i(10, git_diff_num_deltas(diff)); + + /* using diff binary detection (i.e. looking for NUL byte) */ + assert_delta_binary(diff, 0, false); + assert_delta_binary(diff, 1, false); + assert_delta_binary(diff, 2, false); + assert_delta_binary(diff, 3, true); + assert_delta_binary(diff, 4, true); + assert_delta_binary(diff, 5, true); + assert_delta_binary(diff, 6, false); + assert_delta_binary(diff, 7, true); + assert_delta_binary(diff, 8, false); + assert_delta_binary(diff, 9, false); + + git_diff_free(diff); + + git_index_free(idx); + git_buf_free(&b); +} diff --git a/tests/filter/crlf.c b/tests/filter/crlf.c index 66c267e31..a31dac965 100644 --- a/tests/filter/crlf.c +++ b/tests/filter/crlf.c @@ -103,12 +103,12 @@ void test_filter_crlf__with_safecrlf(void) cl_git_fail(git_filter_list_apply_to_data(&out, fl, &in)); cl_assert_equal_i(giterr_last()->klass, GITERR_FILTER); - /* Normalized \n fails with safecrlf */ + /* Normalized \n is reversible, so does not fail with safecrlf */ in.ptr = "Normal\nLF\nonly\nline-endings.\n"; in.size = strlen(in.ptr); - cl_git_fail(git_filter_list_apply_to_data(&out, fl, &in)); - cl_assert_equal_i(giterr_last()->klass, GITERR_FILTER); + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + cl_assert_equal_s(in.ptr, out.ptr); git_filter_list_free(fl); git_buf_free(&out); @@ -196,3 +196,44 @@ void test_filter_crlf__no_safecrlf(void) git_buf_free(&out); } +void test_filter_crlf__safecrlf_warn(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf in = {0}, out = GIT_BUF_INIT; + + cl_repo_set_string(g_repo, "core.safecrlf", "warn"); + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + /* Normalized \r\n succeeds with safecrlf=warn */ + in.ptr = "Normal\r\nCRLF\r\nline-endings.\r\n"; + in.size = strlen(in.ptr); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + + /* Mix of line endings succeeds with safecrlf=warn */ + in.ptr = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; + in.size = strlen(in.ptr); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + /* TODO: check for warning */ + cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); + + /* Normalized \n is reversible, so does not fail with safecrlf=warn */ + in.ptr = "Normal\nLF\nonly\nline-endings.\n"; + in.size = strlen(in.ptr); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + cl_assert_equal_s(in.ptr, out.ptr); + + git_filter_list_free(fl); + git_buf_free(&out); +} diff --git a/tests/index/crlf.c b/tests/index/crlf.c index cf69c6226..7babd5939 100644 --- a/tests/index/crlf.c +++ b/tests/index/crlf.c @@ -134,3 +134,21 @@ void test_index_crlf__autocrlf_input_text_auto_attr(void) cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); cl_assert(git_oid_cmp(&oid, &entry->id) == 0); } + +void test_index_crlf__safecrlf_true_no_attrs(void) +{ + cl_repo_set_bool(g_repo, "core.autocrlf", true); + cl_repo_set_bool(g_repo, "core.safecrlf", true); + + cl_git_mkfile("crlf/newfile.txt", ALL_LF_TEXT_RAW); + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", ALL_CRLF_TEXT_RAW); + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", MORE_CRLF_TEXT_RAW); + cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", MORE_LF_TEXT_RAW); + cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); +} diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c index 013932696..58d7935a0 100644 --- a/tests/index/filemodes.c +++ b/tests/index/filemodes.c @@ -152,3 +152,20 @@ void test_index_filemodes__trusted(void) git_index_free(index); } + +void test_index_filemodes__invalid(void) +{ + git_index *index; + git_index_entry entry; + + cl_git_pass(git_repository_index(&index, g_repo)); + + entry.path = "foo"; + entry.mode = GIT_OBJ_BLOB; + cl_git_fail(git_index_add(index, &entry)); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add(index, &entry)); + + git_index_free(index); +} diff --git a/tests/main.c b/tests/main.c index ffbbcbf48..3de4f9801 100644 --- a/tests/main.c +++ b/tests/main.c @@ -6,16 +6,12 @@ int __cdecl main(int argc, char *argv[]) int main(int argc, char *argv[]) #endif { - const char *sandbox_path; int res; clar_test_init(argc, argv); git_threads_init(); - - sandbox_path = clar_sandbox_path(); - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, sandbox_path); - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, sandbox_path); + cl_sandbox_set_search_path_defaults(); /* Run the test suite */ res = clar_test_run(); diff --git a/tests/merge/workdir/analysis.c b/tests/merge/workdir/analysis.c index 0e937857f..85918abe4 100644 --- a/tests/merge/workdir/analysis.c +++ b/tests/merge/workdir/analysis.c @@ -36,72 +36,105 @@ void test_merge_workdir_analysis__cleanup(void) cl_git_sandbox_cleanup(); } -static git_merge_analysis_t analysis_from_branch(const char *branchname) +static void analysis_from_branch( + git_merge_analysis_t *merge_analysis, + git_merge_preference_t *merge_pref, + const char *branchname) { git_buf refname = GIT_BUF_INIT; git_reference *their_ref; git_merge_head *their_head; - git_merge_analysis_t analysis; git_buf_printf(&refname, "%s%s", GIT_REFS_HEADS_DIR, branchname); cl_git_pass(git_reference_lookup(&their_ref, repo, git_buf_cstr(&refname))); cl_git_pass(git_merge_head_from_ref(&their_head, repo, their_ref)); - cl_git_pass(git_merge_analysis(&analysis, repo, (const git_merge_head **)&their_head, 1)); + cl_git_pass(git_merge_analysis(merge_analysis, merge_pref, repo, (const git_merge_head **)&their_head, 1)); git_buf_free(&refname); git_merge_head_free(their_head); git_reference_free(their_ref); - - return analysis; } void test_merge_workdir_analysis__fastforward(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; - analysis = analysis_from_branch(FASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (analysis & GIT_MERGE_ANALYSIS_NORMAL)); + analysis_from_branch(&merge_analysis, &merge_pref, FASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); } void test_merge_workdir_analysis__no_fastforward(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; - analysis = analysis_from_branch(NOFASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, analysis); + analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); } void test_merge_workdir_analysis__uptodate(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; - analysis = analysis_from_branch(UPTODATE_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, analysis); + analysis_from_branch(&merge_analysis, &merge_pref, UPTODATE_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } void test_merge_workdir_analysis__uptodate_merging_prev_commit(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; - analysis = analysis_from_branch(PREVIOUS_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, analysis); + analysis_from_branch(&merge_analysis, &merge_pref, PREVIOUS_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } void test_merge_workdir_analysis__unborn(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; git_buf master = GIT_BUF_INIT; git_buf_joinpath(&master, git_repository_path(repo), "refs/heads/master"); p_unlink(git_buf_cstr(&master)); - analysis = analysis_from_branch(NOFASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UNBORN, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); + analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UNBORN, (merge_analysis & GIT_MERGE_ANALYSIS_UNBORN)); git_buf_free(&master); } +void test_merge_workdir_analysis__fastforward_with_config_noff(void) +{ + git_config *config; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + git_repository_config(&config, repo); + git_config_set_string(config, "merge.ff", "false"); + + analysis_from_branch(&merge_analysis, &merge_pref, FASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); + cl_assert_equal_i(GIT_MERGE_PREFERENCE_NO_FASTFORWARD, (merge_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD)); +} + +void test_merge_workdir_analysis__no_fastforward_with_config_ffonly(void) +{ + git_config *config; + git_merge_analysis_t merge_analysis; + git_merge_preference_t merge_pref; + + git_repository_config(&config, repo); + git_config_set_string(config, "merge.ff", "only"); + + analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); + cl_assert_equal_i(GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY, (merge_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY)); +} diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 4c39394bb..0d23bef48 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -86,3 +86,29 @@ void test_network_fetchlocal__partial(void) git_strarray_free(&refnames); git_remote_free(origin); } + +void test_network_fetchlocal__clone_into_mirror(void) +{ + git_buf path = GIT_BUF_INIT; + git_repository *repo; + git_remote *remote; + git_reference *head; + + cl_git_pass(git_repository_init(&repo, "./foo.git", true)); + cl_git_pass(git_remote_create(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"))); + + git_remote_clear_refspecs(remote); + cl_git_pass(git_remote_add_fetch(remote, "+refs/*:refs/*")); + + cl_git_pass(git_clone_into(repo, remote, NULL, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&head, repo, "HEAD")); + cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + + git_remote_free(remote); + git_reference_free(head); + git_repository_free(repo); + git_buf_free(&path); + cl_fixture_cleanup("./foo.git"); +} diff --git a/tests/network/remote/defaultbranch.c b/tests/network/remote/defaultbranch.c new file mode 100644 index 000000000..fa3a329db --- /dev/null +++ b/tests/network/remote/defaultbranch.c @@ -0,0 +1,50 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "refspec.h" +#include "remote.h" + +static git_remote *g_remote; +static git_repository *g_repo_a, *g_repo_b; + +void test_network_remote_defaultbranch__initialize(void) +{ + g_repo_a = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_init(&g_repo_b, "repo-b.git", true)); + cl_git_pass(git_remote_create(&g_remote, g_repo_b, "origin", git_repository_path(g_repo_a))); +} + +void test_network_remote_defaultbranch__cleanup(void) +{ + git_remote_free(g_remote); + git_repository_free(g_repo_b); + + cl_git_sandbox_cleanup(); + cl_fixture_cleanup("repo-b.git"); +} + +static void assert_default_branch(const char *should) +{ + git_buf name = GIT_BUF_INIT; + + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH)); + cl_git_pass(git_remote_default_branch(&name, g_remote)); + cl_assert_equal_s(should, name.ptr); + git_buf_free(&name); +} + +void test_network_remote_defaultbranch__master(void) +{ + assert_default_branch("refs/heads/master"); +} + +void test_network_remote_defaultbranch__master_does_not_win(void) +{ + cl_git_pass(git_repository_set_head(g_repo_a, "refs/heads/not-good", NULL, NULL)); + assert_default_branch("refs/heads/not-good"); +} + +void test_network_remote_defaultbranch__master_on_detached(void) +{ + cl_git_pass(git_repository_detach_head(g_repo_a, NULL, NULL)); + assert_default_branch("refs/heads/master"); +} diff --git a/tests/network/remote/delete.c b/tests/network/remote/delete.c new file mode 100644 index 000000000..db55b0768 --- /dev/null +++ b/tests/network/remote/delete.c @@ -0,0 +1,61 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +#include "repository.h" + +static git_remote *_remote; +static git_repository *_repo; + +void test_network_remote_delete__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_remote_load(&_remote, _repo, "test")); +} + +void test_network_remote_delete__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_network_remote_delete__cannot_delete_an_anonymous_remote(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create_anonymous(&remote, _repo, "git://github.com/libgit2/libgit2", NULL)); + + cl_git_fail(git_remote_delete(remote)); + + git_remote_free(remote); + git_remote_free(_remote); +} + +void test_network_remote_delete__remove_remote_tracking_branches(void) +{ + git_reference *ref; + + cl_git_pass(git_remote_delete(_remote)); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, _repo, "refs/remotes/test/master")); +} + +void test_network_remote_delete__remove_remote_configuration_settings(void) +{ + cl_assert(count_config_entries_match(_repo, "remote\\.test\\.+") > 0); + + cl_git_pass(git_remote_delete(_remote)); + + cl_assert_equal_i(0, count_config_entries_match(_repo, "remote\\.test\\.+")); +} + +void test_network_remote_delete__remove_branch_upstream_configuration_settings(void) +{ + assert_config_entry_existence(_repo, "branch.mergeless.remote", true); + assert_config_entry_existence(_repo, "branch.master.remote", true); + + cl_git_pass(git_remote_delete(_remote)); + + assert_config_entry_existence(_repo, "branch.mergeless.remote", false); + assert_config_entry_existence(_repo, "branch.mergeless.merge", false); + assert_config_entry_existence(_repo, "branch.master.remote", false); + assert_config_entry_existence(_repo, "branch.master.merge", false); +} diff --git a/tests/network/remote/remotes.c b/tests/network/remote/remotes.c index 306ccaee5..333b52a5b 100644 --- a/tests/network/remote/remotes.c +++ b/tests/network/remote/remotes.c @@ -60,6 +60,15 @@ void test_network_remote_remotes__pushurl(void) cl_assert(git_remote_pushurl(_remote) == NULL); } +void test_network_remote_remotes__error_when_not_found(void) +{ + git_remote *r; + cl_git_fail_with(git_remote_load(&r, _repo, "does-not-exist"), GIT_ENOTFOUND); + + cl_assert(giterr_last() != NULL); + cl_assert(giterr_last()->klass == GITERR_CONFIG); +} + void test_network_remote_remotes__error_when_no_push_available(void) { git_remote *r; diff --git a/tests/object/commit/commitstagedfile.c b/tests/object/commit/commitstagedfile.c index 9758ea9a2..5b48519b8 100644 --- a/tests/object/commit/commitstagedfile.c +++ b/tests/object/commit/commitstagedfile.c @@ -112,7 +112,7 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); memset(&buffer, 0, sizeof(git_buf)); - cl_git_pass(git_message_prettify(&buffer, "Initial commit", 0)); + cl_git_pass(git_message_prettify(&buffer, "Initial commit", 0, '#')); cl_git_pass(git_commit_create_v( &commit_oid, diff --git a/tests/object/message.c b/tests/object/message.c index ab5565341..40d8e7297 100644 --- a/tests/object/message.c +++ b/tests/object/message.c @@ -6,7 +6,7 @@ static void assert_message_prettifying(char *expected_output, char *input, int s { git_buf prettified_message = GIT_BUF_INIT; - git_message_prettify(&prettified_message, input, strip_comments); + git_message_prettify(&prettified_message, input, strip_comments, '#'); cl_assert_equal_s(expected_output, git_buf_cstr(&prettified_message)); git_buf_free(&prettified_message); @@ -175,25 +175,25 @@ void test_object_message__message_prettify(void) git_buf buffer; memset(&buffer, 0, sizeof(buffer)); - cl_git_pass(git_message_prettify(&buffer, "", 0)); + cl_git_pass(git_message_prettify(&buffer, "", 0, '#')); cl_assert_equal_s(buffer.ptr, ""); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "", 1)); + cl_git_pass(git_message_prettify(&buffer, "", 1, '#')); cl_assert_equal_s(buffer.ptr, ""); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "Short", 0)); + cl_git_pass(git_message_prettify(&buffer, "Short", 0, '#')); cl_assert_equal_s("Short\n", buffer.ptr); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "Short", 1)); + cl_git_pass(git_message_prettify(&buffer, "Short", 1, '#')); cl_assert_equal_s("Short\n", buffer.ptr); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 0)); + cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 0, '#')); cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n# with some comments still in\n"); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 1)); + cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 1, '#')); cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n"); git_buf_free(&buffer); } diff --git a/tests/odb/foreach.c b/tests/odb/foreach.c index ab3808b00..56daf7574 100644 --- a/tests/odb/foreach.c +++ b/tests/odb/foreach.c @@ -93,8 +93,8 @@ void test_odb_foreach__files_in_objects_dir(void) cl_git_pass(git_repository_open(&repo, "testrepo.git")); cl_git_pass(git_buf_printf(&buf, "%s/objects/somefile", git_repository_path(repo))); - cl_git_mkfile(buf.ptr, ""); + git_buf_free(&buf); cl_git_pass(git_repository_odb(&odb, repo)); cl_git_pass(git_odb_foreach(odb, foreach_cb, &nobj)); diff --git a/tests/online/clone.c b/tests/online/clone.c index 6e0e63950..e269771c0 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -164,6 +164,39 @@ void test_online_clone__clone_into(void) git_buf_free(&path); } +void test_online_clone__clone_mirror(void) +{ + git_buf path = GIT_BUF_INIT; + git_remote *remote; + git_reference *head; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + bool fetch_progress_cb_was_called = false; + + cl_git_pass(git_repository_init(&g_repo, "./foo.git", true)); + cl_git_pass(git_remote_create(&remote, g_repo, "origin", LIVE_REPO_URL)); + + callbacks.transfer_progress = &fetch_progress; + callbacks.payload = &fetch_progress_cb_was_called; + git_remote_set_callbacks(remote, &callbacks); + + git_remote_clear_refspecs(remote); + cl_git_pass(git_remote_add_fetch(remote, "+refs/*:refs/*")); + + cl_git_pass(git_clone_into(g_repo, remote, NULL, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + + cl_assert_equal_i(true, fetch_progress_cb_was_called); + + git_remote_free(remote); + git_reference_free(head); + git_buf_free(&path); + cl_fixture_cleanup("./foo.git"); +} + static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload) { int *callcount = (int*)payload; diff --git a/tests/online/fetch.c b/tests/online/fetch.c index c54ec5673..f03a6faa6 100644 --- a/tests/online/fetch.c +++ b/tests/online/fetch.c @@ -184,3 +184,21 @@ void test_online_fetch__ls_disconnected(void) git_remote_free(remote); } + +void test_online_fetch__remote_symrefs(void) +{ + const git_remote_head **refs; + size_t refs_len; + git_remote *remote; + + cl_git_pass(git_remote_create(&remote, _repo, "test", + "http://github.com/libgit2/TestGitRepository.git")); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH)); + git_remote_disconnect(remote); + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + cl_assert_equal_s("HEAD", refs[0]->name); + cl_assert_equal_s("refs/heads/master", refs[0]->symref_target); + + git_remote_free(remote); +} diff --git a/tests/pack/packbuilder.c b/tests/pack/packbuilder.c index 53db81828..1059424ed 100644 --- a/tests/pack/packbuilder.c +++ b/tests/pack/packbuilder.c @@ -17,6 +17,7 @@ static git_transfer_progress _stats; void test_pack_packbuilder__initialize(void) { _repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(p_chdir("testrepo.git")); cl_git_pass(git_revwalk_new(&_revwalker, _repo)); cl_git_pass(git_packbuilder_new(&_packbuilder, _repo)); cl_git_pass(git_vector_init(&_commits, 0, NULL)); @@ -46,6 +47,7 @@ void test_pack_packbuilder__cleanup(void) git_indexer_free(_indexer); _indexer = NULL; + p_chdir(".."); cl_git_sandbox_cleanup(); _repo = NULL; } diff --git a/tests/refs/branches/create.c b/tests/refs/branches/create.c index 864640ab3..3a4f33b6e 100644 --- a/tests/refs/branches/create.c +++ b/tests/refs/branches/create.c @@ -179,11 +179,14 @@ void test_refs_branches_create__can_create_branch_with_unicode(void) expected[0] = nfd; for (i = 0; i < ARRAY_SIZE(names); ++i) { + const char *name; cl_git_pass(git_branch_create( &branch, repo, names[i], target, 0, NULL, NULL)); cl_git_pass(git_oid_cmp( git_reference_target(branch), git_commit_id(target))); + cl_git_pass(git_branch_name(&name, branch)); + cl_assert_equal_s(expected[i], name); assert_branch_matches_name(expected[i], names[i]); if (fs_decompose_unicode && alt[i]) assert_branch_matches_name(expected[i], alt[i]); diff --git a/tests/refs/branches/move.c b/tests/refs/branches/move.c index 6c6dbbe21..f136b00d6 100644 --- a/tests/refs/branches/move.c +++ b/tests/refs/branches/move.c @@ -241,3 +241,20 @@ void test_refs_branches_move__default_reflog_message(void) git_reflog_free(log); git_signature_free(sig); } + +void test_refs_branches_move__can_move_with_unicode(void) +{ + git_reference *original_ref, *new_ref; + const char *new_branch_name = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + cl_git_pass(git_branch_move(&new_ref, original_ref, new_branch_name, 0, NULL, NULL)); + + if (cl_repo_get_bool(repo, "core.precomposeunicode")) + cl_assert_equal_s(GIT_REFS_HEADS_DIR "\xC3\x85\x73\x74\x72\xC3\xB6\x6D", git_reference_name(new_ref)); + else + cl_assert_equal_s(GIT_REFS_HEADS_DIR "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D", git_reference_name(new_ref)); + + git_reference_free(original_ref); + git_reference_free(new_ref); +} diff --git a/tests/refs/iterator.c b/tests/refs/iterator.c index a29b0cf8b..c77451309 100644 --- a/tests/refs/iterator.c +++ b/tests/refs/iterator.c @@ -186,3 +186,36 @@ void test_refs_iterator__foreach_name_can_cancel(void) -333); cl_assert_equal_i(0, cancel_after); } + +void test_refs_iterator__concurrent_delete(void) +{ + git_reference_iterator *iter; + size_t full_count = 0, concurrent_count = 0; + const char *name; + int error; + + git_repository_free(repo); + repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_reference_iterator_new(&iter, repo)); + while ((error = git_reference_next_name(&name, iter)) == 0) { + full_count++; + } + + git_reference_iterator_free(iter); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_git_pass(git_reference_iterator_new(&iter, repo)); + while ((error = git_reference_next_name(&name, iter)) == 0) { + cl_git_pass(git_reference_remove(repo, name)); + concurrent_count++; + } + + git_reference_iterator_free(iter); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_assert_equal_i(full_count, concurrent_count); + + cl_git_sandbox_cleanup(); + repo = NULL; +} diff --git a/tests/repo/config.c b/tests/repo/config.c index 2e7be37aa..93dedd576 100644 --- a/tests/repo/config.c +++ b/tests/repo/config.c @@ -8,7 +8,8 @@ static git_buf path = GIT_BUF_INIT; void test_repo_config__initialize(void) { cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + cl_git_pass(cl_rename( + "empty_standard_repo/.gitted", "empty_standard_repo/.git")); git_buf_clear(&path); @@ -18,15 +19,19 @@ void test_repo_config__initialize(void) void test_repo_config__cleanup(void) { - cl_git_pass(git_path_prettify(&path, "alternate", NULL)); - cl_git_pass(git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)); + cl_sandbox_set_search_path_defaults(); + git_buf_free(&path); + + cl_git_pass( + git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES)); cl_assert(!git_path_isdir("alternate")); cl_fixture_cleanup("empty_standard_repo"); + } -void test_repo_config__open_missing_global(void) +void test_repo_config__can_open_global_when_there_is_no_file(void) { git_repository *repo; git_config *config, *global; @@ -40,23 +45,23 @@ void test_repo_config__open_missing_global(void) cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_open_level(&global, config, GIT_CONFIG_LEVEL_GLOBAL)); + cl_git_pass(git_config_open_level( + &global, config, GIT_CONFIG_LEVEL_GLOBAL)); cl_git_pass(git_config_set_string(global, "test.set", "42")); git_config_free(global); git_config_free(config); git_repository_free(repo); - - git_sysdir_global_shutdown(); } -void test_repo_config__open_missing_global_with_separators(void) +void test_repo_config__can_open_missing_global_with_separators(void) { git_repository *repo; git_config *config, *global; - cl_git_pass(git_buf_printf(&path, "%c%s", GIT_PATH_LIST_SEPARATOR, "dummy")); + cl_git_pass(git_buf_printf( + &path, "%c%s", GIT_PATH_LIST_SEPARATOR, "dummy")); cl_git_pass(git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); @@ -69,20 +74,19 @@ void test_repo_config__open_missing_global_with_separators(void) cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_open_level(&global, config, GIT_CONFIG_LEVEL_GLOBAL)); + cl_git_pass(git_config_open_level( + &global, config, GIT_CONFIG_LEVEL_GLOBAL)); cl_git_pass(git_config_set_string(global, "test.set", "42")); git_config_free(global); git_config_free(config); git_repository_free(repo); - - git_sysdir_global_shutdown(); } #include "repository.h" -void test_repo_config__read_no_configs(void) +void test_repo_config__read_with_no_configs_at_all(void) { git_repository *repo; int val; @@ -106,9 +110,9 @@ void test_repo_config__read_no_configs(void) cl_assert_equal_i(GIT_ABBREV_DEFAULT, val); git_repository_free(repo); - git_sysdir_global_shutdown(); + /* with no local config, just system */ - /* with just system */ + cl_sandbox_set_search_path_defaults(); cl_must_pass(p_mkdir("alternate/1", 0777)); cl_git_pass(git_buf_joinpath(&path, path.ptr, "1")); @@ -123,7 +127,7 @@ void test_repo_config__read_no_configs(void) cl_assert_equal_i(10, val); git_repository_free(repo); - /* with xdg + system */ + /* with just xdg + system */ cl_must_pass(p_mkdir("alternate/2", 0777)); path.ptr[path.size - 1] = '2'; @@ -204,6 +208,4 @@ void test_repo_config__read_no_configs(void) cl_assert(!git_path_exists("empty_standard_repo/.git/config")); cl_assert(!git_path_exists("alternate/3/.gitconfig")); - - git_sysdir_global_shutdown(); } diff --git a/tests/repo/open.c b/tests/repo/open.c index 190adff1c..637c785d5 100644 --- a/tests/repo/open.c +++ b/tests/repo/open.c @@ -298,7 +298,8 @@ void test_repo_open__no_config(void) git_config *config; cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + cl_git_pass(cl_rename( + "empty_standard_repo/.gitted", "empty_standard_repo/.git")); /* remove local config */ cl_git_pass(git_futils_rmdir_r( @@ -325,7 +326,7 @@ void test_repo_open__no_config(void) git_repository_free(repo); cl_fixture_cleanup("empty_standard_repo"); - git_sysdir_global_shutdown(); + cl_sandbox_set_search_path_defaults(); } void test_repo_open__force_bare(void) diff --git a/tests/repo/pathspec.c b/tests/repo/pathspec.c index 334066b67..5b86662bc 100644 --- a/tests/repo/pathspec.c +++ b/tests/repo/pathspec.c @@ -167,7 +167,7 @@ void test_repo_pathspec__workdir4(void) cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); cl_assert_equal_sz(13, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_s("θΏ™", git_pathspec_match_list_entry(m, 12)); + cl_assert_equal_s("\xE8\xBF\x99", git_pathspec_match_list_entry(m, 12)); git_pathspec_match_list_free(m); git_pathspec_free(ps); diff --git a/tests/status/ignore.c b/tests/status/ignore.c index caa1f1927..a4e766fdf 100644 --- a/tests/status/ignore.c +++ b/tests/status/ignore.c @@ -681,3 +681,110 @@ void test_status_ignore__issue_1766_negated_ignores(void) } } +static void add_one_to_index(const char *file) +{ + git_index *index; + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, file)); + git_index_free(index); +} + +/* Some further broken scenarios that have been reported */ +void test_status_ignore__more_breakage(void) +{ + static const char *test_files[] = { + "empty_standard_repo/d1/pfx-d2/d3/d4/d5/tracked", + "empty_standard_repo/d1/pfx-d2/d3/d4/d5/untracked", + "empty_standard_repo/d1/pfx-d2/d3/d4/untracked", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "/d1/pfx-*\n" + "!/d1/pfx-d2/\n" + "/d1/pfx-d2/*\n" + "!/d1/pfx-d2/d3/\n" + "/d1/pfx-d2/d3/*\n" + "!/d1/pfx-d2/d3/d4/\n"); + add_one_to_index("d1/pfx-d2/d3/d4/d5/tracked"); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *files[] = { + ".gitignore", + "d1/pfx-d2/d3/d4/d5/tracked", + "d1/pfx-d2/d3/d4/d5/untracked", + "d1/pfx-d2/d3/d4/untracked", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 4; + counts.expected_paths = files; + counts.expected_statuses = statuses; + opts.flags = GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } + + refute_is_ignored("d1/pfx-d2/d3/d4/d5/tracked"); + refute_is_ignored("d1/pfx-d2/d3/d4/d5/untracked"); + refute_is_ignored("d1/pfx-d2/d3/d4/untracked"); +} + +void test_status_ignore__negative_ignores_inside_ignores(void) +{ + static const char *test_files[] = { + "empty_standard_repo/top/mid/btm/tracked", + "empty_standard_repo/top/mid/btm/untracked", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "top\n!top/mid/btm\n"); + add_one_to_index("top/mid/btm/tracked"); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *files[] = { + ".gitignore", "top/mid/btm/tracked", "top/mid/btm/untracked", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_NEW, GIT_STATUS_WT_NEW, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 3; + counts.expected_paths = files; + counts.expected_statuses = statuses; + opts.flags = GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } + + refute_is_ignored("top/mid/btm/tracked"); + refute_is_ignored("top/mid/btm/untracked"); +} |