diff options
94 files changed, 2133 insertions, 705 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: @@ -6,6 +6,7 @@ Alexei Sholik Andreas Ericsson Anton "antong" Gyllenberg Ankur Sethi +Arthur Schreiber Ben Noordhuis Ben Straub Benjamin C Meyer diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f731d491..6f1a97edb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,9 @@ STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_V STRING(REGEX REPLACE "^.*LIBGIT2_VERSION \"[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" LIBGIT2_VERSION_REV "${GIT2_HEADER}") SET(LIBGIT2_VERSION_STRING "${LIBGIT2_VERSION_MAJOR}.${LIBGIT2_VERSION_MINOR}.${LIBGIT2_VERSION_REV}") +FILE(STRINGS "include/git2/version.h" GIT2_HEADER_SOVERSION REGEX "^#define LIBGIT2_SOVERSION [0-9]+$") +STRING(REGEX REPLACE "^.*LIBGIT2_SOVERSION ([0-9]+)$" "\\1" LIBGIT2_SOVERSION "${GIT2_HEADER_SOVERSION}") + # Find required dependencies INCLUDE_DIRECTORIES(src include) @@ -397,7 +400,7 @@ MSVC_SPLIT_SOURCES(git2) IF (SONAME) SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) - SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR}) + SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_SOVERSION}) IF (LIBGIT2_FILENAME) ADD_DEFINITIONS(-DLIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\") SET_TARGET_PROPERTIES(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME}) 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 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..05b7522ce 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -24,41 +24,91 @@ GIT_BEGIN_DECL /** + * Options for bypassing the git-aware transport on clone. Bypassing + * it means that instead of a fetch, libgit2 will copy the object + * database directory instead of figuring out what it needs, which is + * faster. If possible, it will hardlink the files to save space. + */ +typedef enum { + /** + * Auto-detect (default), libgit2 will bypass the git-aware + * transport for local paths, but use a normal fetch for + * `file://` urls. + */ + GIT_CLONE_LOCAL_AUTO, + /** + * Bypass the git-aware transport even for a `file://` url. + */ + GIT_CLONE_LOCAL, + /** + * Do no bypass the git-aware transport + */ + GIT_CLONE_NO_LOCAL, + /** + * Bypass the git-aware transport, but do not try to use + * hardlinks. + */ + GIT_CLONE_LOCAL_NO_LINKS, +} git_clone_local_t; + +/** * Clone options structure * * Use the GIT_CLONE_OPTIONS_INIT to get the default settings, like this: * * git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - * - * - `checkout_opts` are option passed to the checkout step. To disable - * checkout, set the `checkout_strategy` to GIT_CHECKOUT_NONE. - * Generally you will want the use GIT_CHECKOUT_SAFE_CREATE to create - * all files in the working directory for the newly cloned repository. - * - `bare` should be set to zero (false) to create a standard repo, - * or non-zero for a bare repo - * - `ignore_cert_errors` should be set to 1 if errors validating the - * remote host's certificate should be ignored. - * - * ** "origin" remote options: ** - * - * - `remote_name` is the name to be given to the "origin" remote. The - * default is "origin". - * - `checkout_branch` gives the name of the branch to checkout. NULL - * means use the remote's HEAD. - * - `signature` is the identity used when updating the reflog. NULL means to - * use the default signature using the config. */ typedef struct git_clone_options { unsigned int version; + /** + * These options are passed to the checkout step. To disable + * checkout, set the `checkout_strategy` to + * `GIT_CHECKOUT_NONE`. Generally you will want the use + * GIT_CHECKOUT_SAFE_CREATE to create all files in the working + * directory for the newly cloned repository. + */ git_checkout_options checkout_opts; + + /** + * Callbacks to use for reporting fetch progress. + */ git_remote_callbacks remote_callbacks; + /** + * Set to zero (false) to create a standard repo, or non-zero + * for a bare repo + */ int bare; + + /** + * Set to 1 if errors validating the remote host's certificate + * should be ignored. + */ int ignore_cert_errors; + + /** + * Whether to use a fetch or copy the object database. + */ + git_clone_local_t local; + + /** + * The name to be given to the remote that will be + * created. The default is "origin". + */ const char *remote_name; + + /** + * The name of the branch to checkout. NULL means use the + * remote's default branch. + */ const char* checkout_branch; + + /** + * The identity used when updating the reflog. NULL means to + * use the default signature using the config. + */ git_signature *signature; } git_clone_options; @@ -123,6 +173,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 b40cc6135..675c209be 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -212,9 +212,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; /** @@ -228,15 +228,15 @@ 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_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_t; /** @@ -416,12 +416,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 */ }; /** @@ -464,13 +464,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 */ }; /** @@ -482,10 +482,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..b91560631 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,18 @@ 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_EAUTH = -16, /**< Authentication error */ - 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/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/pathspec.h b/include/git2/pathspec.h index 2fb0bb716..de6f027c5 100644 --- a/include/git2/pathspec.h +++ b/include/git2/pathspec.h @@ -12,6 +12,8 @@ #include "strarray.h" #include "diff.h" +GIT_BEGIN_DECL + /** * Compiled pathspec */ @@ -257,4 +259,5 @@ GIT_EXTERN(size_t) git_pathspec_match_list_failed_entrycount( GIT_EXTERN(const char *) git_pathspec_match_list_failed_entry( const git_pathspec_match_list *m, size_t pos); +GIT_END_DECL #endif 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 07cd2e7c6..c72c9c8cc 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -572,18 +572,17 @@ GIT_EXTERN(void) git_remote_set_autotag( * * A temporary in-memory remote cannot be given a name with this method. * + * @param problems non-default refspecs cannot be renamed and will be + * stored here for further processing by the caller. Always free this + * strarray on succesful return. * @param remote the remote to rename * @param new_name the new name the remote should bear - * @param callback Optional callback to notify the consumer of fetch refspecs - * that haven't been automatically updated and need potential manual tweaking. - * @param payload Additional data to pass to the callback * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code */ GIT_EXTERN(int) git_remote_rename( + git_strarray *problems, git_remote *remote, - const char *new_name, - git_remote_rename_problem_cb callback, - void *payload); + const char *new_name); /** * Retrieve the update FETCH_HEAD setting. @@ -616,13 +615,29 @@ GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name); * 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/transport.h b/include/git2/transport.h index af7812b5d..b57d1dd7f 100644 --- a/include/git2/transport.h +++ b/include/git2/transport.h @@ -44,6 +44,14 @@ typedef enum { /* git_cred_ssh_interactive */ GIT_CREDTYPE_SSH_INTERACTIVE = (1u << 4), + + /** + * Username-only information + * + * If the SSH transport does not know which username to use, + * it will ask via this credential type. + */ + GIT_CREDTYPE_USERNAME = (1u << 5), } git_credtype_t; /* The base structure for all credential types */ @@ -105,6 +113,12 @@ typedef struct git_cred_ssh_custom { /** A key for NTLM/Kerberos "default" credentials */ typedef struct git_cred git_cred_default; +/** Username-only credential information */ +typedef struct git_cred_username { + git_cred parent; + char username[1]; +} git_cred_username; + /** * Check whether a credential object contains username information. * @@ -207,6 +221,14 @@ GIT_EXTERN(int) git_cred_ssh_custom_new( GIT_EXTERN(int) git_cred_default_new(git_cred **out); /** + * Create a credential to specify a username. + * + * This is used with ssh authentication to query for the username if + * none is specified in the url. + */ +GIT_EXTERN(int) git_cred_username_new(git_cred **cred, const char *username); + +/** * Signature of a function which acquires a credential object. * * - cred: The newly created credential object. 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 1b6f4cca1..6295ebbfa 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/include/git2/version.h b/include/git2/version.h index c4c5e8eb1..5bda42735 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,9 +7,11 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.20.0" +#define LIBGIT2_VERSION "0.21.0" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 20 +#define LIBGIT2_VER_MINOR 21 #define LIBGIT2_VER_REVISION 0 +#define LIBGIT2_SOVERSION 21 + #endif diff --git a/script/cibuild.sh b/script/cibuild.sh index 699404bd2..ef2ac6e8e 100755 --- a/script/cibuild.sh +++ b/script/cibuild.sh @@ -22,7 +22,13 @@ ctest -V . || exit $? # can do the push tests over it killall git-daemon -sudo start ssh + +if [ "$TRAVIS_OS_NAME" = "osx" ]; then + echo 'PasswordAuthentication yes' | sudo tee -a /etc/sshd_config +else + sudo start ssh +fi + ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts @@ -34,5 +40,5 @@ export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub" export GITTEST_REMOTE_SSH_PASSPHRASE="" if [ -e ./libgit2_clar ]; then - ./libgit2_clar -sonline::push -sonline::clone::cred_callback_failure + ./libgit2_clar -sonline::push -sonline::clone::cred_callback fi 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/clone.c b/src/clone.c index 24f1cae59..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) @@ -184,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; @@ -199,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) { + if (found_branch) { 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) { - 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; } @@ -301,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) @@ -340,9 +290,26 @@ static bool should_checkout( return !git_repository_head_unborn(repo); } +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; + int error; git_buf reflog_message = GIT_BUF_INIT; git_remote *remote; const git_remote_callbacks *callbacks; @@ -359,37 +326,50 @@ int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout callbacks = git_remote_get_callbacks(_remote); if (!giterr__check_version(callbacks, 1, "git_remote_callbacks") && - (error = git_remote_set_callbacks(remote, git_remote_get_callbacks(_remote))) < 0) + (error = git_remote_set_callbacks(remote, callbacks)) < 0) goto cleanup; if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0) 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); 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, @@ -424,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); } @@ -452,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 4bd27a875..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; diff --git a/src/config_cache.c b/src/config_cache.c index dca9976f8..45c39ce17 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -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,7 +74,7 @@ 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 }, }; 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_print.c b/src/diff_print.c index 26753515b..bb925ef98 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -286,50 +286,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; - git_off_t off_t_old_data_len, off_t_new_data_len; - unsigned long old_data_len, new_data_len, delta_data_len, inflated_len; - size_t 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; - off_t_old_data_len = old ? git_blob_rawsize(old) : 0; - off_t_new_data_len = new ? 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 (off_t_old_data_len > ULONG_MAX || off_t_new_data_len > ULONG_MAX) { - error = -1; - goto done; - } - - old_data_len = (unsigned long)off_t_old_data_len; - new_data_len = (unsigned long)off_t_new_data_len; + 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; /* The git_delta function accepts unsigned long only */ - if (deflate.size > ULONG_MAX) { - error = -1; + if (!git__is_ulong((git_off_t)deflate.size)) { + error = GIT_EBUFS; goto done; } if (old && new) { - void *delta_data; - - delta_data = git_delta(old_data, old_data_len, new_data, - new_data_len, &delta_data_len, (unsigned long)deflate.size); + 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; @@ -345,15 +341,17 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) 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, (char)chunk_len + 'A' - 1); else 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)) { @@ -361,8 +359,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++; } @@ -381,26 +378,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++; @@ -409,6 +413,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/global.c b/src/global.c index 7da31853e..03a4bcedf 100644 --- a/src/global.c +++ b/src/global.c @@ -16,6 +16,12 @@ git_mutex git__mwindow_mutex; #define MAX_SHUTDOWN_CB 8 +#ifdef GIT_SSL +# include <openssl/ssl.h> +SSL_CTX *git__ssl_ctx; +static git_mutex *openssl_locks; +#endif + static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB]; static git_atomic git__n_shutdown_callbacks; static git_atomic git__n_inits; @@ -39,6 +45,62 @@ static void git__shutdown(void) } +#if defined(GIT_THREADS) && defined(GIT_SSL) +void openssl_locking_function(int mode, int n, const char *file, int line) +{ + int lock; + + GIT_UNUSED(file); + GIT_UNUSED(line); + + lock = mode & CRYPTO_LOCK; + + if (lock) { + git_mutex_lock(&openssl_locks[n]); + } else { + git_mutex_unlock(&openssl_locks[n]); + } +} +#endif + + +static void init_ssl(void) +{ +#ifdef GIT_SSL + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + git__ssl_ctx = SSL_CTX_new(SSLv23_method()); + SSL_CTX_set_mode(git__ssl_ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(git__ssl_ctx, SSL_VERIFY_NONE, NULL); + if (!SSL_CTX_set_default_verify_paths(git__ssl_ctx)) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } + +# ifdef GIT_THREADS + { + int num_locks, i; + + num_locks = CRYPTO_num_locks(); + openssl_locks = git__calloc(num_locks, sizeof(git_mutex)); + if (openssl_locks == NULL) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } + + for (i = 0; i < num_locks; i++) { + if (git_mutex_init(&openssl_locks[i]) != 0) { + SSL_CTX_free(git__ssl_ctx); + git__ssl_ctx = NULL; + } + } + + CRYPTO_set_locking_callback(openssl_locking_function); + } +# endif +#endif +} + /** * Handle the global state with TLS * @@ -78,7 +140,7 @@ static void git__shutdown(void) static DWORD _tls_index; static volatile LONG _mutex = 0; -static int synchronized_threads_init() +static int synchronized_threads_init(void) { int error; @@ -112,7 +174,7 @@ int git_threads_init(void) return error; } -static void synchronized_threads_shutdown() +static void synchronized_threads_shutdown(void) { /* Shut down any subsystems that have global state */ git__shutdown(); @@ -168,10 +230,14 @@ static void init_once(void) return; pthread_key_create(&_tls_key, &cb__free_status); + /* Initialize any other subsystems that have global state */ if ((init_error = git_hash_global_init()) >= 0) init_error = git_sysdir_global_init(); + /* OpenSSL needs to be initialized from the main thread */ + init_ssl(); + GIT_MEMORY_BARRIER; } @@ -225,6 +291,7 @@ static git_global_st __state; int git_threads_init(void) { + init_ssl(); git_atomic_inc(&git__n_inits); return 0; } diff --git a/src/global.h b/src/global.h index 778250376..745df3e4a 100644 --- a/src/global.h +++ b/src/global.h @@ -15,6 +15,11 @@ typedef struct { git_error error_t; } git_global_st; +#ifdef GIT_SSL +# include <openssl/ssl.h> +extern SSL_CTX *git__ssl_ctx; +#endif + git_global_st *git__global_state(void); extern git_mutex git__mwindow_mutex; 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/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/netops.c b/src/netops.c index a0193a022..965e4775d 100644 --- a/src/netops.c +++ b/src/netops.c @@ -33,6 +33,7 @@ #include "posix.h" #include "buffer.h" #include "http_parser.h" +#include "global.h" #ifdef GIT_WIN32 static void net_set_error(const char *str) @@ -157,7 +158,7 @@ void gitno_buffer_setup_callback( void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len) { #ifdef GIT_SSL - if (socket->ssl.ctx) { + if (socket->ssl.ssl) { gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL); return; } @@ -202,7 +203,6 @@ static int gitno_ssl_teardown(gitno_ssl *ssl) ret = 0; SSL_free(ssl->ssl); - SSL_CTX_free(ssl->ctx); return ret; } @@ -390,18 +390,12 @@ static int ssl_setup(gitno_socket *socket, const char *host, int flags) { int ret; - SSL_library_init(); - SSL_load_error_strings(); - socket->ssl.ctx = SSL_CTX_new(SSLv23_method()); - if (socket->ssl.ctx == NULL) - return ssl_set_error(&socket->ssl, 0); - - SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL); - if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx)) - return ssl_set_error(&socket->ssl, 0); + if (git__ssl_ctx == NULL) { + giterr_set(GITERR_NET, "OpenSSL initialization failed"); + return -1; + } - socket->ssl.ssl = SSL_new(socket->ssl.ctx); + socket->ssl.ssl = SSL_new(git__ssl_ctx); if (socket->ssl.ssl == NULL) return ssl_set_error(&socket->ssl, 0); @@ -538,7 +532,7 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags) size_t off = 0; #ifdef GIT_SSL - if (socket->ssl.ctx) + if (socket->ssl.ssl) return gitno_send_ssl(&socket->ssl, msg, len, flags); #endif @@ -559,7 +553,7 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags) int gitno_close(gitno_socket *s) { #ifdef GIT_SSL - if (s->ssl.ctx && + if (s->ssl.ssl && gitno_ssl_teardown(&s->ssl) < 0) return -1; #endif diff --git a/src/netops.h b/src/netops.h index 8e3a2524f..dfb4ab7b4 100644 --- a/src/netops.h +++ b/src/netops.h @@ -16,7 +16,6 @@ struct gitno_ssl { #ifdef GIT_SSL - SSL_CTX *ctx; SSL *ssl; #else size_t dummy; @@ -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-objects.c b/src/pack-objects.c index 3d3330ae8..0040a826b 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -1209,7 +1209,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, git_mutex_unlock(&target->mutex); if (!sub_size) { - git_thread_join(target->thread, NULL); + git_thread_join(&target->thread, NULL); git_cond_free(&target->cond); git_mutex_free(&target->mutex); active_threads--; diff --git a/src/path.c b/src/path.c index e0b00a086..5beab97ed 100644 --- a/src/path.c +++ b/src/path.c @@ -1127,3 +1127,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/refdb_fs.c b/src/refdb_fs.c index dd8bf7916..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; } 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 bdc4791a9..47b61b1b1 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; } @@ -1358,19 +1359,24 @@ static int update_branch_remote_config_entry( } static int rename_one_remote_reference( - git_reference *reference, + git_reference *reference_in, const char *old_remote_name, const char *new_remote_name) { int error; + git_reference *ref = NULL, *dummy = NULL; + git_buf namespace = GIT_BUF_INIT, old_namespace = GIT_BUF_INIT; git_buf new_name = GIT_BUF_INIT; git_buf log_message = GIT_BUF_INIT; + size_t pfx_len; + const char *target; - if ((error = git_buf_printf( - &new_name, - GIT_REFS_REMOTES_DIR "%s%s", - new_remote_name, - reference->name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name))) < 0) + if ((error = git_buf_printf(&namespace, GIT_REFS_REMOTES_DIR "%s/", new_remote_name)) < 0) + return error; + + pfx_len = strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name) + 1; + git_buf_puts(&new_name, namespace.ptr); + if ((error = git_buf_puts(&new_name, git_reference_name(reference_in) + pfx_len)) < 0) goto cleanup; if ((error = git_buf_printf(&log_message, @@ -1378,12 +1384,36 @@ static int rename_one_remote_reference( old_remote_name, new_remote_name)) < 0) goto cleanup; - error = git_reference_rename( - NULL, reference, git_buf_cstr(&new_name), 0, - NULL, git_buf_cstr(&log_message)); - git_reference_free(reference); + if ((error = git_reference_rename(&ref, reference_in, git_buf_cstr(&new_name), 1, + NULL, git_buf_cstr(&log_message))) < 0) + goto cleanup; + + if (git_reference_type(ref) != GIT_REF_SYMBOLIC) + goto cleanup; + + /* Handle refs like origin/HEAD -> origin/master */ + target = git_reference_symbolic_target(ref); + if ((error = git_buf_printf(&old_namespace, GIT_REFS_REMOTES_DIR "%s/", old_remote_name)) < 0) + goto cleanup; + + if (git__prefixcmp(target, old_namespace.ptr)) + goto cleanup; + + git_buf_clear(&new_name); + git_buf_puts(&new_name, namespace.ptr); + if ((error = git_buf_puts(&new_name, target + pfx_len)) < 0) + goto cleanup; + + error = git_reference_symbolic_set_target(&dummy, ref, git_buf_cstr(&new_name), + NULL, git_buf_cstr(&log_message)); + + git_reference_free(dummy); cleanup: + git_reference_free(reference_in); + git_reference_free(ref); + git_buf_free(&namespace); + git_buf_free(&old_namespace); git_buf_free(&new_name); git_buf_free(&log_message); return error; @@ -1395,18 +1425,20 @@ static int rename_remote_references( const char *new_name) { int error; + git_buf buf = GIT_BUF_INIT; git_reference *ref; git_reference_iterator *iter; - if ((error = git_reference_iterator_new(&iter, repo)) < 0) + if ((error = git_buf_printf(&buf, GIT_REFS_REMOTES_DIR "%s/*", old_name)) < 0) return error; - while ((error = git_reference_next(&ref, iter)) == 0) { - if (git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) { - git_reference_free(ref); - continue; - } + error = git_reference_iterator_glob_new(&iter, repo, git_buf_cstr(&buf)); + git_buf_free(&buf); + + if (error < 0) + return error; + while ((error = git_reference_next(&ref, iter)) == 0) { if ((error = rename_one_remote_reference(ref, old_name, new_name)) < 0) break; } @@ -1416,11 +1448,7 @@ static int rename_remote_references( return (error == GIT_ITEROVER) ? 0 : error; } -static int rename_fetch_refspecs( - git_remote *remote, - const char *new_name, - int (*callback)(const char *problematic_refspec, void *payload), - void *payload) +static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const char *new_name) { git_config *config; git_buf base = GIT_BUF_INIT, var = GIT_BUF_INIT, val = GIT_BUF_INIT; @@ -1431,6 +1459,9 @@ static int rename_fetch_refspecs( if ((error = git_repository_config__weakptr(&config, remote->repo)) < 0) return error; + if ((error = git_vector_init(problems, 1, NULL)) < 0) + return error; + if ((error = git_buf_printf( &base, "+refs/heads/*:refs/remotes/%s/*", remote->name)) < 0) return error; @@ -1439,15 +1470,15 @@ static int rename_fetch_refspecs( if (spec->push) continue; - /* Every refspec is a problem refspec for an anonymous remote, OR */ /* Does the dst part of the refspec follow the expected format? */ - if (!remote->name || - strcmp(git_buf_cstr(&base), spec->string)) { + if (strcmp(git_buf_cstr(&base), spec->string)) { + char *dup; + + dup = git__strdup(spec->string); + GITERR_CHECK_ALLOC(dup); - if ((error = callback(spec->string, payload)) != 0) { - giterr_set_after_callback(error); + if ((error = git_vector_insert(problems, dup)) < 0) break; - } continue; } @@ -1473,18 +1504,25 @@ static int rename_fetch_refspecs( git_buf_free(&base); git_buf_free(&var); git_buf_free(&val); + + if (error < 0) { + char *str; + git_vector_foreach(problems, i, str) + git__free(str); + + git_vector_free(problems); + } + return error; } -int git_remote_rename( - git_remote *remote, - const char *new_name, - git_remote_rename_problem_cb callback, - void *payload) +int git_remote_rename(git_strarray *out, git_remote *remote, const char *new_name) { int error; + git_vector problem_refspecs; + char *tmp, *dup; - assert(remote && new_name); + assert(out && remote && new_name); if (!remote->name) { giterr_set(GITERR_INVALID, "Can't rename an anonymous remote."); @@ -1494,54 +1532,30 @@ int git_remote_rename( if ((error = ensure_remote_name_is_valid(new_name)) < 0) return error; - if (remote->repo) { - if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0) - return error; + if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0) + return error; + + if ((error = rename_remote_config_section(remote->repo, remote->name, new_name)) < 0) + return error; - if (!remote->name) { - if ((error = rename_fetch_refspecs( - remote, - new_name, - callback, - payload)) < 0) - return error; + if ((error = update_branch_remote_config_entry(remote->repo, remote->name, new_name)) < 0) + return error; - remote->name = git__strdup(new_name); - GITERR_CHECK_ALLOC(remote->name); + if ((error = rename_remote_references(remote->repo, remote->name, new_name)) < 0) + return error; - return git_remote_save(remote); - } + if ((error = rename_fetch_refspecs(&problem_refspecs, remote, new_name)) < 0) + return error; - if ((error = rename_remote_config_section( - remote->repo, - remote->name, - new_name)) < 0) - return error; - - if ((error = update_branch_remote_config_entry( - remote->repo, - remote->name, - new_name)) < 0) - return error; - - if ((error = rename_remote_references( - remote->repo, - remote->name, - new_name)) < 0) - return error; - - if ((error = rename_fetch_refspecs( - remote, - new_name, - callback, - payload)) < 0) - return error; - } + out->count = problem_refspecs.length; + out->strings = (char **) problem_refspecs.contents; - git__free(remote->name); + dup = git__strdup(new_name); + GITERR_CHECK_ALLOC(dup); - remote->name = git__strdup(new_name); - GITERR_CHECK_ALLOC(remote->name); + tmp = remote->name; + remote->name = dup; + git__free(tmp); return 0; } @@ -1809,24 +1823,50 @@ static int remove_branch_config_related_entries( return error; } -static int remove_refs(git_repository *repo, const char *glob) +static int remove_refs(git_repository *repo, const git_refspec *spec) { - git_reference_iterator *iter; + git_reference_iterator *iter = NULL; + git_vector refs; const char *name; + char *dup; int error; + size_t i; - if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0) + 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 ((error = git_reference_remove(repo, name)) < 0) - break; - } - git_reference_iterator_free(iter); + 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; } @@ -1848,7 +1888,7 @@ static int remove_remote_tracking(git_repository *repo, const char *remote_name) if (refspec == NULL) continue; - if ((error = remove_refs(repo, git_refspec_dst(refspec))) < 0) + if ((error = remove_refs(repo, refspec)) < 0) break; } @@ -1881,7 +1921,52 @@ int git_remote_delete(git_remote *remote) 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 b0db5484a..e8d50aed3 100644 --- a/src/repository.c +++ b/src/repository.c @@ -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/revwalk.c b/src/revwalk.c index 7aedd1f44..530c9705e 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -48,7 +48,7 @@ static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit) assert(commit); - git_array_alloc(pending); + git_array_init_to_size(pending, 2); GITERR_CHECK_ARRAY(pending); do { @@ -67,7 +67,7 @@ static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit) tmp = git_array_pop(pending); commit = tmp ? *tmp : NULL; - } while (git_array_size(pending) > 0); + } while (commit != NULL); git_array_clear(pending); diff --git a/src/thread-utils.h b/src/thread-utils.h index 50d8610a3..daec14eeb 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -40,12 +40,18 @@ typedef git_atomic git_atomic_ssize; #ifdef GIT_THREADS -#define git_thread pthread_t -#define git_thread_create(thread, attr, start_routine, arg) \ - pthread_create(thread, attr, start_routine, arg) -#define git_thread_kill(thread) pthread_cancel(thread) -#define git_thread_exit(status) pthread_exit(status) -#define git_thread_join(id, status) pthread_join(id, status) +#if !defined(GIT_WIN32) + +typedef struct { + pthread_t thread; +} git_thread; + +#define git_thread_create(git_thread_ptr, attr, start_routine, arg) \ + pthread_create(&(git_thread_ptr)->thread, attr, start_routine, arg) +#define git_thread_join(git_thread_ptr, status) \ + pthread_join((git_thread_ptr)->thread, status) + +#endif #if defined(GIT_WIN32) #define git_thread_yield() Sleep(0) @@ -179,8 +185,6 @@ GIT_INLINE(int64_t) git_atomic64_add(git_atomic64 *a, int64_t addend) #define git_thread unsigned int #define git_thread_create(thread, attr, start_routine, arg) 0 -#define git_thread_kill(thread) (void)0 -#define git_thread_exit(status) (void)0 #define git_thread_join(id, status) (void)0 #define git_thread_yield() (void)0 diff --git a/src/transports/cred.c b/src/transports/cred.c index 913ec36cc..1b4d29c0a 100644 --- a/src/transports/cred.c +++ b/src/transports/cred.c @@ -17,6 +17,40 @@ int git_cred_has_username(git_cred *cred) return 1; } +const char *git_cred__username(git_cred *cred) +{ + switch (cred->credtype) { + case GIT_CREDTYPE_USERNAME: + { + git_cred_username *c = (git_cred_username *) cred; + return c->username; + } + case GIT_CREDTYPE_USERPASS_PLAINTEXT: + { + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *) cred; + return c->username; + } + case GIT_CREDTYPE_SSH_KEY: + { + git_cred_ssh_key *c = (git_cred_ssh_key *) cred; + return c->username; + } + case GIT_CREDTYPE_SSH_CUSTOM: + { + git_cred_ssh_custom *c = (git_cred_ssh_custom *) cred; + return c->username; + } + case GIT_CREDTYPE_SSH_INTERACTIVE: + { + git_cred_ssh_interactive *c = (git_cred_ssh_interactive *) cred; + return c->username; + } + + default: + return NULL; + } +} + static void plaintext_free(struct git_cred *cred) { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; @@ -129,6 +163,11 @@ static void default_free(struct git_cred *cred) git__free(c); } +static void username_free(struct git_cred *cred) +{ + git__free(cred); +} + int git_cred_ssh_key_new( git_cred **cred, const char *username, @@ -263,3 +302,22 @@ int git_cred_default_new(git_cred **cred) *cred = c; return 0; } + +int git_cred_username_new(git_cred **cred, const char *username) +{ + git_cred_username *c; + size_t len; + + assert(cred); + + len = strlen(username); + c = git__malloc(sizeof(git_cred_username) + len + 1); + GITERR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDTYPE_USERNAME; + c->parent.free = username_free; + memcpy(c->username, username, len + 1); + + *cred = (git_cred *) c; + return 0; +} diff --git a/src/transports/cred.h b/src/transports/cred.h new file mode 100644 index 000000000..2de8deee8 --- /dev/null +++ b/src/transports/cred.h @@ -0,0 +1,14 @@ +/* + * 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_git_cred_h__ +#define INCLUDE_git_cred_h__ + +#include "git2/transport.h" + +const char *git_cred__username(git_cred *cred); + +#endif diff --git a/src/transports/cred_helpers.c b/src/transports/cred_helpers.c index d420e3e3c..5cc9b0869 100644 --- a/src/transports/cred_helpers.c +++ b/src/transports/cred_helpers.c @@ -41,6 +41,9 @@ int git_cred_userpass( else return -1; + if (GIT_CREDTYPE_USERNAME & allowed_types) + return git_cred_username_new(cred, effective_username); + if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 || git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0) return -1; diff --git a/src/transports/http.c b/src/transports/http.c index a7eff7365..ae608ab3d 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -260,7 +260,7 @@ static int on_headers_complete(http_parser *parser) if (parser->status_code == 401 && get_verb == s->verb) { - if (!t->owner->cred_acquire_payload) { + if (!t->owner->cred_acquire_cb) { no_callback = 1; } else { int allowed_types = 0; 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/transports/ssh.c b/src/transports/ssh.c index b403727c9..6a7f67e99 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -9,6 +9,7 @@ #include "buffer.h" #include "netops.h" #include "smart.h" +#include "cred.h" #ifdef GIT_SSH @@ -34,9 +35,10 @@ typedef struct { git_smart_subtransport parent; transport_smart *owner; ssh_stream *current_stream; - git_cred *cred; } ssh_subtransport; +static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username); + static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg) { char *ssherr; @@ -339,6 +341,9 @@ static int _git_ssh_authenticate_session( } } while (LIBSSH2_ERROR_EAGAIN == rc || LIBSSH2_ERROR_TIMEOUT == rc); + if (rc == LIBSSH2_ERROR_PASSWORD_EXPIRED || rc == LIBSSH2_ERROR_AUTHENTICATION_FAILED) + return GIT_EAUTH; + if (rc != LIBSSH2_ERROR_NONE) { ssh_error(session, "Failed to authenticate SSH session"); return -1; @@ -347,6 +352,43 @@ static int _git_ssh_authenticate_session( return 0; } +static int request_creds(git_cred **out, ssh_subtransport *t, const char *user, int auth_methods) +{ + int error, no_callback = 0; + git_cred *cred = NULL; + + if (!t->owner->cred_acquire_cb) { + no_callback = 1; + } else { + error = t->owner->cred_acquire_cb(&cred, t->owner->url, user, auth_methods, + t->owner->cred_acquire_payload); + + if (error == GIT_PASSTHROUGH) + no_callback = 1; + else if (error < 0) + return error; + else if (!cred) { + giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials"); + return -1; + } + } + + if (no_callback) { + giterr_set(GITERR_SSH, "authentication required but no callback set"); + return -1; + } + + if (!(cred->credtype & auth_methods)) { + cred->free(cred); + giterr_set(GITERR_SSH, "callback returned unsupported credentials type"); + return -1; + } + + *out = cred; + + return 0; +} + static int _git_ssh_session_create( LIBSSH2_SESSION** session, gitno_socket socket) @@ -387,8 +429,9 @@ static int _git_ssh_setup_conn( { char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; const char *default_port="22"; - int no_callback = 0; + int auth_methods, error = 0; ssh_stream *s; + git_cred *cred = NULL; LIBSSH2_SESSION* session=NULL; LIBSSH2_CHANNEL* channel=NULL; @@ -399,56 +442,68 @@ static int _git_ssh_setup_conn( s = (ssh_stream *)*stream; if (!git__prefixcmp(url, prefix_ssh)) { - if (gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port) < 0) + if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0) goto on_error; } else { - if (git_ssh_extract_url_parts(&host, &user, url) < 0) + if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0) goto on_error; port = git__strdup(default_port); GITERR_CHECK_ALLOC(port); } - if (gitno_connect(&s->socket, host, port, 0) < 0) - goto on_error; - - if (user && pass) { - if (git_cred_userpass_plaintext_new(&t->cred, user, pass) < 0) + /* we need the username to ask for auth methods */ + if (!user) { + if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0) goto on_error; - } else if (!t->owner->cred_acquire_cb) { - no_callback = 1; - } else { - int error; - error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, user, - GIT_CREDTYPE_USERPASS_PLAINTEXT | - GIT_CREDTYPE_SSH_KEY | GIT_CREDTYPE_SSH_CUSTOM | - GIT_CREDTYPE_SSH_INTERACTIVE, - t->owner->cred_acquire_payload); - if (error == GIT_PASSTHROUGH) - no_callback = 1; - else if (error < 0) + user = git__strdup(((git_cred_username *) cred)->username); + cred->free(cred); + cred = NULL; + if (!user) goto on_error; - else if (!t->cred) { - giterr_set(GITERR_SSH, "Callback failed to initialize SSH credentials"); + } else if (user && pass) { + if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0) goto on_error; - } } - if (no_callback) { - giterr_set(GITERR_SSH, "authentication required but no callback set"); + if ((error = gitno_connect(&s->socket, host, port, 0)) < 0) goto on_error; - } - assert(t->cred); + if ((error = _git_ssh_session_create(&session, s->socket)) < 0) + goto on_error; - if (_git_ssh_session_create(&session, s->socket) < 0) + if ((error = list_auth_methods(&auth_methods, session, user)) < 0) goto on_error; - if (_git_ssh_authenticate_session(session, t->cred) < 0) + error = GIT_EAUTH; + /* if we already have something to try */ + if (cred && auth_methods & cred->credtype) + error = _git_ssh_authenticate_session(session, cred); + + while (error == GIT_EAUTH) { + if (cred) { + cred->free(cred); + cred = NULL; + } + + if ((error = request_creds(&cred, t, user, auth_methods)) < 0) + goto on_error; + + if (strcmp(user, git_cred__username(cred))) { + giterr_set(GITERR_SSH, "username does not match previous request"); + error = -1; + goto on_error; + } + + error = _git_ssh_authenticate_session(session, cred); + } + + if (error < 0) goto on_error; channel = libssh2_channel_open_session(session); if (!channel) { + error = -1; ssh_error(session, "Failed to open SSH channel"); goto on_error; } @@ -459,6 +514,9 @@ static int _git_ssh_setup_conn( s->channel = channel; t->current_stream = s; + if (cred) + cred->free(cred); + git__free(host); git__free(port); git__free(path); @@ -475,6 +533,9 @@ on_error: if (*stream) ssh_stream_free(*stream); + if (cred) + cred->free(cred); + git__free(host); git__free(port); git__free(user); @@ -483,7 +544,7 @@ on_error: if (session) libssh2_session_free(session); - return -1; + return error; } static int ssh_uploadpack_ls( @@ -491,10 +552,7 @@ static int ssh_uploadpack_ls( const char *url, git_smart_subtransport_stream **stream) { - if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0) - return -1; - - return 0; + return _git_ssh_setup_conn(t, url, cmd_uploadpack, stream); } static int ssh_uploadpack( @@ -585,6 +643,53 @@ static void _ssh_free(git_smart_subtransport *subtransport) git__free(t); } + +#define SSH_AUTH_PUBLICKEY "publickey" +#define SSH_AUTH_PASSWORD "password" +#define SSH_AUTH_KEYBOARD_INTERACTIVE "keyboard-interactive" + +static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *username) +{ + const char *list, *ptr; + + *out = 0; + + list = libssh2_userauth_list(session, username, strlen(username)); + + /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ + if (list == NULL && !libssh2_userauth_authenticated(session)) + return -1; + + ptr = list; + while (ptr) { + if (*ptr == ',') + ptr++; + + if (!git__prefixcmp(ptr, SSH_AUTH_PUBLICKEY)) { + *out |= GIT_CREDTYPE_SSH_KEY; + *out |= GIT_CREDTYPE_SSH_CUSTOM; + ptr += strlen(SSH_AUTH_PUBLICKEY); + continue; + } + + if (!git__prefixcmp(ptr, SSH_AUTH_PASSWORD)) { + *out |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + ptr += strlen(SSH_AUTH_PASSWORD); + continue; + } + + if (!git__prefixcmp(ptr, SSH_AUTH_KEYBOARD_INTERACTIVE)) { + *out |= GIT_CREDTYPE_SSH_INTERACTIVE; + ptr += strlen(SSH_AUTH_KEYBOARD_INTERACTIVE); + continue; + } + + /* Skipt it if we don't know it */ + ptr = strchr(ptr, ','); + } + + return 0; +} #endif int git_smart_subtransport_ssh( diff --git a/src/tree.c b/src/tree.c index 94f779eca..b64efe460 100644 --- a/src/tree.c +++ b/src/tree.c @@ -460,7 +460,7 @@ static int append_entry( git_oid_cpy(&entry->oid, id); entry->attr = (uint16_t)filemode; - if (git_vector_insert(&bld->entries, entry) < 0) { + if (git_vector_insert_sorted(&bld->entries, entry, NULL) < 0) { git__free(entry); return -1; } @@ -671,7 +671,7 @@ int git_treebuilder_insert( entry = alloc_entry(filename); GITERR_CHECK_ALLOC(entry); - if (git_vector_insert(&bld->entries, entry) < 0) { + if (git_vector_insert_sorted(&bld->entries, entry, NULL) < 0) { git__free(entry); return -1; } 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/pthread.c b/src/win32/pthread.c index db8927471..ec45ecbe5 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -8,32 +8,64 @@ #include "pthread.h" #include "../global.h" -int pthread_create( - pthread_t *GIT_RESTRICT thread, +#define CLEAN_THREAD_EXIT 0x6F012842 + +/* The thread procedure stub used to invoke the caller's procedure + * and capture the return value for later collection. Windows will + * only hold a DWORD, but we need to be able to store an entire + * void pointer. This requires the indirection. */ +static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) +{ + git_win32_thread *thread = lpParameter; + + thread->result = thread->proc(thread->param); + + return CLEAN_THREAD_EXIT; +} + +int git_win32__thread_create( + git_win32_thread *GIT_RESTRICT thread, const pthread_attr_t *GIT_RESTRICT attr, void *(*start_routine)(void*), void *GIT_RESTRICT arg) { GIT_UNUSED(attr); - *thread = CreateThread( - NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL); - return *thread ? 0 : -1; + + thread->result = NULL; + thread->param = arg; + thread->proc = start_routine; + thread->thread = CreateThread( + NULL, 0, git_win32__threadproc, thread, 0, NULL); + + return thread->thread ? 0 : -1; } -int pthread_join(pthread_t thread, void **value_ptr) +int git_win32__thread_join( + git_win32_thread *thread, + void **value_ptr) { - DWORD ret = WaitForSingleObject(thread, INFINITE); + DWORD exit; + + if (WaitForSingleObject(thread->thread, INFINITE) != WAIT_OBJECT_0) + return -1; + + if (!GetExitCodeThread(thread->thread, &exit)) { + CloseHandle(thread->thread); + return -1; + } - if (ret == WAIT_OBJECT_0) { - if (value_ptr != NULL) { - *value_ptr = NULL; - GetExitCodeThread(thread, (void *)value_ptr); - } - CloseHandle(thread); - return 0; + /* Check for the thread having exited uncleanly. If exit was unclean, + * then we don't have a return value to give back to the caller. */ + if (exit != CLEAN_THREAD_EXIT) { + assert(false); + thread->result = NULL; } - return -1; + if (value_ptr) + *value_ptr = thread->result; + + CloseHandle(thread->thread); + return 0; } int pthread_mutex_init( @@ -144,9 +176,6 @@ int pthread_num_processors_np(void) return n ? n : 1; } - -static HINSTANCE win32_kernel32_dll; - typedef void (WINAPI *win32_srwlock_fn)(GIT_SRWLOCK *); static win32_srwlock_fn win32_srwlock_initialize; @@ -159,7 +188,7 @@ int pthread_rwlock_init( pthread_rwlock_t *GIT_RESTRICT lock, const pthread_rwlockattr_t *GIT_RESTRICT attr) { - (void)attr; + GIT_UNUSED(attr); if (win32_srwlock_initialize) win32_srwlock_initialize(&lock->native.srwl); @@ -217,38 +246,22 @@ int pthread_rwlock_destroy(pthread_rwlock_t *lock) return 0; } - -static void win32_pthread_shutdown(void) -{ - if (win32_kernel32_dll) { - FreeLibrary(win32_kernel32_dll); - win32_kernel32_dll = NULL; - } -} - int win32_pthread_initialize(void) { - if (win32_kernel32_dll) - return 0; - - win32_kernel32_dll = LoadLibrary("Kernel32.dll"); - if (!win32_kernel32_dll) { - giterr_set(GITERR_OS, "Could not load Kernel32.dll!"); - return -1; + HMODULE hModule = GetModuleHandleW(L"kernel32"); + + if (hModule) { + win32_srwlock_initialize = (win32_srwlock_fn) + GetProcAddress(hModule, "InitializeSRWLock"); + win32_srwlock_acquire_shared = (win32_srwlock_fn) + GetProcAddress(hModule, "AcquireSRWLockShared"); + win32_srwlock_release_shared = (win32_srwlock_fn) + GetProcAddress(hModule, "ReleaseSRWLockShared"); + win32_srwlock_acquire_exclusive = (win32_srwlock_fn) + GetProcAddress(hModule, "AcquireSRWLockExclusive"); + win32_srwlock_release_exclusive = (win32_srwlock_fn) + GetProcAddress(hModule, "ReleaseSRWLockExclusive"); } - win32_srwlock_initialize = (win32_srwlock_fn) - GetProcAddress(win32_kernel32_dll, "InitializeSRWLock"); - win32_srwlock_acquire_shared = (win32_srwlock_fn) - GetProcAddress(win32_kernel32_dll, "AcquireSRWLockShared"); - win32_srwlock_release_shared = (win32_srwlock_fn) - GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockShared"); - win32_srwlock_acquire_exclusive = (win32_srwlock_fn) - GetProcAddress(win32_kernel32_dll, "AcquireSRWLockExclusive"); - win32_srwlock_release_exclusive = (win32_srwlock_fn) - GetProcAddress(win32_kernel32_dll, "ReleaseSRWLockExclusive"); - - git__on_shutdown(win32_pthread_shutdown); - return 0; } diff --git a/src/win32/pthread.h b/src/win32/pthread.h index af5b121f0..e4826ca7f 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -16,13 +16,19 @@ # define GIT_RESTRICT __restrict__ #endif +typedef struct { + HANDLE thread; + void *(*proc)(void *); + void *param; + void *result; +} git_win32_thread; + typedef int pthread_mutexattr_t; typedef int pthread_condattr_t; typedef int pthread_attr_t; typedef int pthread_rwlockattr_t; typedef CRITICAL_SECTION pthread_mutex_t; -typedef HANDLE pthread_t; typedef HANDLE pthread_cond_t; typedef struct { void *Ptr; } GIT_SRWLOCK; @@ -36,13 +42,26 @@ typedef struct { #define PTHREAD_MUTEX_INITIALIZER {(void*)-1} -int pthread_create( - pthread_t *GIT_RESTRICT thread, - const pthread_attr_t *GIT_RESTRICT attr, - void *(*start_routine)(void*), - void *GIT_RESTRICT arg); +int git_win32__thread_create( + git_win32_thread *GIT_RESTRICT, + const pthread_attr_t *GIT_RESTRICT, + void *(*) (void *), + void *GIT_RESTRICT); + +int git_win32__thread_join( + git_win32_thread *, + void **); + +#ifdef GIT_THREADS -int pthread_join(pthread_t, void **); +typedef git_win32_thread git_thread; + +#define git_thread_create(git_thread_ptr, attr, start_routine, arg) \ + git_win32__thread_create(git_thread_ptr, attr, start_routine, arg) +#define git_thread_join(git_thread_ptr, status) \ + git_win32__thread_join(git_thread_ptr, status) + +#endif int pthread_mutex_init( pthread_mutex_t *GIT_RESTRICT mutex, 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/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/diff/workdir.c b/tests/diff/workdir.c index f82bb00e8..963be9481 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -1610,8 +1610,8 @@ void test_diff_workdir__binary_detection(void) int i; git_buf data[10] = { { "1234567890", 0, 0 }, /* 0 - all ascii text control */ - { "Åü†HøπΩ", 0, 0 }, /* 1 - UTF-8 multibyte text */ - { "\xEF\xBB\xBFÜ⤒ƒ8£€", 0, 0 }, /* 2 - UTF-8 with BOM */ + { "\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 */ 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/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/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 index db55b0768..664f47a43 100644 --- a/tests/network/remote/delete.c +++ b/tests/network/remote/delete.c @@ -15,6 +15,7 @@ void test_network_remote_delete__initialize(void) void test_network_remote_delete__cleanup(void) { + git_remote_free(_remote); cl_git_sandbox_cleanup(); } @@ -27,7 +28,6 @@ void test_network_remote_delete__cannot_delete_an_anonymous_remote(void) 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) 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/network/remote/rename.c b/tests/network/remote/rename.c index 4d8628425..1b819a445 100644 --- a/tests/network/remote/rename.c +++ b/tests/network/remote/rename.c @@ -33,10 +33,14 @@ static int dont_call_me_cb(const char *fetch_refspec, void *payload) void test_network_remote_rename__renaming_a_remote_moves_related_configuration_section(void) { + git_strarray problems = {0}; + assert_config_entry_existence(_repo, "remote.test.fetch", true); assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); - cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); assert_config_entry_existence(_repo, "remote.test.fetch", false); assert_config_entry_existence(_repo, "remote.just/renamed.fetch", true); @@ -44,16 +48,24 @@ void test_network_remote_rename__renaming_a_remote_moves_related_configuration_s void test_network_remote_rename__renaming_a_remote_updates_branch_related_configuration_entries(void) { + git_strarray problems = {0}; + assert_config_entry_value(_repo, "branch.master.remote", "test"); - cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); assert_config_entry_value(_repo, "branch.master.remote", "just/renamed"); } void test_network_remote_rename__renaming_a_remote_updates_default_fetchrefspec(void) { - cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + git_strarray problems = {0}; + + cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/heads/*:refs/remotes/just/renamed/*"); } @@ -61,6 +73,7 @@ void test_network_remote_rename__renaming_a_remote_updates_default_fetchrefspec( void test_network_remote_rename__renaming_a_remote_without_a_fetchrefspec_doesnt_create_one(void) { git_config *config; + git_strarray problems = {0}; git_remote_free(_remote); cl_git_pass(git_repository_config__weakptr(&config, _repo)); @@ -70,70 +83,64 @@ void test_network_remote_rename__renaming_a_remote_without_a_fetchrefspec_doesnt assert_config_entry_existence(_repo, "remote.test.fetch", false); - cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); } -static int ensure_refspecs(const char* refspec_name, void *payload) -{ - int i = 0; - bool found = false; - const char ** exp = (const char **)payload; - - while (exp[i]) { - if (strcmp(exp[i++], refspec_name)) - continue; - - found = true; - break; - } - - cl_assert(found); - - return 0; -} - void test_network_remote_rename__renaming_a_remote_notifies_of_non_default_fetchrefspec(void) { git_config *config; - char *expected_refspecs[] = { - "+refs/*:refs/*", - NULL - }; + git_strarray problems = {0}; git_remote_free(_remote); cl_git_pass(git_repository_config__weakptr(&config, _repo)); cl_git_pass(git_config_set_string(config, "remote.test.fetch", "+refs/*:refs/*")); cl_git_pass(git_remote_load(&_remote, _repo, "test")); - cl_git_pass(git_remote_rename(_remote, "just/renamed", ensure_refspecs, &expected_refspecs)); + cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed")); + cl_assert_equal_i(1, problems.count); + cl_assert_equal_s("+refs/*:refs/*", problems.strings[0]); + git_strarray_free(&problems); assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/*:refs/*"); + + git_strarray_free(&problems); } void test_network_remote_rename__new_name_can_contain_dots(void) { - cl_git_pass(git_remote_rename(_remote, "just.renamed", dont_call_me_cb, NULL)); + git_strarray problems = {0}; + + cl_git_pass(git_remote_rename(&problems, _remote, "just.renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); cl_assert_equal_s("just.renamed", git_remote_name(_remote)); } void test_network_remote_rename__new_name_must_conform_to_reference_naming_conventions(void) { + git_strarray problems = {0}; + cl_assert_equal_i( GIT_EINVALIDSPEC, - git_remote_rename(_remote, "new@{name", dont_call_me_cb, NULL)); + git_remote_rename(&problems, _remote, "new@{name")); } void test_network_remote_rename__renamed_name_is_persisted(void) { git_remote *renamed; git_repository *another_repo; + git_strarray problems = {0}; cl_git_fail(git_remote_load(&renamed, _repo, "just/renamed")); - cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); cl_git_pass(git_repository_open(&another_repo, "testrepo.git")); cl_git_pass(git_remote_load(&renamed, _repo, "just/renamed")); @@ -144,19 +151,24 @@ void test_network_remote_rename__renamed_name_is_persisted(void) void test_network_remote_rename__cannot_overwrite_an_existing_remote(void) { - cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(_remote, "test", dont_call_me_cb, NULL)); - cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(_remote, "test_with_pushurl", dont_call_me_cb, NULL)); + git_strarray problems = {0}; + + cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(&problems, _remote, "test")); + cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(&problems, _remote, "test_with_pushurl")); } void test_network_remote_rename__renaming_a_remote_moves_the_underlying_reference(void) { git_reference *underlying; + git_strarray problems = {0}; cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed")); cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); git_reference_free(underlying); - cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + cl_git_pass(git_remote_rename(&problems, _remote, "just/renamed")); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed/master")); @@ -166,9 +178,91 @@ void test_network_remote_rename__renaming_a_remote_moves_the_underlying_referenc void test_network_remote_rename__cannot_rename_an_inmemory_remote(void) { git_remote *remote; + git_strarray problems = {0}; cl_git_pass(git_remote_create_anonymous(&remote, _repo, "file:///blah", NULL)); - cl_git_fail(git_remote_rename(remote, "newname", NULL, NULL)); + cl_git_fail(git_remote_rename(&problems, remote, "newname")); + git_strarray_free(&problems); git_remote_free(remote); } + +void test_network_remote_rename__overwrite_ref_in_target(void) +{ + git_oid id; + char idstr[GIT_OID_HEXSZ + 1] = {0}; + git_remote *remote; + git_reference *ref; + git_branch_t btype; + git_branch_iterator *iter; + git_strarray problems = {0}; + + cl_git_pass(git_oid_fromstr(&id, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750")); + cl_git_pass(git_reference_create(&ref, _repo, "refs/remotes/renamed/master", &id, 1, NULL, NULL)); + git_reference_free(ref); + + cl_git_pass(git_remote_load(&remote, _repo, "test")); + cl_git_pass(git_remote_rename(&problems, remote, "renamed")); + git_remote_free(remote); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); + + /* make sure there's only one remote-tracking branch */ + cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE)); + cl_git_pass(git_branch_next(&ref, &btype, iter)); + cl_assert_equal_s("refs/remotes/renamed/master", git_reference_name(ref)); + git_oid_fmt(idstr, git_reference_target(ref)); + cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr); + git_reference_free(ref); + + cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter)); + git_branch_iterator_free(iter); +} + +void test_network_remote_rename__symref_head(void) +{ + int error; + git_remote *remote; + git_reference *ref; + git_branch_t btype; + git_branch_iterator *iter; + git_strarray problems = {0}; + char idstr[GIT_OID_HEXSZ + 1] = {0}; + git_vector refs; + + cl_git_pass(git_reference_symbolic_create(&ref, _repo, "refs/remotes/test/HEAD", "refs/remotes/test/master", 0, NULL, NULL)); + git_reference_free(ref); + + cl_git_pass(git_remote_load(&remote, _repo, "test")); + cl_git_pass(git_remote_rename(&problems, remote, "renamed")); + git_remote_free(remote); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); + + cl_git_pass(git_vector_init(&refs, 2, (git_vector_cmp) git_reference_cmp)); + cl_git_pass(git_branch_iterator_new(&iter, _repo, GIT_BRANCH_REMOTE)); + + while ((error = git_branch_next(&ref, &btype, iter)) == 0) { + cl_git_pass(git_vector_insert(&refs, ref)); + } + cl_assert_equal_i(GIT_ITEROVER, error); + git_vector_sort(&refs); + + cl_assert_equal_i(2, refs.length); + + ref = git_vector_get(&refs, 0); + cl_assert_equal_s("refs/remotes/renamed/HEAD", git_reference_name(ref)); + cl_assert_equal_s("refs/remotes/renamed/master", git_reference_symbolic_target(ref)); + git_reference_free(ref); + + ref = git_vector_get(&refs, 1); + cl_assert_equal_s("refs/remotes/renamed/master", git_reference_name(ref)); + git_oid_fmt(idstr, git_reference_target(ref)); + cl_assert_equal_s("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", idstr); + git_reference_free(ref); + + git_vector_free(&refs); + + cl_git_fail_with(GIT_ITEROVER, git_branch_next(&ref, &btype, iter)); + git_branch_iterator_free(iter); +} diff --git a/tests/object/cache.c b/tests/object/cache.c index b927b2514..bdf12da7a 100644 --- a/tests/object/cache.c +++ b/tests/object/cache.c @@ -229,7 +229,7 @@ void test_object_cache__threadmania(void) #ifdef GIT_THREADS for (th = 0; th < THREADCOUNT; ++th) { - cl_git_pass(git_thread_join(t[th], &data)); + cl_git_pass(git_thread_join(&t[th], &data)); cl_assert_equal_i(th, ((int *)data)[0]); git__free(data); } @@ -276,7 +276,7 @@ void test_object_cache__fast_thread_rush(void) #ifdef GIT_THREADS for (th = 0; th < THREADCOUNT*2; ++th) { void *rval; - cl_git_pass(git_thread_join(t[th], &rval)); + cl_git_pass(git_thread_join(&t[th], &rval)); cl_assert_equal_i(th, *((int *)rval)); } #endif 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 e269771c0..cb541acf1 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -8,10 +8,11 @@ #define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository" #define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository" -#define BB_REPO_URL "https://libgit2@bitbucket.org/libgit2/testgitrepository.git" -#define BB_REPO_URL_WITH_PASS "https://libgit2:libgit2@bitbucket.org/libgit2/testgitrepository.git" -#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2:wrong@bitbucket.org/libgit2/testgitrepository.git" -#define ASSEMBLA_REPO_URL "https://libgit2:_Libgit2@git.assembla.com/libgit2-test-repos.git" +#define BB_REPO_URL "https://libgit3@bitbucket.org/libgit2/testgitrepository.git" +#define BB_REPO_URL_WITH_PASS "https://libgit3:libgit3@bitbucket.org/libgit2/testgitrepository.git" +#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit3:wrong@bitbucket.org/libgit2/testgitrepository.git" + +#define SSH_REPO_URL "ssh://github.com/libgit2/TestGitRepository" static git_repository *g_repo; static git_clone_options g_options; @@ -194,6 +195,9 @@ void test_online_clone__clone_mirror(void) git_remote_free(remote); git_reference_free(head); git_buf_free(&path); + git_repository_free(g_repo); + g_repo = NULL; + cl_fixture_cleanup("./foo.git"); } @@ -238,8 +242,41 @@ void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void) g_options.remote_callbacks.credentials = cred_failure_cb; - /* TODO: this should expect -172. */ - cl_git_fail_with(git_clone(&g_repo, remote_url, "./foo", &g_options), -1); + cl_git_fail_with(-172, git_clone(&g_repo, remote_url, "./foo", &g_options)); +} + +static int cred_count_calls_cb(git_cred **cred, const char *url, const char *user, + unsigned int allowed_types, void *data) +{ + size_t *counter = (size_t *) data; + + GIT_UNUSED(url); GIT_UNUSED(user); GIT_UNUSED(allowed_types); + + if (allowed_types == GIT_CREDTYPE_USERNAME) + return git_cred_username_new(cred, "foo"); + + (*counter)++; + + if (*counter == 3) + return GIT_EUSER; + + return git_cred_userpass_plaintext_new(cred, "foo", "bar"); +} + +void test_online_clone__cred_callback_called_again_on_auth_failure(void) +{ + const char *remote_url = cl_getenv("GITTEST_REMOTE_URL"); + const char *remote_user = cl_getenv("GITTEST_REMOTE_USER"); + size_t counter = 0; + + if (!remote_url || !remote_user) + clar__skip(); + + g_options.remote_callbacks.credentials = cred_count_calls_cb; + g_options.remote_callbacks.payload = &counter; + + cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, remote_url, "./foo", &g_options)); + cl_assert_equal_i(3, counter); } void test_online_clone__credentials(void) @@ -287,11 +324,6 @@ void test_online_clone__bitbucket_style(void) cl_fixture_cleanup("./foo"); } -void test_online_clone__assembla_style(void) -{ - cl_git_pass(git_clone(&g_repo, ASSEMBLA_REPO_URL, "./foo", NULL)); -} - static int cancel_at_half(const git_transfer_progress *stats, void *payload) { GIT_UNUSED(payload); @@ -310,7 +342,48 @@ void test_online_clone__can_cancel(void) } +static int check_ssh_auth_methods(git_cred **cred, const char *url, const char *username_from_url, + unsigned int allowed_types, void *data) +{ + int *with_user = (int *) data; + GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(data); + + if (!*with_user) + cl_assert_equal_i(GIT_CREDTYPE_USERNAME, allowed_types); + else + cl_assert(!(allowed_types & GIT_CREDTYPE_USERNAME)); + return GIT_EUSER; +} +void test_online_clone__ssh_auth_methods(void) +{ + int with_user; + g_options.remote_callbacks.credentials = check_ssh_auth_methods; + g_options.remote_callbacks.payload = &with_user; + with_user = 0; + cl_git_fail_with(GIT_EUSER, + git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options)); + + with_user = 1; + cl_git_fail_with(GIT_EUSER, + git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); +} + +static int cred_foo_bar(git_cred **cred, const char *url, const char *username_from_url, + unsigned int allowed_types, void *data) + +{ + GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(allowed_types); GIT_UNUSED(data); + + return git_cred_userpass_plaintext_new(cred, "foo", "bar"); +} + +void test_online_clone__ssh_cannot_change_username(void) +{ + g_options.remote_callbacks.credentials = cred_foo_bar; + + cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options)); +} 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/online/push.c b/tests/online/push.c index 6da27bb96..50419efd4 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -50,6 +50,15 @@ static int cred_acquire_cb( GIT_UNUSED(user_from_url); GIT_UNUSED(payload); + if (GIT_CREDTYPE_USERNAME & allowed_types) { + if (!_remote_user) { + printf("GITTEST_REMOTE_USER must be set\n"); + return -1; + } + + return git_cred_username_new(cred, _remote_user); + } + if (GIT_CREDTYPE_DEFAULT & allowed_types) { if (!_remote_default) { printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n"); 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/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/submodule/add.c b/tests/submodule/add.c index af81713f1..9fdc7cc57 100644 --- a/tests/submodule/add.c +++ b/tests/submodule/add.c @@ -68,13 +68,16 @@ void test_submodule_add__url_relative(void) { git_submodule *sm; git_remote *remote; + git_strarray problems = {0}; /* default remote url is https://github.com/libgit2/false.git */ g_repo = cl_git_sandbox_init("testrepo2"); /* make sure we don't default to origin - rename origin -> test_remote */ cl_git_pass(git_remote_load(&remote, g_repo, "origin")); - cl_git_pass(git_remote_rename(remote, "test_remote", NULL, NULL)); + cl_git_pass(git_remote_rename(&problems, remote, "test_remote")); + cl_assert_equal_i(0, problems.count); + git_strarray_free(&problems); cl_git_fail(git_remote_load(&remote, g_repo, "origin")); git_remote_free(remote); diff --git a/tests/submodule/submodule_helpers.c b/tests/submodule/submodule_helpers.c index 50aa97568..c6d04b40a 100644 --- a/tests/submodule/submodule_helpers.c +++ b/tests/submodule/submodule_helpers.c @@ -19,8 +19,8 @@ void rewrite_gitmodules(const char *workdir) cl_git_pass(git_buf_joinpath(&in_f, workdir, "gitmodules")); cl_git_pass(git_buf_joinpath(&out_f, workdir, ".gitmodules")); - cl_assert((in = fopen(in_f.ptr, "r")) != NULL); - cl_assert((out = fopen(out_f.ptr, "w")) != NULL); + cl_assert((in = fopen(in_f.ptr, "rb")) != NULL); + cl_assert((out = fopen(out_f.ptr, "wb")) != NULL); while (fgets(line, sizeof(line), in) != NULL) { char *scan = line; diff --git a/tests/threads/refdb.c b/tests/threads/refdb.c index c1cd29677..94a21f259 100644 --- a/tests/threads/refdb.c +++ b/tests/threads/refdb.c @@ -84,7 +84,7 @@ void test_threads_refdb__iterator(void) #ifdef GIT_THREADS for (t = 0; t < THREADS; ++t) { - cl_git_pass(git_thread_join(th[t], NULL)); + cl_git_pass(git_thread_join(&th[t], NULL)); } #endif @@ -215,7 +215,7 @@ void test_threads_refdb__edit_while_iterate(void) } for (t = 0; t < THREADS; ++t) { - cl_git_pass(git_thread_join(th[t], NULL)); + cl_git_pass(git_thread_join(&th[t], NULL)); } #endif } diff --git a/tests/threads/thread_helpers.c b/tests/threads/thread_helpers.c index 25370dddb..760a7bd33 100644 --- a/tests/threads/thread_helpers.c +++ b/tests/threads/thread_helpers.c @@ -32,7 +32,7 @@ void run_in_parallel( #ifdef GIT_THREADS for (t = 0; t < threads; ++t) - cl_git_pass(git_thread_join(th[t], NULL)); + cl_git_pass(git_thread_join(&th[t], NULL)); memset(th, 0, threads * sizeof(git_thread)); #endif |