From c49d328cf433da1bf25a97e3935069308daf7f8d Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Mon, 27 Aug 2012 09:59:13 -0400 Subject: [PATCH 01/72] Expose a malloc function to 3rd party ODB backends --- include/git2/odb_backend.h | 6 ++++++ src/odb.c | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index b812fef1efc..cb806978782 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -26,6 +26,10 @@ struct git_odb_stream; struct git_odb_backend { git_odb *odb; + /* read and read_prefix each return to libgit2 a buffer which + * will be freed later. The buffer should be allocated using + * the function git_odb_backend_malloc to ensure that it can + * be safely freed later. */ int (* read)( void **, size_t *, git_otype *, struct git_odb_backend *, @@ -102,6 +106,8 @@ GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **backend_out, const char * GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir, int compression_level, int do_fsync); GIT_EXTERN(int) git_odb_backend_one_pack(git_odb_backend **backend_out, const char *index_file); +GIT_EXTERN(void *) git_odb_backend_malloc(git_odb_backend *backend, size_t len); + GIT_END_DECL #endif diff --git a/src/odb.c b/src/odb.c index d5902840d25..55d434a8f2e 100644 --- a/src/odb.c +++ b/src/odb.c @@ -708,6 +708,11 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi return error; } +void * git_odb_backend_malloc(git_odb_backend *backend, size_t len) +{ + return git__malloc(len); +} + int git_odb__error_notfound(const char *message, const git_oid *oid) { if (oid != NULL) { From 89cd5708d94d8eb68a5e3a7b0fbda6ee904fb148 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 29 Aug 2012 14:20:53 +0200 Subject: [PATCH 02/72] repository: make initialization cope with missing core.worktree --- src/repository.c | 4 ++-- tests-clar/repo/init.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/repository.c b/src/repository.c index c12df25c3a1..b9d180da450 100644 --- a/src/repository.c +++ b/src/repository.c @@ -777,8 +777,8 @@ static int repo_init_config( SET_REPO_CONFIG(string, "core.worktree", work_dir); } else if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0) { - if ((error = git_config_delete(config, "core.worktree")) < 0) - goto cleanup; + if (git_config_delete(config, "core.worktree") < 0) + giterr_clear(); } } else { if (!are_symlinks_supported(repo_dir)) diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 67a9917db3f..f76e8bc3dc4 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -378,3 +378,18 @@ void test_repo_init__extended_with_template(void) cleanup_repository("templated.git"); } + +void test_repo_init__can_reinit_an_initialized_repository(void) +{ + git_repository *reinit; + + cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0)); + cl_git_pass(git_repository_init(&_repo, "extended", false)); + + cl_git_pass(git_repository_init(&reinit, "extended", false)); + + cl_assert_equal_s(git_repository_path(_repo), git_repository_path(reinit)); + + git_repository_free(reinit); + cleanup_repository("extended"); +} From 22e1b4b8a8ee8601bdd57da8cd4652afc24e068c Mon Sep 17 00:00:00 2001 From: Ben Straub Date: Thu, 30 Aug 2012 07:55:36 -0700 Subject: [PATCH 03/72] Ignore tags file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 45d7b195781..7fa7d547c75 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ CMake* *.cmake .DS_Store *~ +tags From 4deda91bda034d330fd20c1000ef2d3d2972bd0e Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Tue, 4 Sep 2012 00:13:59 +0200 Subject: [PATCH 04/72] netops: continue writing on SSL_ERROR_WANT_WRITE --- src/netops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/netops.c b/src/netops.c index f622e0d1017..a0d2bf3ab2b 100644 --- a/src/netops.c +++ b/src/netops.c @@ -442,7 +442,7 @@ static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) while (off < len) { ret = SSL_write(ssl->ssl, msg + off, len - off); - if (ret <= 0) + if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE) return ssl_set_error(ssl, ret); off += ret; From 65ac67fbbdb1a980aefb46bfdde918f43515acaf Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Tue, 28 Aug 2012 21:58:10 +0200 Subject: [PATCH 05/72] netops: be more careful with SSL errors SSL_get_error() allows to receive a result code for various SSL operations. Depending on the return value (see man (3) SSL_get_error) there might be additional information in the OpenSSL error queue. Return the queued message if available, otherwise set an error message corresponding to the return code. --- src/netops.c | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/netops.c b/src/netops.c index a0d2bf3ab2b..df502e61979 100644 --- a/src/netops.c +++ b/src/netops.c @@ -55,8 +55,44 @@ static void net_set_error(const char *str) static int ssl_set_error(gitno_ssl *ssl, int error) { int err; + unsigned long e; + err = SSL_get_error(ssl->ssl, error); - giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL)); + + assert(err != SSL_ERROR_WANT_READ); + assert(err != SSL_ERROR_WANT_WRITE); + + switch (err) { + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + giterr_set(GITERR_NET, "SSL error: connection failure\n"); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + giterr_set(GITERR_NET, "SSL error: x509 error\n"); + break; + case SSL_ERROR_SYSCALL: + e = ERR_get_error(); + if (e > 0) { + giterr_set(GITERR_NET, "SSL error: %s", + ERR_error_string(e, NULL)); + break; + } else if (error < 0) { + giterr_set(GITERR_OS, "SSL error: syscall failure"); + break; + } + giterr_set(GITERR_NET, "SSL error: received early EOF"); + break; + case SSL_ERROR_SSL: + e = ERR_get_error(); + giterr_set(GITERR_NET, "SSL error: %s", + ERR_error_string(e, NULL)); + break; + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + default: + giterr_set(GITERR_NET, "SSL error: unknown error"); + break; + } return -1; } #endif From b97c169ec0e6f6297dd00cae425f91e8baeb0f58 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 4 Sep 2012 10:01:18 +0200 Subject: [PATCH 06/72] Fix MSVC compilation warnings --- src/diff_output.c | 2 +- src/signature.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diff_output.c b/src/diff_output.c index 2bf939f330d..1f2c7233f1a 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -727,7 +727,7 @@ int git_diff_entrycount(git_diff_list *diff, int delta_t) assert(diff); if (delta_t < 0) - return diff->deltas.length; + return (int)diff->deltas.length; git_vector_foreach(&diff->deltas, i, delta) { if (delta->status == (git_delta_t)delta_t) diff --git a/src/signature.c b/src/signature.c index 84c3f499275..0159488a4d7 100644 --- a/src/signature.c +++ b/src/signature.c @@ -142,7 +142,7 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema time(&now); utc_tm = p_gmtime_r(&now, &_utc); utc_tm->tm_isdst = -1; - offset = difftime(now, mktime(utc_tm)); + offset = (time_t)difftime(now, mktime(utc_tm)); offset /= 60; if (git_signature_new(&sig, name, email, now, (int)offset) < 0) From 0e2dd29ba57580d3d81c62caa7ee4c3ca0a33829 Mon Sep 17 00:00:00 2001 From: authmillenon Date: Tue, 4 Sep 2012 12:07:51 +0200 Subject: [PATCH 07/72] Fix logical error in git_index_set_caps --- src/index.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.c b/src/index.c index a1042b72331..3a92c360bfe 100644 --- a/src/index.c +++ b/src/index.c @@ -247,7 +247,7 @@ int git_index_set_caps(git_index *index, unsigned int caps) if (git_config_get_bool(&val, cfg, "core.filemode") == 0) index->distrust_filemode = (val == 0); if (git_config_get_bool(&val, cfg, "core.symlinks") == 0) - index->no_symlinks = (val != 0); + index->no_symlinks = (val == 0); } else { index->ignore_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0); From 925be045d5c227dc595e9379f49a3f97b0aaeadd Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 4 Sep 2012 15:40:05 +0200 Subject: [PATCH 08/72] clar: Clear errors on shutdown --- tests-clar/clar_helpers.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_helpers.c index c914794380c..80d0e3ae9b7 100644 --- a/tests-clar/clar_helpers.c +++ b/tests-clar/clar_helpers.c @@ -9,6 +9,7 @@ void clar_on_init(void) void clar_on_shutdown(void) { git_threads_shutdown(); + giterr_clear(); } void cl_git_mkfile(const char *filename, const char *content) From 064ee42d99f1c457fcf728df728c0fb7ea65bc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 4 Sep 2012 15:54:33 +0200 Subject: [PATCH 09/72] travis: use a valgrind suppressions file We don't care about the supposed zlib errors, and the leak from giterr_set isn't interesting, as it gets freed each time an error is set. Give valgrind a suppressions file so it doesn't tell us about them. --- .travis.yml | 2 +- libgit2_clar.supp | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 libgit2_clar.supp diff --git a/.travis.yml b/.travis.yml index 29ef9d40dd5..54da48a40f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ script: # Run Tests after_script: - ctest -V . - - if [ -f ./libgit2_clar ]; then valgrind --leak-check=full --show-reachable=yes ./libgit2_clar; else echo "Skipping valgrind"; fi + - if [ -f ./libgit2_clar ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=../libgit2_clar.supp ./libgit2_clar; else echo "Skipping valgrind"; fi # Only watch the development branch branches: diff --git a/libgit2_clar.supp b/libgit2_clar.supp new file mode 100644 index 00000000000..f49eb005479 --- /dev/null +++ b/libgit2_clar.supp @@ -0,0 +1,12 @@ +{ + ignore-zlib-errors-cond + Memcheck:Cond + obj:*libz.so* +} + +{ + ignore-giterr-set-leak + Memcheck:Leak + ... + fun:giterr_set +} From f9988d4e4cc9818b0f338d6f6101241eb6da526b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 4 Sep 2012 21:42:00 +0200 Subject: [PATCH 10/72] odb: pass the user's data pointer correctly in foreach --- src/odb_pack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/odb_pack.c b/src/odb_pack.c index 8fc6e68e8c4..6e3d3eefd24 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -435,7 +435,7 @@ static int pack_backend__foreach(git_odb_backend *_backend, int (*cb)(git_oid *o return error; git_vector_foreach(&backend->packs, i, p) { - if ((error = git_pack_foreach_entry(p, cb, &data)) < 0) + if ((error = git_pack_foreach_entry(p, cb, data)) < 0) return error; } From c9d223f0de390e8b28af7c7513d03340001c2580 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 4 Sep 2012 22:57:31 +0200 Subject: [PATCH 11/72] branch: Add missing include --- include/git2/branch.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/git2/branch.h b/include/git2/branch.h index bbbdf1c4a59..f072799c5ac 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -8,6 +8,7 @@ #define INCLUDE_git_branch_h__ #include "common.h" +#include "oid.h" #include "types.h" /** From f335ecd6e126aa9dea28786522c0e6ce71596e91 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 30 Aug 2012 14:24:16 -0700 Subject: [PATCH 12/72] Diff iterators This refactors the diff output code so that an iterator object can be used to traverse and generate the diffs, instead of just the `foreach()` style with callbacks. The code has been rearranged so that the two styles can still share most functions. This also replaces `GIT_REVWALKOVER` with `GIT_ITEROVER` and uses that as a common error code for marking the end of iteration when using a iterator style of object. --- include/git2/diff.h | 61 +- include/git2/errors.h | 2 +- include/git2/revwalk.h | 2 +- src/attr.c | 1 + src/config_file.c | 1 + src/diff.c | 14 +- src/diff.h | 1 + src/diff_output.c | 1016 +++++++++++++----- src/fetch.c | 2 +- src/pool.h | 11 + src/refs.c | 1 - src/revparse.c | 2 +- src/revwalk.c | 30 +- src/status.c | 3 + src/submodule.c | 1 + tests-clar/diff/blob.c | 118 +- tests-clar/diff/diff_helpers.c | 71 ++ tests-clar/diff/diff_helpers.h | 6 + tests-clar/diff/diffiter.c | 116 ++ tests-clar/diff/index.c | 38 +- tests-clar/diff/tree.c | 72 +- tests-clar/diff/workdir.c | 485 ++++++--- tests-clar/resources/attr/.gitted/index | Bin 1856 -> 1856 bytes tests-clar/resources/issue_592/.gitted/index | Bin 392 -> 392 bytes tests-clar/resources/status/.gitted/index | Bin 1160 -> 1160 bytes 25 files changed, 1484 insertions(+), 570 deletions(-) create mode 100644 tests-clar/diff/diffiter.c diff --git a/include/git2/diff.h b/include/git2/diff.h index 088e1ecfa94..7ac6994e2bc 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -169,7 +169,7 @@ enum { GIT_DIFF_LINE_CONTEXT = ' ', GIT_DIFF_LINE_ADDITION = '+', GIT_DIFF_LINE_DELETION = '-', - GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< DEPRECATED */ + GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< DEPRECATED - will not be returned */ GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */ /* The following values will only be sent to a `git_diff_data_fn` when @@ -197,6 +197,11 @@ typedef int (*git_diff_data_fn)( const char *content, size_t content_len); +/** + * The diff iterator object is used to scan a diff list. + */ +typedef struct git_diff_iterator git_diff_iterator; + /** @name Diff List Generator Functions * * These are the functions you would use to create (or destroy) a @@ -321,6 +326,60 @@ GIT_EXTERN(int) git_diff_merge( */ /**@{*/ +/** + * Create a diff iterator object that can be used to traverse a diff. + */ +GIT_EXTERN(int) git_diff_iterator_new( + git_diff_iterator **iterator, + git_diff_list *diff); + +GIT_EXTERN(void) git_diff_iterator_free(git_diff_iterator *iter); + +/** + * Return the number of files in the diff. + */ +GIT_EXTERN(int) git_diff_iterator_num_files(git_diff_iterator *iterator); + +GIT_EXTERN(int) git_diff_iterator_num_hunks_in_file(git_diff_iterator *iterator); + +GIT_EXTERN(int) git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iterator); + +/** + * Return the delta information for the next file in the diff. + * + * This will return a pointer to the next git_diff_delta` to be processed or + * NULL if the iterator is at the end of the diff, then advance. + */ +GIT_EXTERN(int) git_diff_iterator_next_file( + git_diff_delta **delta, + git_diff_iterator *iterator); + +/** + * Return the hunk information for the next hunk in the current file. + * + * It is recommended that you not call this if the file is a binary + * file, but it is allowed to do so. + * + * Warning! Call this function for the first time on a file is when the + * actual text diff will be computed (it cannot be computed incrementally) + * so the first call for a new file is expensive (at least in relative + * terms - in reality, it is still pretty darn fast). + */ +GIT_EXTERN(int) git_diff_iterator_next_hunk( + git_diff_range **range, + const char **header, + size_t *header_len, + git_diff_iterator *iterator); + +/** + * Return the next line of the current hunk of diffs. + */ +GIT_EXTERN(int) git_diff_iterator_next_line( + char *line_origin, /**< GIT_DIFF_LINE_... value from above */ + const char **content, + size_t *content_len, + git_diff_iterator *iterator); + /** * Iterate over a diff list issuing callbacks. * diff --git a/include/git2/errors.h b/include/git2/errors.h index b55f8c30d93..f6671c49d71 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -28,7 +28,7 @@ enum { GIT_EUSER = -7, GIT_PASSTHROUGH = -30, - GIT_REVWALKOVER = -31, + GIT_ITEROVER = -31, }; typedef struct { diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index d86bb28ebbf..0a85a4c6082 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -201,7 +201,7 @@ GIT_EXTERN(int) git_revwalk_hide_ref(git_revwalk *walk, const char *refname); * @param oid Pointer where to store the oid of the next commit * @param walk the walker to pop the commit from. * @return 0 if the next commit was found; - * GIT_REVWALKOVER if there are no commits left to iterate + * GIT_ITEROVER if there are no commits left to iterate */ GIT_EXTERN(int) git_revwalk_next(git_oid *oid, git_revwalk *walk); diff --git a/src/attr.c b/src/attr.c index 99322066777..68f8d7de636 100644 --- a/src/attr.c +++ b/src/attr.c @@ -188,6 +188,7 @@ int git_attr_foreach( error = callback(assign->name, assign->value, payload); if (error) { + giterr_clear(); error = GIT_EUSER; goto cleanup; } diff --git a/src/config_file.c b/src/config_file.c index d3fb56aaafb..c575649afb8 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -221,6 +221,7 @@ static int file_foreach( /* abort iterator on non-zero return value */ if (fn(key, var->value, data)) { + giterr_clear(); result = GIT_EUSER; goto cleanup; } diff --git a/src/diff.c b/src/diff.c index 430f52e0ac3..f8a01086ca7 100644 --- a/src/diff.c +++ b/src/diff.c @@ -316,6 +316,7 @@ static git_diff_list *git_diff_list_alloc( if (diff == NULL) return NULL; + GIT_REFCOUNT_INC(diff); diff->repo = repo; if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 || @@ -391,15 +392,12 @@ static git_diff_list *git_diff_list_alloc( return NULL; } -void git_diff_list_free(git_diff_list *diff) +static void diff_list_free(git_diff_list *diff) { git_diff_delta *delta; git_attr_fnmatch *match; unsigned int i; - if (!diff) - return; - git_vector_foreach(&diff->deltas, i, delta) { git__free(delta); diff->deltas.contents[i] = NULL; @@ -416,6 +414,14 @@ void git_diff_list_free(git_diff_list *diff) git__free(diff); } +void git_diff_list_free(git_diff_list *diff) +{ + if (!diff) + return; + + GIT_REFCOUNT_DEC(diff, diff_list_free); +} + static int oid_for_workdir_item( git_repository *repo, const git_index_entry *item, diff --git a/src/diff.h b/src/diff.h index 6cc854fbd60..2785fa425b8 100644 --- a/src/diff.h +++ b/src/diff.h @@ -26,6 +26,7 @@ enum { }; struct git_diff_list { + git_refcount rc; git_repository *repo; git_diff_options opts; git_vector pathspec; diff --git a/src/diff_output.c b/src/diff_output.c index 1f2c7233f1a..69921741c45 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -16,16 +16,35 @@ #include "fileops.h" #include "filter.h" +/* + * A diff_delta_context represents all of the information that goes into + * processing the diff of an observed file change. In the case of the + * git_diff_foreach() call it is an emphemeral structure that is filled + * in to execute each diff. In the case of a git_diff_iterator, it holds + * most of the information for the diff in progress. + */ typedef struct { - git_diff_list *diff; - void *cb_data; - git_diff_hunk_fn hunk_cb; - git_diff_data_fn line_cb; - unsigned int index; + git_repository *repo; + git_diff_options *opts; + xdemitconf_t xdiff_config; + xpparam_t xdiff_params; git_diff_delta *delta; + uint32_t prepped : 1; + uint32_t loaded : 1; + uint32_t diffable : 1; + uint32_t diffed : 1; + git_iterator_type_t old_src; + git_iterator_type_t new_src; + git_blob *old_blob; + git_blob *new_blob; + git_map old_data; + git_map new_data; + void *cb_data; + git_diff_hunk_fn per_hunk; + git_diff_data_fn per_line; + int cb_error; git_diff_range range; - int error; -} diff_output_info; +} diff_delta_context; static int read_next_int(const char **str, int *value) { @@ -41,71 +60,89 @@ static int read_next_int(const char **str, int *value) return (digits > 0) ? 0 : -1; } -static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len) +static int parse_hunk_header(git_diff_range *range, const char *header) { - diff_output_info *info = priv; - - if (len == 1 && info->hunk_cb) { - git_diff_range range = { -1, 0, -1, 0 }; - const char *scan = bufs[0].ptr; + /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ + if (*header != '@') + return -1; + if (read_next_int(&header, &range->old_start) < 0) + return -1; + if (*header == ',') { + if (read_next_int(&header, &range->old_lines) < 0) + return -1; + } else + range->old_lines = 1; + if (read_next_int(&header, &range->new_start) < 0) + return -1; + if (*header == ',') { + if (read_next_int(&header, &range->new_lines) < 0) + return -1; + } else + range->new_lines = 1; + if (range->old_start < 0 || range->new_start < 0) + return -1; - /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ - if (*scan != '@') - info->error = -1; - else if (read_next_int(&scan, &range.old_start) < 0) - info->error = -1; - else if (*scan == ',' && read_next_int(&scan, &range.old_lines) < 0) - info->error = -1; - else if (read_next_int(&scan, &range.new_start) < 0) - info->error = -1; - else if (*scan == ',' && read_next_int(&scan, &range.new_lines) < 0) - info->error = -1; - else if (range.old_start < 0 || range.new_start < 0) - info->error = -1; - else { - memcpy(&info->range, &range, sizeof(git_diff_range)); + return 0; +} - if (info->hunk_cb( - info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size)) - info->error = GIT_EUSER; - } +static int format_hunk_header(char *header, size_t len, git_diff_range *range) +{ + if (range->old_lines != 1) { + if (range->new_lines != 1) + return snprintf( + header, len, "@@ -%d,%d +%d,%d @@", + range->old_start, range->old_lines, + range->new_start, range->new_lines); + else + return snprintf( + header, len, "@@ -%d,%d +%d @@", + range->old_start, range->old_lines, range->new_start); + } else { + if (range->new_lines != 1) + return snprintf( + header, len, "@@ -%d +%d,%d @@", + range->old_start, range->new_start, range->new_lines); + else + return snprintf( + header, len, "@@ -%d +%d @@", + range->old_start, range->new_start); } +} - if ((len == 2 || len == 3) && info->line_cb) { - int origin; - - /* expect " "/"-"/"+", then data, then maybe newline */ - origin = - (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : - (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : - GIT_DIFF_LINE_CONTEXT; +static bool diff_delta_is_ambiguous(git_diff_delta *delta) +{ + return (git_oid_iszero(&delta->new_file.oid) && + (delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0 && + delta->status == GIT_DELTA_MODIFIED); +} - if (info->line_cb( - info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size)) - info->error = GIT_EUSER; +static bool diff_delta_should_skip(git_diff_options *opts, git_diff_delta *delta) +{ + if (delta->status == GIT_DELTA_UNMODIFIED && + (opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return true; - /* This should only happen if we are adding a line that does not - * have a newline at the end and the old code did. In that case, - * we have a ADD with a DEL_EOFNL as a pair. - */ - else if (len == 3) { - origin = (origin == GIT_DIFF_LINE_ADDITION) ? - GIT_DIFF_LINE_DEL_EOFNL : GIT_DIFF_LINE_ADD_EOFNL; + if (delta->status == GIT_DELTA_IGNORED && + (opts->flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return true; - if (info->line_cb( - info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size)) - info->error = GIT_EUSER; - } - } + if (delta->status == GIT_DELTA_UNTRACKED && + (opts->flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return true; - return info->error; + return false; } #define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY) -static int update_file_is_binary_by_attr(git_repository *repo, git_diff_file *file) +static int update_file_is_binary_by_attr( + git_repository *repo, git_diff_file *file) { const char *value; + + if (!repo) + return 0; + if (git_attr_get(&value, repo, 0, file->path, "diff") < 0) return -1; @@ -129,11 +166,10 @@ static void update_delta_is_binary(git_diff_delta *delta) /* otherwise leave delta->binary value untouched */ } -static int file_is_binary_by_attr( - git_diff_list *diff, - git_diff_delta *delta) +static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) { int error = 0, mirror_new; + git_diff_delta *delta = ctxt->delta; delta->binary = -1; @@ -148,7 +184,7 @@ static int file_is_binary_by_attr( } /* check if user is forcing us to text diff these files */ - if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) { + if (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) { delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->binary = 0; @@ -156,7 +192,7 @@ static int file_is_binary_by_attr( } /* check diff attribute +, -, or 0 */ - if (update_file_is_binary_by_attr(diff->repo, &delta->old_file) < 0) + if (update_file_is_binary_by_attr(ctxt->repo, &delta->old_file) < 0) return -1; mirror_new = (delta->new_file.path == delta->old_file.path || @@ -164,23 +200,21 @@ static int file_is_binary_by_attr( if (mirror_new) delta->new_file.flags |= (delta->old_file.flags & BINARY_DIFF_FLAGS); else - error = update_file_is_binary_by_attr(diff->repo, &delta->new_file); + error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file); update_delta_is_binary(delta); return error; } -static int file_is_binary_by_content( - git_diff_delta *delta, - git_map *old_data, - git_map *new_data) +static int diff_delta_is_binary_by_content(diff_delta_context *ctxt) { + git_diff_delta *delta = ctxt->delta; git_buf search; if ((delta->old_file.flags & BINARY_DIFF_FLAGS) == 0) { - search.ptr = old_data->data; - search.size = min(old_data->len, 4000); + search.ptr = ctxt->old_data.data; + search.size = min(ctxt->old_data.len, 4000); if (git_buf_is_binary(&search)) delta->old_file.flags |= GIT_DIFF_FILE_BINARY; @@ -189,8 +223,8 @@ static int file_is_binary_by_content( } if ((delta->new_file.flags & BINARY_DIFF_FLAGS) == 0) { - search.ptr = new_data->data; - search.size = min(new_data->len, 4000); + search.ptr = ctxt->new_data.data; + search.size = min(ctxt->new_data.len, 4000); if (git_buf_is_binary(&search)) delta->new_file.flags |= GIT_DIFF_FILE_BINARY; @@ -256,16 +290,21 @@ static int get_workdir_content( return -1; if (S_ISLNK(file->mode)) { - ssize_t read_len; + ssize_t alloc_len, read_len; file->flags |= GIT_DIFF_FILE_FREE_DATA; file->flags |= GIT_DIFF_FILE_BINARY; - map->data = git__malloc((size_t)file->size + 1); + /* link path on disk could be UTF-16, so prepare a buffer that is + * big enough to handle some UTF-8 data expansion + */ + alloc_len = (ssize_t)(file->size * 2) + 1; + + map->data = git__malloc(alloc_len); GITERR_CHECK_ALLOC(map->data); - read_len = p_readlink(path.ptr, map->data, (size_t)file->size + 1); - if (read_len != (ssize_t)file->size) { + read_len = p_readlink(path.ptr, map->data, (int)alloc_len); + if (read_len < 0) { giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path); error = -1; } else @@ -286,189 +325,304 @@ static void release_content(git_diff_file *file, git_map *map, git_blob *blob) if (file->flags & GIT_DIFF_FILE_FREE_DATA) { git__free(map->data); - map->data = NULL; + map->data = ""; + map->len = 0; file->flags &= ~GIT_DIFF_FILE_FREE_DATA; } else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) { git_futils_mmap_free(map); - map->data = NULL; + map->data = ""; + map->len = 0; file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA; } } -static void fill_map_from_mmfile(git_map *dst, mmfile_t *src) { - assert(dst && src); +static void diff_delta_init_context( + diff_delta_context *ctxt, + git_repository *repo, + git_diff_options *opts, + git_iterator_type_t old_src, + git_iterator_type_t new_src) +{ + memset(ctxt, 0, sizeof(diff_delta_context)); + + ctxt->repo = repo; + ctxt->opts = opts; + ctxt->old_src = old_src; + ctxt->new_src = new_src; - dst->data = src->ptr; - dst->len = src->size; -#ifdef GIT_WIN32 - dst->fmh = NULL; -#endif + setup_xdiff_options(opts, &ctxt->xdiff_config, &ctxt->xdiff_params); } -int git_diff_foreach( - git_diff_list *diff, - void *data, - git_diff_file_fn file_cb, - git_diff_hunk_fn hunk_cb, - git_diff_data_fn line_cb) +static void diff_delta_init_context_from_diff_list( + diff_delta_context *ctxt, + git_diff_list *diff) { - int error = 0; - diff_output_info info; - git_diff_delta *delta; - xpparam_t xdiff_params; - xdemitconf_t xdiff_config; - xdemitcb_t xdiff_callback; + diff_delta_init_context( + ctxt, diff->repo, &diff->opts, diff->old_src, diff->new_src); +} - memset(&info, 0, sizeof(info)); - info.diff = diff; - info.cb_data = data; - info.hunk_cb = hunk_cb; - info.line_cb = line_cb; +static void diff_delta_unload(diff_delta_context *ctxt) +{ + ctxt->diffed = 0; - setup_xdiff_options(&diff->opts, &xdiff_config, &xdiff_params); - memset(&xdiff_callback, 0, sizeof(xdiff_callback)); - xdiff_callback.outf = diff_output_cb; - xdiff_callback.priv = &info; + if (ctxt->loaded) { + release_content(&ctxt->delta->old_file, &ctxt->old_data, ctxt->old_blob); + release_content(&ctxt->delta->new_file, &ctxt->new_data, ctxt->new_blob); + ctxt->loaded = 0; + } - git_vector_foreach(&diff->deltas, info.index, delta) { - git_blob *old_blob = NULL, *new_blob = NULL; - git_map old_data, new_data; - mmfile_t old_xdiff_data, new_xdiff_data; + ctxt->delta = NULL; + ctxt->prepped = 0; +} - if (delta->status == GIT_DELTA_UNMODIFIED && - (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - continue; +static int diff_delta_prep(diff_delta_context *ctxt) +{ + int error; - if (delta->status == GIT_DELTA_IGNORED && - (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) - continue; + if (ctxt->prepped || !ctxt->delta) + return 0; - if (delta->status == GIT_DELTA_UNTRACKED && - (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) - continue; + error = diff_delta_is_binary_by_attr(ctxt); - if ((error = file_is_binary_by_attr(diff, delta)) < 0) - goto cleanup; + ctxt->prepped = !error; - old_data.data = ""; - old_data.len = 0; - new_data.data = ""; - new_data.len = 0; + return error; +} - /* TODO: Partial blob reading to defer loading whole blob. - * I.e. I want a blob with just the first 4kb loaded, then - * later on I will read the rest of the blob if needed. - */ +static int diff_delta_load(diff_delta_context *ctxt) +{ + int error = 0; + git_diff_delta *delta = ctxt->delta; - /* map files */ - if (delta->binary != 1 && - (hunk_cb || line_cb || git_oid_iszero(&delta->old_file.oid)) && - (delta->status == GIT_DELTA_DELETED || - delta->status == GIT_DELTA_MODIFIED)) - { - if (diff->old_src == GIT_ITERATOR_WORKDIR) - error = get_workdir_content(diff->repo, &delta->old_file, &old_data); - else - error = get_blob_content( - diff->repo, &delta->old_file.oid, &old_data, &old_blob); + if (ctxt->loaded || !ctxt->delta) + return 0; - if (error < 0) - goto cleanup; + if (!ctxt->prepped && (error = diff_delta_prep(ctxt)) < 0) + goto cleanup; + + ctxt->old_data.data = ""; + ctxt->old_data.len = 0; + ctxt->old_blob = NULL; + + if (!error && delta->binary != 1 && + (delta->status == GIT_DELTA_DELETED || + delta->status == GIT_DELTA_MODIFIED)) + { + if (ctxt->old_src == GIT_ITERATOR_WORKDIR) + error = get_workdir_content( + ctxt->repo, &delta->old_file, &ctxt->old_data); + else { + error = get_blob_content( + ctxt->repo, &delta->old_file.oid, + &ctxt->old_data, &ctxt->old_blob); + + if (ctxt->new_src == GIT_ITERATOR_WORKDIR) { + /* TODO: convert crlf of blob content */ + } } + } - if (delta->binary != 1 && - (hunk_cb || line_cb || git_oid_iszero(&delta->new_file.oid)) && - (delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_MODIFIED)) - { - if (diff->new_src == GIT_ITERATOR_WORKDIR) - error = get_workdir_content(diff->repo, &delta->new_file, &new_data); - else - error = get_blob_content( - diff->repo, &delta->new_file.oid, &new_data, &new_blob); + ctxt->new_data.data = ""; + ctxt->new_data.len = 0; + ctxt->new_blob = NULL; + + if (!error && delta->binary != 1 && + (delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_MODIFIED)) + { + if (ctxt->new_src == GIT_ITERATOR_WORKDIR) + error = get_workdir_content( + ctxt->repo, &delta->new_file, &ctxt->new_data); + else { + error = get_blob_content( + ctxt->repo, &delta->new_file.oid, + &ctxt->new_data, &ctxt->new_blob); + if (ctxt->old_src == GIT_ITERATOR_WORKDIR) { + /* TODO: convert crlf of blob content */ + } + } + + if (!error && !(delta->new_file.flags & GIT_DIFF_FILE_VALID_OID)) { + error = git_odb_hash( + &delta->new_file.oid, ctxt->new_data.data, + ctxt->new_data.len, GIT_OBJ_BLOB); if (error < 0) goto cleanup; - if ((delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0) { - error = git_odb_hash( - &delta->new_file.oid, new_data.data, new_data.len, GIT_OBJ_BLOB); + delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; - if (error < 0) + /* since we did not have the definitive oid, we may have + * incorrect status and need to skip this item. + */ + if (delta->old_file.mode == delta->new_file.mode && + !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)) + { + delta->status = GIT_DELTA_UNMODIFIED; + + if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) goto cleanup; - delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; - - /* since we did not have the definitive oid, we may have - * incorrect status and need to skip this item. - */ - if (delta->old_file.mode == delta->new_file.mode && - !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)) - { - delta->status = GIT_DELTA_UNMODIFIED; - if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - goto cleanup; - } } } + } + + /* if we have not already decided whether file is binary, + * check the first 4K for nul bytes to decide... + */ + if (!error && delta->binary == -1) + error = diff_delta_is_binary_by_content(ctxt); + +cleanup: + ctxt->loaded = !error; + + /* flag if we would want to diff the contents of these files */ + if (ctxt->loaded) + ctxt->diffable = + (delta->binary != 1 && + delta->status != GIT_DELTA_UNMODIFIED && + (ctxt->old_data.len || ctxt->new_data.len) && + git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)); + + return error; +} + +static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len) +{ + diff_delta_context *ctxt = priv; + + if (len == 1) { + if ((ctxt->cb_error = parse_hunk_header(&ctxt->range, bufs[0].ptr)) < 0) + return ctxt->cb_error; + + if (ctxt->per_hunk != NULL && + ctxt->per_hunk(ctxt->cb_data, ctxt->delta, &ctxt->range, + bufs[0].ptr, bufs[0].size)) + ctxt->cb_error = GIT_EUSER; + } - /* if we have not already decided whether file is binary, - * check the first 4K for nul bytes to decide... + if (len == 2 || len == 3) { + /* expect " "/"-"/"+", then data */ + char origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : + GIT_DIFF_LINE_CONTEXT; + + if (ctxt->per_line != NULL && + ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin, + bufs[1].ptr, bufs[1].size)) + ctxt->cb_error = GIT_EUSER; + } + + if (len == 3 && !ctxt->cb_error) { + /* This should only happen if we are adding a line that does not + * have a newline at the end and the old code did. In that case, + * we have a ADD with a DEL_EOFNL as a pair. */ - if (delta->binary == -1) { - error = file_is_binary_by_content( - delta, &old_data, &new_data); - if (error < 0) + char origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : + GIT_DIFF_LINE_CONTEXT; + + if (ctxt->per_line != NULL && + ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin, + bufs[2].ptr, bufs[2].size)) + ctxt->cb_error = GIT_EUSER; + } + + return ctxt->cb_error; +} + +static int diff_delta_exec( + diff_delta_context *ctxt, + void *cb_data, + git_diff_hunk_fn per_hunk, + git_diff_data_fn per_line) +{ + int error = 0; + xdemitcb_t xdiff_callback; + mmfile_t old_xdiff_data, new_xdiff_data; + + if (ctxt->diffed || !ctxt->delta) + return 0; + + if (!ctxt->loaded && (error = diff_delta_load(ctxt)) < 0) + goto cleanup; + + if (!ctxt->diffable) + return 0; + + ctxt->cb_data = cb_data; + ctxt->per_hunk = per_hunk; + ctxt->per_line = per_line; + ctxt->cb_error = 0; + + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_delta_cb; + xdiff_callback.priv = ctxt; + + old_xdiff_data.ptr = ctxt->old_data.data; + old_xdiff_data.size = ctxt->old_data.len; + new_xdiff_data.ptr = ctxt->new_data.data; + new_xdiff_data.size = ctxt->new_data.len; + + xdl_diff(&old_xdiff_data, &new_xdiff_data, + &ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback); + + error = ctxt->cb_error; + +cleanup: + ctxt->diffed = !error; + + return error; +} + +int git_diff_foreach( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb) +{ + int error = 0; + diff_delta_context ctxt; + size_t idx; + + diff_delta_init_context_from_diff_list(&ctxt, diff); + + git_vector_foreach(&diff->deltas, idx, ctxt.delta) { + if (diff_delta_is_ambiguous(ctxt.delta)) + if ((error = diff_delta_load(&ctxt)) < 0) goto cleanup; - } - /* TODO: if ignore_whitespace is set, then we *must* do text - * diffs to tell if a file has really been changed. - */ + if (diff_delta_should_skip(ctxt.opts, ctxt.delta)) + continue; + + if ((error = diff_delta_load(&ctxt)) < 0) + goto cleanup; if (file_cb != NULL && - file_cb(data, delta, (float)info.index / diff->deltas.length)) + file_cb(data, ctxt.delta, (float)idx / diff->deltas.length) != 0) { error = GIT_EUSER; goto cleanup; } - /* don't do hunk and line diffs if file is binary */ - if (delta->binary == 1) - goto cleanup; - - /* nothing to do if we did not get data */ - if (!old_data.len && !new_data.len) - goto cleanup; - - /* nothing to do if only diff was a mode change */ - if (!git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)) - goto cleanup; - - assert(hunk_cb || line_cb); - - info.delta = delta; - old_xdiff_data.ptr = old_data.data; - old_xdiff_data.size = old_data.len; - new_xdiff_data.ptr = new_data.data; - new_xdiff_data.size = new_data.len; - - xdl_diff(&old_xdiff_data, &new_xdiff_data, - &xdiff_params, &xdiff_config, &xdiff_callback); - error = info.error; + error = diff_delta_exec(&ctxt, data, hunk_cb, line_cb); cleanup: - release_content(&delta->old_file, &old_data, old_blob); - release_content(&delta->new_file, &new_data, new_blob); + diff_delta_unload(&ctxt); if (error < 0) break; } + if (error == GIT_EUSER) + giterr_clear(); + return error; } - typedef struct { git_diff_list *diff; git_diff_data_fn print_cb; @@ -531,7 +685,10 @@ static int print_compact(void *data, git_diff_delta *delta, float progress) if (pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); return GIT_EUSER; + } return 0; } @@ -628,7 +785,10 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress) return -1; if (pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); return GIT_EUSER; + } if (delta->binary != 1) return 0; @@ -642,7 +802,10 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress) if (pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_BINARY, git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); return GIT_EUSER; + } return 0; } @@ -662,7 +825,10 @@ static int print_patch_hunk( if (pi->print_cb(pi->cb_data, d, r, GIT_DIFF_LINE_HUNK_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); return GIT_EUSER; + } return 0; } @@ -691,7 +857,10 @@ static int print_patch_line( if (pi->print_cb(pi->cb_data, delta, range, line_origin, git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); return GIT_EUSER; + } return 0; } @@ -726,17 +895,36 @@ int git_diff_entrycount(git_diff_list *diff, int delta_t) assert(diff); - if (delta_t < 0) - return (int)diff->deltas.length; - git_vector_foreach(&diff->deltas, i, delta) { - if (delta->status == (git_delta_t)delta_t) + if (diff_delta_should_skip(&diff->opts, delta)) + continue; + + if (delta_t < 0 || delta->status == (git_delta_t)delta_t) count++; } + /* It is possible that this has overcounted the number of diffs because + * there may be entries that are marked as MODIFIED due to differences + * in stat() output that will turn out to be the same once we calculate + * the actual SHA of the data on disk. + */ + return count; } +static void set_data_from_blob( + git_blob *blob, git_map *map, git_diff_file *file) +{ + if (blob) { + map->data = (char *)git_blob_rawcontent(blob); + file->size = map->len = git_blob_rawsize(blob); + git_oid_cpy(&file->oid, git_object_id((const git_object *)blob)); + } else { + map->data = ""; + file->size = map->len = 0; + } +} + int git_diff_blobs( git_blob *old_blob, git_blob *new_blob, @@ -746,17 +934,11 @@ int git_diff_blobs( git_diff_hunk_fn hunk_cb, git_diff_data_fn line_cb) { - diff_output_info info; + int error; + diff_delta_context ctxt; git_diff_delta delta; - mmfile_t old_data, new_data; - git_map old_map, new_map; - xpparam_t xdiff_params; - xdemitconf_t xdiff_config; - xdemitcb_t xdiff_callback; git_blob *new, *old; - memset(&delta, 0, sizeof(delta)); - new = new_blob; old = old_blob; @@ -766,25 +948,16 @@ int git_diff_blobs( new = swap; } - if (old) { - old_data.ptr = (char *)git_blob_rawcontent(old); - old_data.size = git_blob_rawsize(old); - git_oid_cpy(&delta.old_file.oid, git_object_id((const git_object *)old)); - } else { - old_data.ptr = ""; - old_data.size = 0; - } - - if (new) { - new_data.ptr = (char *)git_blob_rawcontent(new); - new_data.size = git_blob_rawsize(new); - git_oid_cpy(&delta.new_file.oid, git_object_id((const git_object *)new)); - } else { - new_data.ptr = ""; - new_data.size = 0; - } + diff_delta_init_context( + &ctxt, NULL, options, GIT_ITERATOR_TREE, GIT_ITERATOR_TREE); /* populate a "fake" delta record */ + + memset(&delta, 0, sizeof(delta)); + + set_data_from_blob(old, &ctxt.old_data, &delta.old_file); + set_data_from_blob(new, &ctxt.new_data, &delta.new_file); + delta.status = new ? (old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : (old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); @@ -792,39 +965,370 @@ int git_diff_blobs( if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0) delta.status = GIT_DELTA_UNMODIFIED; - delta.old_file.size = old_data.size; - delta.new_file.size = new_data.size; + ctxt.delta = δ + + if ((error = diff_delta_prep(&ctxt)) < 0) + goto cleanup; + + if (delta.binary == -1 && + (error = diff_delta_is_binary_by_content(&ctxt)) < 0) + goto cleanup; + + ctxt.loaded = 1; + ctxt.diffable = (delta.binary != 1 && delta.status != GIT_DELTA_UNMODIFIED); + + /* do diffs */ + + if (file_cb != NULL && file_cb(cb_data, &delta, 1)) { + error = GIT_EUSER; + goto cleanup; + } + + error = diff_delta_exec(&ctxt, cb_data, hunk_cb, line_cb); + +cleanup: + if (error == GIT_EUSER) + giterr_clear(); + + diff_delta_unload(&ctxt); + + return error; +} + +typedef struct diffiter_line diffiter_line; +struct diffiter_line { + diffiter_line *next; + char origin; + const char *ptr; + size_t len; +}; + +typedef struct diffiter_hunk diffiter_hunk; +struct diffiter_hunk { + diffiter_hunk *next; + git_diff_range range; + diffiter_line *line_head; + size_t line_count; +}; + +struct git_diff_iterator { + git_diff_list *diff; + diff_delta_context ctxt; + size_t file_index; + size_t next_index; + size_t file_count; + git_pool hunks; + size_t hunk_count; + diffiter_hunk *hunk_head; + diffiter_hunk *hunk_curr; + char hunk_header[128]; + git_pool lines; + size_t line_count; + diffiter_line *line_curr; +}; + +typedef struct { + git_diff_iterator *iter; + diffiter_hunk *last_hunk; + diffiter_line *last_line; +} diffiter_cb_info; - fill_map_from_mmfile(&old_map, &old_data); - fill_map_from_mmfile(&new_map, &new_data); +static int diffiter_hunk_cb( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + const char *header, + size_t header_len) +{ + diffiter_cb_info *info = cb_data; + git_diff_iterator *iter = info->iter; + diffiter_hunk *hunk; + + GIT_UNUSED(delta); + GIT_UNUSED(header); + GIT_UNUSED(header_len); - if (file_is_binary_by_content(&delta, &old_map, &new_map) < 0) + if ((hunk = git_pool_mallocz(&iter->hunks, 1)) == NULL) { + iter->ctxt.cb_error = -1; return -1; + } - if (file_cb != NULL && file_cb(cb_data, &delta, 1)) - return GIT_EUSER; + if (info->last_hunk) + info->last_hunk->next = hunk; + info->last_hunk = hunk; - /* don't do hunk and line diffs if the two blobs are identical */ - if (delta.status == GIT_DELTA_UNMODIFIED) - return 0; + memcpy(&hunk->range, range, sizeof(hunk->range)); + + iter->hunk_count++; + + if (iter->hunk_head == NULL) + iter->hunk_curr = iter->hunk_head = hunk; + + return 0; +} - /* don't do hunk and line diffs if file is binary */ - if (delta.binary == 1) +static int diffiter_line_cb( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + char line_origin, + const char *content, + size_t content_len) +{ + diffiter_cb_info *info = cb_data; + git_diff_iterator *iter = info->iter; + diffiter_line *line; + + GIT_UNUSED(delta); + GIT_UNUSED(range); + + if ((line = git_pool_mallocz(&iter->lines, 1)) == NULL) { + iter->ctxt.cb_error = -1; + return -1; + } + + if (info->last_line) + info->last_line->next = line; + info->last_line = line; + + line->origin = line_origin; + line->ptr = content; + line->len = content_len; + + info->last_hunk->line_count++; + iter->line_count++; + + if (info->last_hunk->line_head == NULL) + info->last_hunk->line_head = line; + + return 0; +} + +static int diffiter_do_diff_file(git_diff_iterator *iter) +{ + int error; + diffiter_cb_info info; + + if (iter->ctxt.diffed || !iter->ctxt.delta) return 0; memset(&info, 0, sizeof(info)); - info.diff = NULL; - info.delta = δ - info.cb_data = cb_data; - info.hunk_cb = hunk_cb; - info.line_cb = line_cb; + info.iter = iter; - setup_xdiff_options(options, &xdiff_config, &xdiff_params); - memset(&xdiff_callback, 0, sizeof(xdiff_callback)); - xdiff_callback.outf = diff_output_cb; - xdiff_callback.priv = &info; + error = diff_delta_exec( + &iter->ctxt, &info, diffiter_hunk_cb, diffiter_line_cb); + + if (error == GIT_EUSER) + error = iter->ctxt.cb_error; + + return error; +} - xdl_diff(&old_data, &new_data, &xdiff_params, &xdiff_config, &xdiff_callback); +static void diffiter_do_unload_file(git_diff_iterator *iter) +{ + if (iter->ctxt.loaded) { + diff_delta_unload(&iter->ctxt); + + git_pool_clear(&iter->lines); + git_pool_clear(&iter->hunks); + } + + iter->ctxt.delta = NULL; + iter->hunk_head = NULL; + iter->hunk_count = 0; + iter->line_count = 0; +} + +int git_diff_iterator_new( + git_diff_iterator **iterator_ptr, + git_diff_list *diff) +{ + size_t i; + git_diff_delta *delta; + git_diff_iterator *iter; + + assert(diff && iterator_ptr); + + *iterator_ptr = NULL; + + iter = git__malloc(sizeof(git_diff_iterator)); + GITERR_CHECK_ALLOC(iter); + + memset(iter, 0, sizeof(*iter)); + + iter->diff = diff; + GIT_REFCOUNT_INC(iter->diff); + + diff_delta_init_context_from_diff_list(&iter->ctxt, diff); + + if (git_pool_init(&iter->hunks, sizeof(diffiter_hunk), 0) < 0 || + git_pool_init(&iter->lines, sizeof(diffiter_line), 0) < 0) + goto fail; + + git_vector_foreach(&diff->deltas, i, delta) { + if (diff_delta_should_skip(iter->ctxt.opts, delta)) + continue; + iter->file_count++; + } + + *iterator_ptr = iter; + + return 0; + +fail: + git_diff_iterator_free(iter); + + return -1; +} + +void git_diff_iterator_free(git_diff_iterator *iter) +{ + diffiter_do_unload_file(iter); + git_diff_list_free(iter->diff); /* decrement ref count */ + git__free(iter); +} + +int git_diff_iterator_num_files(git_diff_iterator *iter) +{ + return (int)iter->file_count; +} + +int git_diff_iterator_num_hunks_in_file(git_diff_iterator *iter) +{ + int error = diffiter_do_diff_file(iter); + return (error != 0) ? error : (int)iter->hunk_count; +} + +int git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iter) +{ + int error = diffiter_do_diff_file(iter); + return (error != 0) ? error : (int)iter->line_count; +} + +int git_diff_iterator_next_file( + git_diff_delta **delta_ptr, + git_diff_iterator *iter) +{ + int error = 0; + + assert(iter); + + iter->file_index = iter->next_index; + + diffiter_do_unload_file(iter); - return info.error; + while (!error) { + iter->ctxt.delta = git_vector_get(&iter->diff->deltas, iter->file_index); + if (!iter->ctxt.delta) { + error = GIT_ITEROVER; + break; + } + + if (diff_delta_is_ambiguous(iter->ctxt.delta) && + (error = diff_delta_load(&iter->ctxt)) < 0) + break; + + if (!diff_delta_should_skip(iter->ctxt.opts, iter->ctxt.delta)) + break; + + iter->file_index++; + } + + if (!error) { + iter->next_index = iter->file_index + 1; + + error = diff_delta_prep(&iter->ctxt); + } + + if (iter->ctxt.delta == NULL) { + iter->hunk_curr = NULL; + iter->line_curr = NULL; + } + + if (delta_ptr != NULL) + *delta_ptr = !error ? iter->ctxt.delta : NULL; + + return error; +} + +int git_diff_iterator_next_hunk( + git_diff_range **range_ptr, + const char **header, + size_t *header_len, + git_diff_iterator *iter) +{ + int error = diffiter_do_diff_file(iter); + git_diff_range *range; + + if (error) + return error; + + if (iter->hunk_curr == NULL) { + if (range_ptr) *range_ptr = NULL; + if (header) *header = NULL; + if (header_len) *header_len = 0; + iter->line_curr = NULL; + return GIT_ITEROVER; + } + + range = &iter->hunk_curr->range; + + if (range_ptr) + *range_ptr = range; + + if (header) { + int out = format_hunk_header( + iter->hunk_header, sizeof(iter->hunk_header), range); + + /* TODO: append function name to header */ + + *(iter->hunk_header + out++) = '\n'; + + *header = iter->hunk_header; + + if (header_len) + *header_len = (size_t)out; + } + + iter->line_curr = iter->hunk_curr->line_head; + iter->hunk_curr = iter->hunk_curr->next; + + return error; +} + +int git_diff_iterator_next_line( + char *line_origin, /**< GIT_DIFF_LINE_... value from above */ + const char **content_ptr, + size_t *content_len, + git_diff_iterator *iter) +{ + int error = diffiter_do_diff_file(iter); + + if (error) + return error; + + /* if the user has not called next_hunk yet, call it implicitly (OK?) */ + if (iter->hunk_curr == iter->hunk_head) { + error = git_diff_iterator_next_hunk(NULL, NULL, NULL, iter); + if (error) + return error; + } + + if (iter->line_curr == NULL) { + if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT; + if (content_ptr) *content_ptr = NULL; + if (content_len) *content_len = 0; + return GIT_ITEROVER; + } + + if (line_origin) + *line_origin = iter->line_curr->origin; + if (content_ptr) + *content_ptr = iter->line_curr->ptr; + if (content_len) + *content_len = iter->line_curr->len; + + iter->line_curr = iter->line_curr->next; + + return error; } diff --git a/src/fetch.c b/src/fetch.c index 278ba3c50f1..98e1f0b13aa 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -221,7 +221,7 @@ int git_fetch_negotiate(git_remote *remote) } } - if (error < 0 && error != GIT_REVWALKOVER) + if (error < 0 && error != GIT_ITEROVER) goto on_error; /* Tell the other end that we're done negotiating */ diff --git a/src/pool.h b/src/pool.h index 05d3392447c..dee6ecdae11 100644 --- a/src/pool.h +++ b/src/pool.h @@ -75,6 +75,17 @@ extern void git_pool_swap(git_pool *a, git_pool *b); */ extern void *git_pool_malloc(git_pool *pool, uint32_t items); +/** + * Allocate space and zero it out. + */ +GIT_INLINE(void *) git_pool_mallocz(git_pool *pool, uint32_t items) +{ + void *ptr = git_pool_malloc(pool, items); + if (ptr) + memset(ptr, 0, (size_t)items * (size_t)pool->item_size); + return ptr; +} + /** * Allocate space and duplicate string data into it. * diff --git a/src/refs.c b/src/refs.c index 1589bc37d35..211a5870cc8 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1643,7 +1643,6 @@ int git_reference_normalize_name( } } - *buffer_out++ = *current++; buffer_size--; } diff --git a/src/revparse.c b/src/revparse.c index 3855c29f129..17266b94485 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -493,7 +493,7 @@ static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex) git_object_free(obj); } - if (error < 0 && error == GIT_REVWALKOVER) + if (error < 0 && error == GIT_ITEROVER) error = GIT_ENOTFOUND; return error; diff --git a/src/revwalk.c b/src/revwalk.c index 8b0e93baf4b..1a092771957 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -449,6 +449,7 @@ int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *tw if (!result) { git_revwalk_free(walk); + giterr_clear(); return GIT_ENOTFOUND; } @@ -682,7 +683,8 @@ static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk) } } - return GIT_REVWALKOVER; + giterr_clear(); + return GIT_ITEROVER; } static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) @@ -700,7 +702,8 @@ static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) } } - return GIT_REVWALKOVER; + giterr_clear(); + return GIT_ITEROVER; } static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) @@ -710,8 +713,10 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) for (;;) { next = commit_list_pop(&walk->iterator_topo); - if (next == NULL) - return GIT_REVWALKOVER; + if (next == NULL) { + giterr_clear(); + return GIT_ITEROVER; + } if (next->in_degree > 0) { next->topo_delay = 1; @@ -736,7 +741,7 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk) { *object_out = commit_list_pop(&walk->iterator_reverse); - return *object_out ? 0 : GIT_REVWALKOVER; + return *object_out ? 0 : GIT_ITEROVER; } @@ -751,8 +756,10 @@ static int prepare_walk(git_revwalk *walk) * If walk->one is NULL, there were no positive references, * so we know that the walk is already over. */ - if (walk->one == NULL) - return GIT_REVWALKOVER; + if (walk->one == NULL) { + giterr_clear(); + return GIT_ITEROVER; + } /* first figure out what the merge bases are */ if (merge_bases_many(&bases, walk, walk->one, &walk->twos) < 0) @@ -780,7 +787,7 @@ static int prepare_walk(git_revwalk *walk) return -1; } - if (error != GIT_REVWALKOVER) + if (error != GIT_ITEROVER) return error; walk->get_next = &revwalk_next_toposort; @@ -792,7 +799,7 @@ static int prepare_walk(git_revwalk *walk) if (commit_list_insert(next, &walk->iterator_reverse) == NULL) return -1; - if (error != GIT_REVWALKOVER) + if (error != GIT_ITEROVER) return error; walk->get_next = &revwalk_next_reverse; @@ -891,9 +898,10 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) error = walk->get_next(&next, walk); - if (error == GIT_REVWALKOVER) { + if (error == GIT_ITEROVER) { git_revwalk_reset(walk); - return GIT_REVWALKOVER; + giterr_clear(); + return GIT_ITEROVER; } if (!error) diff --git a/src/status.c b/src/status.c index 3d3d15d77f8..0a5fbdcbf79 100644 --- a/src/status.c +++ b/src/status.c @@ -151,6 +151,9 @@ int git_status_foreach_ext( git_diff_list_free(idx2head); git_diff_list_free(wd2idx); + if (err == GIT_EUSER) + giterr_clear(); + return err; } diff --git a/src/submodule.c b/src/submodule.c index a9de9ee6ed9..66f1f84b45b 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -163,6 +163,7 @@ int git_submodule_foreach( } if (callback(sm, sm->name, payload)) { + giterr_clear(); error = GIT_EUSER; break; } diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c index 5d3ab8d569e..d5cf41e9955 100644 --- a/tests-clar/diff/blob.c +++ b/tests-clar/diff/blob.c @@ -58,59 +58,59 @@ void test_diff_blob__can_compare_text_blobs(void) cl_git_pass(git_diff_blobs( a, b, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_mods); cl_assert(expected.at_least_one_of_them_is_binary == false); - cl_assert(expected.hunks == 1); - cl_assert(expected.lines == 6); - cl_assert(expected.line_ctxt == 1); - cl_assert(expected.line_adds == 5); - cl_assert(expected.line_dels == 0); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(6, expected.lines); + cl_assert_equal_i(1, expected.line_ctxt); + cl_assert_equal_i(5, expected.line_adds); + cl_assert_equal_i(0, expected.line_dels); /* diff on tests/resources/attr/root_test2 */ memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( b, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_mods); cl_assert(expected.at_least_one_of_them_is_binary == false); - cl_assert(expected.hunks == 1); - cl_assert(expected.lines == 15); - cl_assert(expected.line_ctxt == 3); - cl_assert(expected.line_adds == 9); - cl_assert(expected.line_dels == 3); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(15, expected.lines); + cl_assert_equal_i(3, expected.line_ctxt); + cl_assert_equal_i(9, expected.line_adds); + cl_assert_equal_i(3, expected.line_dels); /* diff on tests/resources/attr/root_test3 */ memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( a, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_mods); cl_assert(expected.at_least_one_of_them_is_binary == false); - cl_assert(expected.hunks == 1); - cl_assert(expected.lines == 13); - cl_assert(expected.line_ctxt == 0); - cl_assert(expected.line_adds == 12); - cl_assert(expected.line_dels == 1); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(13, expected.lines); + cl_assert_equal_i(0, expected.line_ctxt); + cl_assert_equal_i(12, expected.line_adds); + cl_assert_equal_i(1, expected.line_dels); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( c, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_mods); cl_assert(expected.at_least_one_of_them_is_binary == false); - cl_assert(expected.hunks == 2); - cl_assert(expected.lines == 14); - cl_assert(expected.line_ctxt == 4); - cl_assert(expected.line_adds == 6); - cl_assert(expected.line_dels == 4); + cl_assert_equal_i(2, expected.hunks); + cl_assert_equal_i(14, expected.lines); + cl_assert_equal_i(4, expected.line_ctxt); + cl_assert_equal_i(6, expected.line_adds); + cl_assert_equal_i(4, expected.line_dels); git_blob_free(a); git_blob_free(b); @@ -124,14 +124,14 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_git_pass(git_diff_blobs( d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_dels == 1); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_dels); cl_assert(expected.at_least_one_of_them_is_binary == false); - cl_assert(expected.hunks == 1); - cl_assert(expected.hunk_old_lines == 14); - cl_assert(expected.lines == 14); - cl_assert(expected.line_dels == 14); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(14, expected.hunk_old_lines); + cl_assert_equal_i(14, expected.lines); + cl_assert_equal_i(14, expected.line_dels); opts.flags |= GIT_DIFF_REVERSE; memset(&expected, 0, sizeof(expected)); @@ -139,14 +139,14 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_git_pass(git_diff_blobs( d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_adds == 1); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_adds); cl_assert(expected.at_least_one_of_them_is_binary == false); - cl_assert(expected.hunks == 1); - cl_assert(expected.hunk_new_lines == 14); - cl_assert(expected.lines == 14); - cl_assert(expected.line_adds == 14); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(14, expected.hunk_new_lines); + cl_assert_equal_i(14, expected.lines); + cl_assert_equal_i(14, expected.line_adds); opts.flags ^= GIT_DIFF_REVERSE; memset(&expected, 0, sizeof(expected)); @@ -156,10 +156,10 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_assert(expected.at_least_one_of_them_is_binary == true); - cl_assert(expected.files == 1); - cl_assert(expected.file_dels == 1); - cl_assert(expected.hunks == 0); - cl_assert(expected.lines == 0); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_dels); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); memset(&expected, 0, sizeof(expected)); @@ -168,18 +168,18 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_assert(expected.at_least_one_of_them_is_binary == true); - cl_assert(expected.files == 1); - cl_assert(expected.file_adds == 1); - cl_assert(expected.hunks == 0); - cl_assert(expected.lines == 0); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_adds); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); } static void assert_identical_blobs_comparison(diff_expects expected) { - cl_assert(expected.files == 1); - cl_assert(expected.file_unmodified == 1); - cl_assert(expected.hunks == 0); - cl_assert(expected.lines == 0); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_unmodified); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); } void test_diff_blob__can_compare_identical_blobs(void) @@ -209,10 +209,10 @@ static void assert_binary_blobs_comparison(diff_expects expected) { cl_assert(expected.at_least_one_of_them_is_binary == true); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); - cl_assert(expected.hunks == 0); - cl_assert(expected.lines == 0); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_mods); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); } void test_diff_blob__can_compare_two_binary_blobs(void) @@ -292,7 +292,7 @@ void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) cl_git_pass(git_diff_blobs( old_d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.hunks == 2); + cl_assert_equal_i(2, expected.hunks); /* Test with inter-hunk-context explicitly set to 0 */ opts.interhunk_lines = 0; @@ -300,7 +300,7 @@ void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) cl_git_pass(git_diff_blobs( old_d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.hunks == 2); + cl_assert_equal_i(2, expected.hunks); /* Test with inter-hunk-context explicitly set to 1 */ opts.interhunk_lines = 1; @@ -308,7 +308,7 @@ void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) cl_git_pass(git_diff_blobs( old_d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.hunks == 1); + cl_assert_equal_i(1, expected.hunks); git_blob_free(old_d); } diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index 7b391262dd4..59e01802c1f 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -103,3 +103,74 @@ int diff_line_fn( } return 0; } + +int diff_foreach_via_iterator( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb) +{ + int error, curr, total; + git_diff_iterator *iter; + git_diff_delta *delta; + + if ((error = git_diff_iterator_new(&iter, diff)) < 0) + return error; + + curr = 0; + total = git_diff_iterator_num_files(iter); + + while (!(error = git_diff_iterator_next_file(&delta, iter))) { + git_diff_range *range; + const char *hdr; + size_t hdr_len; + + /* call file_cb for this file */ + if (file_cb != NULL && file_cb(data, delta, (float)curr / total) != 0) + goto abort; + + if (!hunk_cb && !line_cb) + continue; + + while (!(error = git_diff_iterator_next_hunk( + &range, &hdr, &hdr_len, iter))) { + char origin; + const char *line; + size_t line_len; + + if (hunk_cb && hunk_cb(data, delta, range, hdr, hdr_len) != 0) + goto abort; + + if (!line_cb) + continue; + + while (!(error = git_diff_iterator_next_line( + &origin, &line, &line_len, iter))) { + + if (line_cb(data, delta, range, origin, line, line_len) != 0) + goto abort; + } + + if (error && error != GIT_ITEROVER) + goto done; + } + + if (error && error != GIT_ITEROVER) + goto done; + } + +done: + git_diff_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; + + return error; + +abort: + git_diff_iterator_free(iter); + giterr_clear(); + + return GIT_EUSER; +} diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h index 0aaa6c11148..79e140921f9 100644 --- a/tests-clar/diff/diff_helpers.h +++ b/tests-clar/diff/diff_helpers.h @@ -45,3 +45,9 @@ extern int diff_line_fn( const char *content, size_t content_len); +extern int diff_foreach_via_iterator( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb); diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c new file mode 100644 index 00000000000..56c25474168 --- /dev/null +++ b/tests-clar/diff/diffiter.c @@ -0,0 +1,116 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +void test_diff_diffiter__initialize(void) +{ +} + +void test_diff_diffiter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_diffiter__create(void) +{ + git_repository *repo = cl_git_sandbox_init("attr"); + git_diff_list *diff; + git_diff_iterator *iter; + + cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + git_diff_iterator_free(iter); + git_diff_list_free(diff); +} + +void test_diff_diffiter__iterate_files(void) +{ + git_repository *repo = cl_git_sandbox_init("attr"); + git_diff_list *diff; + git_diff_iterator *iter; + git_diff_delta *delta; + int error, count = 0; + + cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta != NULL); + count++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + cl_assert_equal_i(6, count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); +} + +void test_diff_diffiter__iterate_files_2(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_list *diff; + git_diff_iterator *iter; + git_diff_delta *delta; + int error, count = 0; + + cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta != NULL); + count++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + cl_assert_equal_i(8, count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); +} + +void test_diff_diffiter__iterate_files_and_hunks(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + git_diff_iterator *iter; + git_diff_delta *delta; + git_diff_range *range; + const char *header; + size_t header_len; + int error, file_count = 0, hunk_count = 0; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); + + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta); + + file_count++; + + while ((error = git_diff_iterator_next_hunk( + &range, &header, &header_len, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(range); + hunk_count++; + } + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + cl_assert_equal_i(13, file_count); + cl_assert_equal_i(8, hunk_count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); +} diff --git a/tests-clar/diff/index.c b/tests-clar/diff/index.c index 89e65e3b7be..2c6e89c4a39 100644 --- a/tests-clar/diff/index.c +++ b/tests-clar/diff/index.c @@ -44,17 +44,17 @@ void test_diff_index__0(void) * - git diff -U1 --cached 26a125ee1bf * - mv .git .gitted */ - cl_assert(exp.files == 8); - cl_assert(exp.file_adds == 3); - cl_assert(exp.file_dels == 2); - cl_assert(exp.file_mods == 3); + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(3, exp.file_adds); + cl_assert_equal_i(2, exp.file_dels); + cl_assert_equal_i(3, exp.file_mods); - cl_assert(exp.hunks == 8); + cl_assert_equal_i(8, exp.hunks); - cl_assert(exp.lines == 11); - cl_assert(exp.line_ctxt == 3); - cl_assert(exp.line_adds == 6); - cl_assert(exp.line_dels == 2); + cl_assert_equal_i(11, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(6, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); git_diff_list_free(diff); diff = NULL; @@ -72,17 +72,17 @@ void test_diff_index__0(void) * - git diff -U1 --cached 0017bd4ab1ec3 * - mv .git .gitted */ - cl_assert(exp.files == 12); - cl_assert(exp.file_adds == 7); - cl_assert(exp.file_dels == 2); - cl_assert(exp.file_mods == 3); + cl_assert_equal_i(12, exp.files); + cl_assert_equal_i(7, exp.file_adds); + cl_assert_equal_i(2, exp.file_dels); + cl_assert_equal_i(3, exp.file_mods); - cl_assert(exp.hunks == 12); + cl_assert_equal_i(12, exp.hunks); - cl_assert(exp.lines == 16); - cl_assert(exp.line_ctxt == 3); - cl_assert(exp.line_adds == 11); - cl_assert(exp.line_dels == 2); + cl_assert_equal_i(16, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(11, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); git_diff_list_free(diff); diff = NULL; @@ -132,7 +132,7 @@ void test_diff_index__1(void) git_diff_foreach(diff, &exp, diff_stop_after_2_files, NULL, NULL) ); - cl_assert(exp.files == 2); + cl_assert_equal_i(2, exp.files); git_diff_list_free(diff); diff = NULL; diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c index be9eb6c1310..3003374a52b 100644 --- a/tests-clar/diff/tree.c +++ b/tests-clar/diff/tree.c @@ -39,17 +39,17 @@ void test_diff_tree__0(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 5); - cl_assert(exp.file_adds == 2); - cl_assert(exp.file_dels == 1); - cl_assert(exp.file_mods == 2); + cl_assert_equal_i(5, exp.files); + cl_assert_equal_i(2, exp.file_adds); + cl_assert_equal_i(1, exp.file_dels); + cl_assert_equal_i(2, exp.file_mods); - cl_assert(exp.hunks == 5); + cl_assert_equal_i(5, exp.hunks); - cl_assert(exp.lines == 7 + 24 + 1 + 6 + 6); - cl_assert(exp.line_ctxt == 1); - cl_assert(exp.line_adds == 24 + 1 + 5 + 5); - cl_assert(exp.line_dels == 7 + 1); + cl_assert_equal_i(7 + 24 + 1 + 6 + 6, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(24 + 1 + 5 + 5, exp.line_adds); + cl_assert_equal_i(7 + 1, exp.line_dels); git_diff_list_free(diff); diff = NULL; @@ -61,17 +61,17 @@ void test_diff_tree__0(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 2); - cl_assert(exp.file_adds == 0); - cl_assert(exp.file_dels == 0); - cl_assert(exp.file_mods == 2); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(2, exp.file_mods); - cl_assert(exp.hunks == 2); + cl_assert_equal_i(2, exp.hunks); - cl_assert(exp.lines == 8 + 15); - cl_assert(exp.line_ctxt == 1); - cl_assert(exp.line_adds == 1); - cl_assert(exp.line_dels == 7 + 14); + cl_assert_equal_i(8 + 15, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(7 + 14, exp.line_dels); git_diff_list_free(diff); @@ -192,17 +192,17 @@ void test_diff_tree__bare(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 3); - cl_assert(exp.file_adds == 2); - cl_assert(exp.file_dels == 0); - cl_assert(exp.file_mods == 1); + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(2, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); - cl_assert(exp.hunks == 3); + cl_assert_equal_i(3, exp.hunks); - cl_assert(exp.lines == 4); - cl_assert(exp.line_ctxt == 0); - cl_assert(exp.line_adds == 3); - cl_assert(exp.line_dels == 1); + cl_assert_equal_i(4, exp.lines); + cl_assert_equal_i(0, exp.line_ctxt); + cl_assert_equal_i(3, exp.line_adds); + cl_assert_equal_i(1, exp.line_dels); git_diff_list_free(diff); git_tree_free(a); @@ -242,17 +242,17 @@ void test_diff_tree__merge(void) cl_git_pass(git_diff_foreach( diff1, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 6); - cl_assert(exp.file_adds == 2); - cl_assert(exp.file_dels == 1); - cl_assert(exp.file_mods == 3); + cl_assert_equal_i(6, exp.files); + cl_assert_equal_i(2, exp.file_adds); + cl_assert_equal_i(1, exp.file_dels); + cl_assert_equal_i(3, exp.file_mods); - cl_assert(exp.hunks == 6); + cl_assert_equal_i(6, exp.hunks); - cl_assert(exp.lines == 59); - cl_assert(exp.line_ctxt == 1); - cl_assert(exp.line_adds == 36); - cl_assert(exp.line_dels == 22); + cl_assert_equal_i(59, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(36, exp.line_adds); + cl_assert_equal_i(22, exp.line_dels); git_diff_list_free(diff1); } diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 801439e3079..eac7eb87dff 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -17,6 +17,7 @@ void test_diff_workdir__to_index(void) git_diff_options opts = {0}; git_diff_list *diff = NULL; diff_expects exp; + int use_iterator; g_repo = cl_git_sandbox_init("status"); @@ -24,33 +25,39 @@ void test_diff_workdir__to_index(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - - /* to generate these values: - * - cd to tests/resources/status, - * - mv .gitted .git - * - git diff --name-status - * - git diff - * - mv .git .gitted - */ - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(4, exp.file_untracked); - - cl_assert_equal_i(8, exp.hunks); - - cl_assert_equal_i(14, exp.lines); - cl_assert_equal_i(5, exp.line_ctxt); - cl_assert_equal_i(4, exp.line_adds); - cl_assert_equal_i(5, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status + * - git diff + * - mv .git .gitted + */ + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(4, exp.file_dels); + cl_assert_equal_i(4, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(4, exp.file_untracked); + + cl_assert_equal_i(8, exp.hunks); + + cl_assert_equal_i(14, exp.lines); + cl_assert_equal_i(5, exp.line_ctxt); + cl_assert_equal_i(4, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + } git_diff_list_free(diff); } @@ -65,6 +72,7 @@ void test_diff_workdir__to_tree(void) git_diff_list *diff = NULL; git_diff_list *diff2 = NULL; diff_expects exp; + int use_iterator; g_repo = cl_git_sandbox_init("status"); @@ -75,8 +83,6 @@ void test_diff_workdir__to_tree(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - memset(&exp, 0, sizeof(exp)); - /* You can't really generate the equivalent of git_diff_workdir_to_tree() * using C git. It really wants to interpose the index into the diff. * @@ -89,15 +95,23 @@ void test_diff_workdir__to_tree(void) */ cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); - cl_assert_equal_i(14, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(5, exp.file_untracked); + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(14, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(4, exp.file_dels); + cl_assert_equal_i(4, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(5, exp.file_untracked); + } /* Since there is no git diff equivalent, let's just assume that the * text diffs produced by git_diff_foreach are accurate here. We will @@ -117,22 +131,30 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_merge(diff, diff2)); git_diff_list_free(diff2); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(15, exp.files); - cl_assert_equal_i(2, exp.file_adds); - cl_assert_equal_i(5, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(3, exp.file_untracked); + cl_assert_equal_i(15, exp.files); + cl_assert_equal_i(2, exp.file_adds); + cl_assert_equal_i(5, exp.file_dels); + cl_assert_equal_i(4, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(3, exp.file_untracked); - cl_assert_equal_i(11, exp.hunks); + cl_assert_equal_i(11, exp.hunks); - cl_assert_equal_i(17, exp.lines); - cl_assert_equal_i(4, exp.line_ctxt); - cl_assert_equal_i(8, exp.line_adds); - cl_assert_equal_i(5, exp.line_dels); + cl_assert_equal_i(17, exp.lines); + cl_assert_equal_i(4, exp.line_ctxt); + cl_assert_equal_i(8, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + } git_diff_list_free(diff); diff = NULL; @@ -146,22 +168,30 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_merge(diff, diff2)); git_diff_list_free(diff2); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); - cl_assert_equal_i(16, exp.files); - cl_assert_equal_i(5, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(3, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(3, exp.file_untracked); + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(12, exp.hunks); + cl_assert_equal_i(16, exp.files); + cl_assert_equal_i(5, exp.file_adds); + cl_assert_equal_i(4, exp.file_dels); + cl_assert_equal_i(3, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(3, exp.file_untracked); - cl_assert_equal_i(19, exp.lines); - cl_assert_equal_i(3, exp.line_ctxt); - cl_assert_equal_i(12, exp.line_adds); - cl_assert_equal_i(4, exp.line_dels); + cl_assert_equal_i(12, exp.hunks); + + cl_assert_equal_i(19, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(12, exp.line_adds); + cl_assert_equal_i(4, exp.line_dels); + } git_diff_list_free(diff); @@ -175,6 +205,7 @@ void test_diff_workdir__to_index_with_pathspec(void) git_diff_list *diff = NULL; diff_expects exp; char *pathspec = NULL; + int use_iterator; g_repo = cl_git_sandbox_init("status"); @@ -184,62 +215,93 @@ void test_diff_workdir__to_index_with_pathspec(void) opts.pathspec.strings = &pathspec; opts.pathspec.count = 1; - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(4, exp.file_untracked); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, NULL, NULL)); + else + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(4, exp.file_dels); + cl_assert_equal_i(4, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(4, exp.file_untracked); + } git_diff_list_free(diff); - memset(&exp, 0, sizeof(exp)); pathspec = "modified_file"; cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(0, exp.file_untracked); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, NULL, NULL)); + else + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(0, exp.file_untracked); + } git_diff_list_free(diff); - memset(&exp, 0, sizeof(exp)); pathspec = "subdir"; cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(1, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(1, exp.file_untracked); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, NULL, NULL)); + else + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(1, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(1, exp.file_untracked); + } git_diff_list_free(diff); - memset(&exp, 0, sizeof(exp)); pathspec = "*_deleted"; cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(2, exp.file_dels); - cl_assert_equal_i(0, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(0, exp.file_untracked); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, NULL, NULL)); + else + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(2, exp.file_dels); + cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(0, exp.file_untracked); + } git_diff_list_free(diff); } @@ -249,6 +311,7 @@ void test_diff_workdir__filemode_changes(void) git_config *cfg; git_diff_list *diff = NULL; diff_expects exp; + int use_iterator; if (!cl_is_chmod_supported()) return; @@ -262,13 +325,20 @@ void test_diff_workdir__filemode_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_mods); - cl_assert_equal_i(0, exp.hunks); + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.hunks); + } git_diff_list_free(diff); @@ -278,13 +348,20 @@ void test_diff_workdir__filemode_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.hunks); + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.hunks); + } git_diff_list_free(diff); @@ -347,6 +424,7 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) diff_expects exp; char *pathspec = "staged_changes_modified_file"; git_tree *tree; + int use_iterator; /* For this file, * - head->index diff has 1 line of context, 1 line of diff @@ -366,46 +444,70 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) cl_git_pass(git_diff_index_to_tree(g_repo, &opts, tree, &diff_i2t)); cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff_w2i)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(2, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(2, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(2, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(2, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } cl_git_pass(git_diff_merge(diff_i2t, diff_w2i)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(2, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(2, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } git_diff_list_free(diff_i2t); git_diff_list_free(diff_w2i); @@ -419,6 +521,7 @@ void test_diff_workdir__eof_newline_changes(void) git_diff_list *diff = NULL; diff_expects exp; char *pathspec = "current_file"; + int use_iterator; g_repo = cl_git_sandbox_init("status"); @@ -427,18 +530,26 @@ void test_diff_workdir__eof_newline_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(0, exp.file_mods); - cl_assert_equal_i(0, exp.hunks); - cl_assert_equal_i(0, exp.lines); - cl_assert_equal_i(0, exp.line_ctxt); - cl_assert_equal_i(0, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + cl_assert_equal_i(0, exp.line_ctxt); + cl_assert_equal_i(0, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } git_diff_list_free(diff); @@ -446,18 +557,26 @@ void test_diff_workdir__eof_newline_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(2, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(2, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } git_diff_list_free(diff); @@ -465,18 +584,26 @@ void test_diff_workdir__eof_newline_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(0, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(2, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(0, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); + } git_diff_list_free(diff); } diff --git a/tests-clar/resources/attr/.gitted/index b/tests-clar/resources/attr/.gitted/index index 943e2243e2fa9414886669bdb45e54ba4d7808ea..439ffb151ef49cb3e655e6cda9eee1a64aeaaa54 100644 GIT binary patch delta 704 zcmX@WcYx2p#WTp6fq{Vuh{gRM@c02~1{lr9z`(+AoKKd4p>YWV1LIeql*mK_Rb}kT z*vwM|;+OX|t`JhH2X!JJ`7a_+*Jw delta 732 zcmX@WcYx2p#WTp6fq{Vuh{gR^w;u-53@{qRhJeN;FxEr^Rb^t7RS>7_08z>&b1)L8 zE`YI=7}rgHL8N&GOu9sw*FdDY3q-0@U`E*cv5y@V_~?-_xd2HWG*%F55s^0e05g%{ z$0DiEQCym&pO%@Eibx3f62#;H7KB^zxnTi|DN$khgIM#}BsV9ro@NwDxak!a^>>$W Qs^O!*Nk$b?YqWj20pOE^!~g&Q diff --git a/tests-clar/resources/issue_592/.gitted/index b/tests-clar/resources/issue_592/.gitted/index index eaeb5d761423e5db70d3d4f32678c61b9e10bbc0..be7a29d99fd23046b1e54532f56586e4920ac30c 100644 GIT binary patch literal 392 zcmZ?q402{*U|<4bR{sY)en6T5M)NT+urM6w`^CV}xP*a$@hebD1c-S*u6>_;nxj*S z(`kGDk)20*ay-3{Gq5J=l~j~~w8QnIne&GzbJ&yhVJ5-!!)T~`{t{&lYYrjz{3psB p_7Vc_;RpJdi0}cLlLU3qd&9h?|8Eo(2_>vuVPIe7nQ(pbQ~+(}YqkIY literal 392 zcmZ?q402{*U|<4bR{sp`L?F%JpQbenL?b}s5(Jxp_v704$)`Cwr8u3o=O5X5lqbj2 z`#1w@qFzZw30N6L=CCL0!%U*c9M&8vxu*o#JqJM^BrAN_OZ1bVF4}YL*3_Wdyt1o3 PEJBL2&BC0e-gW>0HnCg3 diff --git a/tests-clar/resources/status/.gitted/index b/tests-clar/resources/status/.gitted/index index 9a383ec0ccf625ea718079bb8f2ee818bf6bf219..2af99a18301ef69ee362708cb5b54d7d25df50c4 100644 GIT binary patch delta 177 zcmeC+?BG;!@eFciU|?VZVqX6TJbpl$0Y>vNFt9Kj=g*m_a#I|;RKa8s#-BLkiYKcu p;gTzzY{G1bLtpvi7UmBk9>$B!9VV7*TgE#%GgRI76HVXc0suH2Cb<9r delta 177 zcmeC+?BG;!@eFciU|?VZVqSmoG9@6*;CJX@Cy3@_U|?b3j`}cB<)%1Rsc7%XB8)$= z%SC;jtilA6Q^zzimTj^Lvn39F|0cIEe-L3?_3obgI{A-BH1j64TJ0?n_Hbka0G)^_ AfB*mh From 510f1bac6b94ce19459498ae78f87fc4f4552305 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 30 Aug 2012 16:39:05 -0700 Subject: [PATCH 13/72] Fix comments and a minor bug This adds better header comments and also fixes a bug in one of simple APIs that tells the number of lines in the current hunk. --- include/git2/diff.h | 134 +++++++++++++++++++++++++++++++++----------- src/diff_output.c | 7 +-- 2 files changed, 103 insertions(+), 38 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 7ac6994e2bc..d145506177a 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -326,29 +326,119 @@ GIT_EXTERN(int) git_diff_merge( */ /**@{*/ +/** + * Iterate over a diff list issuing callbacks. + * + * This will iterate through all of the files described in a diff. You + * should provide a file callback to learn about each file. + * + * The "hunk" and "line" callbacks are optional, and the text diff of the + * files will only be calculated if they are not NULL. Of course, these + * callbacks will not be invoked for binary files on the diff list or for + * files whose only changed is a file mode change. + * + * Returning a non-zero value from any of the callbacks will terminate + * the iteration and cause this return `GIT_EUSER`. + * + * @param diff A git_diff_list generated by one of the above functions. + * @param cb_data Reference pointer that will be passed to your callbacks. + * @param file_cb Callback function to make per file in the diff. + * @param hunk_cb Optional callback to make per hunk of text diff. This + * callback is called to describe a range of lines in the + * diff. It will not be issued for binary files. + * @param line_cb Optional callback to make per line of diff text. This + * same callback will be made for context lines, added, and + * removed lines, and even for a deleted trailing newline. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_diff_foreach( + git_diff_list *diff, + void *cb_data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb); + /** * Create a diff iterator object that can be used to traverse a diff. + * + * This iterator can be used instead of `git_diff_foreach` in situations + * where callback functions are awkward to use. Because of the way that + * diffs are calculated internally, using an iterator will use somewhat + * more memory than `git_diff_foreach` would. + * + * @param iterator Output parameter of newly created iterator. + * @param diff Diff over which you wish to iterate. + * @return 0 on success, < 0 on error */ GIT_EXTERN(int) git_diff_iterator_new( git_diff_iterator **iterator, git_diff_list *diff); -GIT_EXTERN(void) git_diff_iterator_free(git_diff_iterator *iter); +/** + * Release the iterator object. + * + * Call this when you are done using the iterator. + * + * @param iterator The diff iterator to be freed. + */ +GIT_EXTERN(void) git_diff_iterator_free(git_diff_iterator *iterator); /** * Return the number of files in the diff. + * + * Note that there is an uncommon scenario where this number might be too + * high -- if a file in the working directory has been "touched" on disk but + * the contents were then reverted, it might have been added to the + * `git_diff_list` as a MODIFIED file along with a note that the status + * needs to be confirmed when the file contents are loaded into memory. In + * that case, when the file is loaded, we will check the contents and might + * switch it back to UNMODIFIED. The loading of the file is deferred until + * as late as possible. As a result, this might return a value what was too + * high in those circumstances. + * + * This is true of `git_diff_foreach` as well, but the only implication + * there is that the `progress` value would not advance evenly. + * + * @param iterator The iterator object + * @return The maximum number of files to be iterated over */ GIT_EXTERN(int) git_diff_iterator_num_files(git_diff_iterator *iterator); +/** + * Return the number of hunks in the current file + * + * This will return the number of diff hunks in the current file. If the + * diff has not been performed yet, this may result in loading the file and + * performing the diff. + * + * @param iterator The iterator object + * @return The number of hunks in the current file or <0 on loading failure + */ GIT_EXTERN(int) git_diff_iterator_num_hunks_in_file(git_diff_iterator *iterator); +/** + * Return the number of lines in the hunk currently being examined. + * + * This will return the number of lines in the current hunk. If the diff + * has not been performed yet, this may result in loading the file and + * performing the diff. + * + * @param iterator The iterator object + * @return The number of lines in the current hunk (context, added, and + * removed all added together) or <0 on loading failure + */ GIT_EXTERN(int) git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iterator); /** * Return the delta information for the next file in the diff. * * This will return a pointer to the next git_diff_delta` to be processed or - * NULL if the iterator is at the end of the diff, then advance. + * NULL if the iterator is at the end of the diff, then advance. This + * returns the value `GIT_ITEROVER` after processing the last file. + * + * @param delta Output parameter for the next delta object + * @param iterator The iterator object + * @return 0 on success, GIT_ITEROVER when done, other value < 0 on error */ GIT_EXTERN(int) git_diff_iterator_next_file( git_diff_delta **delta, @@ -364,6 +454,10 @@ GIT_EXTERN(int) git_diff_iterator_next_file( * actual text diff will be computed (it cannot be computed incrementally) * so the first call for a new file is expensive (at least in relative * terms - in reality, it is still pretty darn fast). + * + * @param iterator The iterator object + * @return 0 on success, GIT_ITEROVER when done with current file, other + * value < 0 on error */ GIT_EXTERN(int) git_diff_iterator_next_hunk( git_diff_range **range, @@ -373,6 +467,10 @@ GIT_EXTERN(int) git_diff_iterator_next_hunk( /** * Return the next line of the current hunk of diffs. + * + * @param iterator The iterator object + * @return 0 on success, GIT_ITEROVER when done with current hunk, other + * value < 0 on error */ GIT_EXTERN(int) git_diff_iterator_next_line( char *line_origin, /**< GIT_DIFF_LINE_... value from above */ @@ -380,38 +478,6 @@ GIT_EXTERN(int) git_diff_iterator_next_line( size_t *content_len, git_diff_iterator *iterator); -/** - * Iterate over a diff list issuing callbacks. - * - * This will iterate through all of the files described in a diff. You - * should provide a file callback to learn about each file. - * - * The "hunk" and "line" callbacks are optional, and the text diff of the - * files will only be calculated if they are not NULL. Of course, these - * callbacks will not be invoked for binary files on the diff list or for - * files whose only changed is a file mode change. - * - * Returning a non-zero value from any of the callbacks will terminate - * the iteration and cause this return `GIT_EUSER`. - * - * @param diff A git_diff_list generated by one of the above functions. - * @param cb_data Reference pointer that will be passed to your callbacks. - * @param file_cb Callback function to make per file in the diff. - * @param hunk_cb Optional callback to make per hunk of text diff. This - * callback is called to describe a range of lines in the - * diff. It will not be issued for binary files. - * @param line_cb Optional callback to make per line of diff text. This - * same callback will be made for context lines, added, and - * removed lines, and even for a deleted trailing newline. - * @return 0 on success, GIT_EUSER on non-zero callback, or error code - */ -GIT_EXTERN(int) git_diff_foreach( - git_diff_list *diff, - void *cb_data, - git_diff_file_fn file_cb, - git_diff_hunk_fn hunk_cb, - git_diff_data_fn line_cb); - /** * Iterate over a diff generating text output like "git diff --name-status". * diff --git a/src/diff_output.c b/src/diff_output.c index 69921741c45..d715f9ef448 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -1023,7 +1023,6 @@ struct git_diff_iterator { diffiter_hunk *hunk_curr; char hunk_header[128]; git_pool lines; - size_t line_count; diffiter_line *line_curr; }; @@ -1096,7 +1095,6 @@ static int diffiter_line_cb( line->len = content_len; info->last_hunk->line_count++; - iter->line_count++; if (info->last_hunk->line_head == NULL) info->last_hunk->line_head = line; @@ -1136,7 +1134,6 @@ static void diffiter_do_unload_file(git_diff_iterator *iter) iter->ctxt.delta = NULL; iter->hunk_head = NULL; iter->hunk_count = 0; - iter->line_count = 0; } int git_diff_iterator_new( @@ -1202,7 +1199,9 @@ int git_diff_iterator_num_hunks_in_file(git_diff_iterator *iter) int git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iter) { int error = diffiter_do_diff_file(iter); - return (error != 0) ? error : (int)iter->line_count; + if (!error && iter->hunk_curr) + error = iter->hunk_curr->line_count; + return error; } int git_diff_iterator_next_file( From fed886d9903e377996d7d5f7a7e3f558e4f2b78a Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 5 Sep 2012 15:54:32 -0700 Subject: [PATCH 14/72] Test for gitmodules only submodule def This should confirm that issue #835 is fixed where a submodule that is only declared in the .gitmodules file was not accessible via the submodule APIs. --- tests-clar/resources/submod2/gitmodules | 3 +++ tests-clar/submodule/lookup.c | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests-clar/resources/submod2/gitmodules b/tests-clar/resources/submod2/gitmodules index 7b150b18927..4c31108edb2 100644 --- a/tests-clar/resources/submod2/gitmodules +++ b/tests-clar/resources/submod2/gitmodules @@ -19,3 +19,6 @@ [submodule "sm_added_and_uncommited"] path = sm_added_and_uncommited url = ../submod2_target +[submodule "sm_gitmodules_only"] + path = sm_gitmodules_only + url = ../submod2_target diff --git a/tests-clar/submodule/lookup.c b/tests-clar/submodule/lookup.c index 669338f1c7c..94eb19b5e53 100644 --- a/tests-clar/submodule/lookup.c +++ b/tests-clar/submodule/lookup.c @@ -34,6 +34,10 @@ void test_submodule_lookup__simple_lookup(void) cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); cl_assert(sm); + /* lookup pending change in .gitmodules that is neither in HEAD nor index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); + cl_assert(sm); + /* lookup git repo subdir that is not added as submodule */ cl_assert(git_submodule_lookup(&sm, g_repo, "not_submodule") == GIT_EEXISTS); @@ -106,5 +110,5 @@ void test_submodule_lookup__foreach(void) sm_lookup_data data; memset(&data, 0, sizeof(data)); cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); - cl_assert_equal_i(7, data.count); + cl_assert_equal_i(8, data.count); } From 01ae1909c59951f2d4f7955090ce7590e62662e8 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Thu, 6 Sep 2012 10:13:38 +0200 Subject: [PATCH 15/72] diff: Cleanup documentation and printf compat --- include/git2/diff.h | 13 ++++++++++++- src/diff_output.c | 8 ++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index d145506177a..85bb308dda8 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -455,6 +455,11 @@ GIT_EXTERN(int) git_diff_iterator_next_file( * so the first call for a new file is expensive (at least in relative * terms - in reality, it is still pretty darn fast). * + * @param range Pointer where to store the range for the hunk + * @param header Pointer where to store the header for the chunk; + * this string is owned by the library and should not be freed by + * the user + * @param header_len Pointer where to store the length of the returned header * @param iterator The iterator object * @return 0 on success, GIT_ITEROVER when done with current file, other * value < 0 on error @@ -468,8 +473,14 @@ GIT_EXTERN(int) git_diff_iterator_next_hunk( /** * Return the next line of the current hunk of diffs. * + * @param line_origin Pointer where to store a GIT_DIFF_LINE_ value; + * this value is a single character, not a buffer + * @param content Pointer where to store the content of the line; + * this string is owned by the library and should not be freed by + * the user + * @param Pointer where to store the length of the returned content * @param iterator The iterator object - * @return 0 on success, GIT_ITEROVER when done with current hunk, other + * @return 0 on success, GIT_ITEROVER when done with current line, other * value < 0 on error */ GIT_EXTERN(int) git_diff_iterator_next_line( diff --git a/src/diff_output.c b/src/diff_output.c index d715f9ef448..2c64b92eebb 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -89,21 +89,21 @@ static int format_hunk_header(char *header, size_t len, git_diff_range *range) { if (range->old_lines != 1) { if (range->new_lines != 1) - return snprintf( + return p_snprintf( header, len, "@@ -%d,%d +%d,%d @@", range->old_start, range->old_lines, range->new_start, range->new_lines); else - return snprintf( + return p_snprintf( header, len, "@@ -%d,%d +%d @@", range->old_start, range->old_lines, range->new_start); } else { if (range->new_lines != 1) - return snprintf( + return p_snprintf( header, len, "@@ -%d +%d,%d @@", range->old_start, range->new_start, range->new_lines); else - return snprintf( + return p_snprintf( header, len, "@@ -%d +%d @@", range->old_start, range->new_start); } From 0e9f2fcef6955a9c15f216ad78eec538cc97a8f3 Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Thu, 6 Sep 2012 11:35:09 +0200 Subject: [PATCH 16/72] odb: mark unused variable --- src/odb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/odb.c b/src/odb.c index 55d434a8f2e..34033d15c70 100644 --- a/src/odb.c +++ b/src/odb.c @@ -710,6 +710,7 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi void * git_odb_backend_malloc(git_odb_backend *backend, size_t len) { + GIT_UNUSED(backend); return git__malloc(len); } From 316659489a97e8e93f88dd3610320c8ae5b35e4a Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 24 Aug 2012 21:30:45 +0200 Subject: [PATCH 17/72] refs: introduce git_reference_peel() Fix #530 --- include/git2/refs.h | 20 ++++++++++ src/refs.c | 47 ++++++++++++++++++++++ tests-clar/refs/peel.c | 91 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 tests-clar/refs/peel.c diff --git a/include/git2/refs.h b/include/git2/refs.h index 660b48b5f19..73b32a9e2b2 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -434,6 +434,26 @@ GIT_EXTERN(int) git_reference_normalize_name( const char *name, unsigned int flags); +/** + * Recursively peel an reference until an object of the + * specified type is met. + * + * The retrieved `peeled` object is owned by the repository + * and should be closed with the `git_object_free` method. + * + * If you pass `GIT_OBJ_ANY` as the target type, then the object + * will be peeled until a non-tag object is met. + * + * @param peeled Pointer to the peeled git_object + * @param ref The reference to be processed + * @param target_type The type of the requested object + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reference_peel( + git_object **out, + git_reference *ref, + git_otype type); + /** @} */ GIT_END_DECL #endif diff --git a/src/refs.c b/src/refs.c index 211a5870cc8..cdf3cb96ec4 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1844,3 +1844,50 @@ int git_reference_is_remote(git_reference *ref) assert(ref); return git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) == 0; } + +static int peel_error(int error, git_reference *ref, const char* msg) +{ + giterr_set( + GITERR_INVALID, + "The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg); + return error; +} + +static int reference_target(git_object **object, git_reference *ref) +{ + const git_oid *oid; + + oid = git_reference_oid(ref); + + return git_object_lookup(object, git_reference_owner(ref), oid, GIT_OBJ_ANY); +} + +int git_reference_peel( + git_object **peeled, + git_reference *ref, + git_otype target_type) +{ + git_reference *resolved = NULL; + git_object *target = NULL; + int error; + + assert(ref); + + if ((error = git_reference_resolve(&resolved, ref)) < 0) + return peel_error(error, ref, "Cannot resolve reference"); + + if ((error = reference_target(&target, resolved)) < 0) { + peel_error(error, ref, "Cannot retrieve reference target"); + goto cleanup; + } + + if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG) + error = git_object__dup(peeled, target); + else + error = git_object_peel(peeled, target, target_type); + +cleanup: + git_object_free(target); + git_reference_free(resolved); + return error; +} diff --git a/tests-clar/refs/peel.c b/tests-clar/refs/peel.c new file mode 100644 index 00000000000..35a290b2e15 --- /dev/null +++ b/tests-clar/refs/peel.c @@ -0,0 +1,91 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo; + +void test_refs_peel__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_refs_peel__cleanup(void) +{ + git_repository_free(g_repo); +} + +static void assert_peel( + const char *ref_name, + git_otype requested_type, + const char* expected_sha, + git_otype expected_type) +{ + git_oid expected_oid; + git_reference *ref; + git_object *peeled; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + + cl_git_pass(git_reference_peel(&peeled, ref, requested_type)); + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled))); + + cl_assert_equal_i(expected_type, git_object_type(peeled)); + + git_object_free(peeled); + git_reference_free(ref); +} + +static void assert_peel_error(int error, const char *ref_name, git_otype requested_type) +{ + git_reference *ref; + git_object *peeled; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + + cl_assert_equal_i(error, git_reference_peel(&peeled, ref, requested_type)); + + git_reference_free(ref); +} + +void test_refs_peel__can_peel_a_tag(void) +{ + assert_peel("refs/tags/test", GIT_OBJ_TAG, + "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", GIT_OBJ_TAG); + assert_peel("refs/tags/test", GIT_OBJ_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + assert_peel("refs/tags/test", GIT_OBJ_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); + assert_peel("refs/tags/point_to_blob", GIT_OBJ_BLOB, + "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJ_BLOB); +} + +void test_refs_peel__can_peel_a_branch(void) +{ + assert_peel("refs/heads/master", GIT_OBJ_COMMIT, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT); + assert_peel("refs/heads/master", GIT_OBJ_TREE, + "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJ_TREE); +} + +void test_refs_peel__can_peel_a_symbolic_reference(void) +{ + assert_peel("HEAD", GIT_OBJ_COMMIT, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT); + assert_peel("HEAD", GIT_OBJ_TREE, + "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJ_TREE); +} + +void test_refs_peel__cannot_peel_into_a_non_existing_target(void) +{ + assert_peel_error(GIT_ERROR, "refs/tags/point_to_blob", GIT_OBJ_TAG); +} + +void test_refs_peel__can_peel_into_any_non_tag_object(void) +{ + assert_peel("refs/heads/master", GIT_OBJ_ANY, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT); + assert_peel("refs/tags/point_to_blob", GIT_OBJ_ANY, + "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJ_BLOB); + assert_peel("refs/tags/test", GIT_OBJ_ANY, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); +} From bb2d305c20d62b10b39d95916d1a172057c26d65 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 22 Aug 2012 10:47:25 +0200 Subject: [PATCH 18/72] errors: introduce GIT_EBAREREPO --- include/git2/errors.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/git2/errors.h b/include/git2/errors.h index f6671c49d71..e5f435926ce 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -26,6 +26,7 @@ enum { GIT_EAMBIGUOUS = -5, GIT_EBUFS = -6, GIT_EUSER = -7, + GIT_EBAREREPO = -8, GIT_PASSTHROUGH = -30, GIT_ITEROVER = -31, From ced8d1420a76c13796d951203c2b35540a49b454 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 22 Aug 2012 11:30:55 +0200 Subject: [PATCH 19/72] errors: deploy GIT_EBAREREPO usage --- src/blob.c | 4 +++- src/iterator.c | 7 ++----- src/repository.h | 15 +++++++++++++++ src/reset.c | 5 +++-- tests-clar/reset/mixed.c | 2 +- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/blob.c b/src/blob.c index 699adec6b7f..5a4a26bfa85 100644 --- a/src/blob.c +++ b/src/blob.c @@ -212,8 +212,10 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat const char *workdir; int error; + if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0) + return error; + workdir = git_repository_workdir(repo); - assert(workdir); /* error to call this on bare repo */ if (git_buf_joinpath(&full_path, workdir, path) < 0) { git_buf_free(&full_path); diff --git a/src/iterator.c b/src/iterator.c index 92fe6713429..e30e112203d 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -659,11 +659,8 @@ int git_iterator_for_workdir_range( assert(iter && repo); - if (git_repository_is_bare(repo)) { - giterr_set(GITERR_INVALID, - "Cannot scan working directory for bare repo"); - return -1; - } + if ((error = git_repository__ensure_not_bare(repo, "scan working directory")) < 0) + return error; ITERATOR_BASE_INIT(wi, workdir, WORKDIR); diff --git a/src/repository.h b/src/repository.h index 4695edf3a6c..4aa8af2928a 100644 --- a/src/repository.h +++ b/src/repository.h @@ -149,4 +149,19 @@ void git_repository__cvar_cache_clear(git_repository *repo); */ extern void git_submodule_config_free(git_repository *repo); +GIT_INLINE(int) git_repository__ensure_not_bare( + git_repository *repo, + const char *operation_name) +{ + if (!git_repository_is_bare(repo)) + return 0; + + giterr_set( + GITERR_REPOSITORY, + "Cannot %s. This operation is not allowed against bare repositories.", + operation_name); + + return GIT_EBAREREPO; +} + #endif diff --git a/src/reset.c b/src/reset.c index f9e16f7c678..5aaf9484003 100644 --- a/src/reset.c +++ b/src/reset.c @@ -34,8 +34,9 @@ int git_reset( if (git_object_owner(target) != repo) return reset_error_invalid("The given target does not belong to this repository."); - if (reset_type == GIT_RESET_MIXED && git_repository_is_bare(repo)) - return reset_error_invalid("Mixed reset is not allowed in a bare repository."); + if (reset_type == GIT_RESET_MIXED + && git_repository__ensure_not_bare(repo, "reset mixed") < 0) + return GIT_EBAREREPO; if (git_object_peel(&commit, target, GIT_OBJ_COMMIT) < 0) { reset_error_invalid("The given target does not resolve to a commit"); diff --git a/tests-clar/reset/mixed.c b/tests-clar/reset/mixed.c index 7cfff65d48e..d5f8e10c5ce 100644 --- a/tests-clar/reset/mixed.c +++ b/tests-clar/reset/mixed.c @@ -27,7 +27,7 @@ void test_reset_mixed__cannot_reset_in_a_bare_repository(void) retrieve_target_from_oid(&target, bare, KNOWN_COMMIT_IN_BARE_REPO); - cl_git_fail(git_reset(bare, target, GIT_RESET_MIXED)); + cl_assert_equal_i(GIT_EBAREREPO, git_reset(bare, target, GIT_RESET_MIXED)); git_repository_free(bare); } From 35d2e449bd6291c97bb8075f5976104c9ad57236 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 20 Aug 2012 11:26:02 +0200 Subject: [PATCH 20/72] checkout: cleanup misplaced declaration --- src/checkout.c | 6 ------ src/clone.c | 7 ------- 2 files changed, 13 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index 88df2128db4..d1720fcf370 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -22,9 +22,6 @@ #include "filter.h" #include "blob.h" -GIT_BEGIN_DECL - - typedef struct tree_walk_data { git_indexer_stats *stats; @@ -226,6 +223,3 @@ int git_checkout_reference(git_reference *ref, git_reference_free(head); return retcode; } - - -GIT_END_DECL diff --git a/src/clone.c b/src/clone.c index 33953d7a09a..e06e9ada8b2 100644 --- a/src/clone.c +++ b/src/clone.c @@ -26,8 +26,6 @@ #include "refs.h" #include "path.h" -GIT_BEGIN_DECL - struct HeadInfo { git_repository *repo; git_oid remote_head_oid; @@ -247,8 +245,3 @@ int git_clone(git_repository **out, return retcode; } - - - - -GIT_END_DECL From 746642a6b3bb31e6d6adf67298044f9864e1eb42 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 20 Aug 2012 12:30:54 +0200 Subject: [PATCH 21/72] checkout: fix documentation code alignment --- include/git2/checkout.h | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index ac31b3462d3..deb82872248 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -40,13 +40,13 @@ typedef struct git_checkout_opts { * @param repo repository to check out (must be non-bare) * @param opts specifies checkout options (may be NULL) * @param stats structure through which progress information is reported - * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error) + * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information + * about the error) */ -GIT_EXTERN(int) git_checkout_head(git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats); - - +GIT_EXTERN(int) git_checkout_head( + git_repository *repo, + git_checkout_opts *opts, + git_indexer_stats *stats); /** * Updates files in the working tree to match a commit pointed to by a ref. @@ -54,11 +54,13 @@ GIT_EXTERN(int) git_checkout_head(git_repository *repo, * @param ref reference to follow to a commit * @param opts specifies checkout options (may be NULL) * @param stats structure through which progress information is reported - * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error) + * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information + * about the error) */ -GIT_EXTERN(int) git_checkout_reference(git_reference *ref, - git_checkout_opts *opts, - git_indexer_stats *stats); +GIT_EXTERN(int) git_checkout_reference( + git_reference *ref, + git_checkout_opts *opts, + git_indexer_stats *stats); /** @} */ From cf4c43abaa2d8dace6d70e21c23f7d779a9ad473 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Tue, 4 Sep 2012 11:17:46 +0200 Subject: [PATCH 22/72] object: make git_object_peel() test more readable --- tests-clar/object/peel.c | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/tests-clar/object/peel.c b/tests-clar/object/peel.c index f4ea1eb0fa6..f748be7f435 100644 --- a/tests-clar/object/peel.c +++ b/tests-clar/object/peel.c @@ -12,7 +12,11 @@ void test_object_peel__cleanup(void) git_repository_free(g_repo); } -static void assert_peel(const char* expected_sha, const char *sha, git_otype requested_type) +static void assert_peel( + const char *sha, + git_otype requested_type, + const char* expected_sha, + git_otype expected_type) { git_oid oid, expected_oid; git_object *obj; @@ -26,6 +30,8 @@ static void assert_peel(const char* expected_sha, const char *sha, git_otype req cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled))); + cl_assert_equal_i(expected_type, git_object_type(peeled)); + git_object_free(peeled); git_object_free(obj); } @@ -46,21 +52,28 @@ static void assert_peel_error(int error, const char *sha, git_otype requested_ty void test_object_peel__peeling_an_object_into_its_own_type_returns_another_instance_of_it(void) { - assert_peel("e90810b8df3e80c413d903f631643c716887138d", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); - assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG); - assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); - assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB); + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG, + "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG); + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); + assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB, + "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB); } void test_object_peel__can_peel_a_tag(void) { - assert_peel("e90810b8df3e80c413d903f631643c716887138d", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_COMMIT); - assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TREE); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); } void test_object_peel__can_peel_a_commit(void) { - assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_TREE); + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); } void test_object_peel__cannot_peel_a_tree(void) @@ -76,10 +89,12 @@ void test_object_peel__cannot_peel_a_blob(void) void test_object_peel__target_any_object_for_type_change(void) { /* tag to commit */ - assert_peel("e90810b8df3e80c413d903f631643c716887138d", "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_ANY); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_ANY, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); /* commit to tree */ - assert_peel("53fc32d17276939fc79ed05badaef2db09990016", "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_ANY); + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_ANY, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); /* fail to peel tree */ assert_peel_error(GIT_ERROR, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_ANY); From 52462e1ccecdea86cceae9d25bf343265831bbaf Mon Sep 17 00:00:00 2001 From: pontusm Date: Sun, 13 May 2012 10:11:13 +0200 Subject: [PATCH 23/72] Test case to reproduce issue #690. Staged file status does not handle CRLF correctly. Ensures that the test repo has core.autocrlf=true for the test to fail. --- tests-clar/status/worktree.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 75975c98898..d4ae48b8df8 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -797,3 +797,30 @@ void test_status_worktree__interruptable_foreach(void) cl_assert_equal_i(8, count); } + +void test_status_worktree__new_staged_file_must_handle_crlf(void) +{ + git_repository *repo; + git_index *index; + git_config *config; + unsigned int status; + + cl_git_pass(git_repository_init(&repo, "getting_started", 0)); + + // Ensure that repo has core.autocrlf=true + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "core.autocrlf", true)); + + cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); // Content with CRLF + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add(index, "testfile.txt", 0)); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_file(&status, repo, "testfile.txt")); + cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); + + git_config_free(config); + git_index_free(index); + git_repository_free(repo); +} From f8e2cc9a0a59dc87f8e8842b6818f3df180fffda Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 31 Aug 2012 15:53:47 -0700 Subject: [PATCH 24/72] Alternate test for autocrlf with status I couldn't get the last failing test to actually fail. This is a different test suggested by @nulltoken which should fail. --- src/crlf.c | 2 +- tests-clar/status/worktree.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/crlf.c b/src/crlf.c index 509e5589709..1b6898ba6b5 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -276,7 +276,7 @@ static int find_and_add_filter(git_vector *filters, git_repository *repo, const /* * Use the core Git logic to see if we should perform CRLF for this file - * based on its attributes & the value of `core.auto_crlf` + * based on its attributes & the value of `core.autocrlf` */ ca.crlf_action = crlf_input_action(&ca); diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index d4ae48b8df8..c0412ef96b2 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -824,3 +824,24 @@ void test_status_worktree__new_staged_file_must_handle_crlf(void) git_index_free(index); git_repository_free(repo); } + +void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_config *config; + unsigned int status; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "core.autocrlf", true)); + git_config_free(config); + + cl_git_rewritefile("status/current_file", "current_file\r\n"); + + cl_git_pass(git_status_file(&status, repo, "current_file")); + +#ifdef GIT_WIN32 + cl_assert_equal_i(GIT_STATUS_CURRENT, status); +#else + cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); +#endif +} From 8f9b6a132b358b23b518197240184e2f08e0a913 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 31 Aug 2012 16:39:30 -0700 Subject: [PATCH 25/72] Better header comments --- include/git2/diff.h | 58 ++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 85bb308dda8..2898f3b2020 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -386,18 +386,19 @@ GIT_EXTERN(void) git_diff_iterator_free(git_diff_iterator *iterator); /** * Return the number of files in the diff. * - * Note that there is an uncommon scenario where this number might be too - * high -- if a file in the working directory has been "touched" on disk but - * the contents were then reverted, it might have been added to the - * `git_diff_list` as a MODIFIED file along with a note that the status - * needs to be confirmed when the file contents are loaded into memory. In - * that case, when the file is loaded, we will check the contents and might - * switch it back to UNMODIFIED. The loading of the file is deferred until - * as late as possible. As a result, this might return a value what was too - * high in those circumstances. - * - * This is true of `git_diff_foreach` as well, but the only implication - * there is that the `progress` value would not advance evenly. + * NOTE: This number has to be treated as an upper bound on the number of + * files that have changed if the diff is with the working directory. + * + * Why?! For efficiency, we defer loading the file contents as long as + * possible, so if a file has been "touched" in the working directory and + * then reverted to the original content, it may get stored in the diff list + * as MODIFIED along with a flag that the status should be reconfirmed when + * it is actually loaded into memory. When that load happens, it could get + * flipped to UNMODIFIED. If unmodified files are being skipped, then the + * iterator will skip that file and this number may be too high. + * + * This behavior is true of `git_diff_foreach` as well, but the only + * implication there is that the `progress` value would not advance evenly. * * @param iterator The iterator object * @return The maximum number of files to be iterated over @@ -450,16 +451,19 @@ GIT_EXTERN(int) git_diff_iterator_next_file( * It is recommended that you not call this if the file is a binary * file, but it is allowed to do so. * - * Warning! Call this function for the first time on a file is when the + * The `header` text output will contain the standard hunk header that + * would appear in diff output. The header string will be NUL terminated. + * + * WARNING! Call this function for the first time on a file is when the * actual text diff will be computed (it cannot be computed incrementally) * so the first call for a new file is expensive (at least in relative * terms - in reality, it is still pretty darn fast). * - * @param range Pointer where to store the range for the hunk - * @param header Pointer where to store the header for the chunk; - * this string is owned by the library and should not be freed by - * the user - * @param header_len Pointer where to store the length of the returned header + * @param range Output pointer to range of lines covered by the hunk; + * This range object is owned by the library and should not be freed. + * @param header Output pointer to the text of the hunk header + * This string is owned by the library and should not be freed. + * @param header_len Output pointer to store the length of the header text * @param iterator The iterator object * @return 0 on success, GIT_ITEROVER when done with current file, other * value < 0 on error @@ -473,12 +477,18 @@ GIT_EXTERN(int) git_diff_iterator_next_hunk( /** * Return the next line of the current hunk of diffs. * - * @param line_origin Pointer where to store a GIT_DIFF_LINE_ value; - * this value is a single character, not a buffer - * @param content Pointer where to store the content of the line; - * this string is owned by the library and should not be freed by - * the user - * @param Pointer where to store the length of the returned content + * The `line_origin` output will tell you what type of line this is + * (e.g. was it added or removed or is it just context for the diff). + * + * The `content` will be a pointer to the file data that goes in the + * line. IT WILL NOT BE NUL TERMINATED. You have to use the `content_len` + * value and only process that many bytes of data from the content string. + * + * @param line_origin Output pointer to store a GIT_DIFF_LINE value for this + * next chunk of data. The value is a single character, not a buffer. + * @param content Output pointer to store the content of the diff; this + * string is owned by the library and should not be freed. + * @param content_len Output pointer to store the length of the content. * @param iterator The iterator object * @return 0 on success, GIT_ITEROVER when done with current line, other * value < 0 on error From 60b9d3fcef04a6beb0ad4df225ada058afabf0b9 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Wed, 5 Sep 2012 15:00:40 -0700 Subject: [PATCH 26/72] Implement filters for status/diff blobs This adds support to diff and status for running filters (a la crlf) on blobs in the workdir before computing SHAs and before generating text diffs. This ended up being a bit more code change than I had thought since I had to reorganize some of the diff logic to minimize peak memory use when filtering blobs in a diff. This also adds a cap on the maximum size of data that will be loaded to diff. I set it at 512Mb which should match core git. Right now it is a #define in src/diff.h but it could be moved into the public API if desired. --- src/diff.c | 33 ++++-- src/diff.h | 2 + src/diff_output.c | 214 +++++++++++++++++++++++++---------- src/fileops.c | 62 ++++++---- src/fileops.h | 1 + src/odb.c | 33 +++++- src/odb.h | 17 ++- tests-clar/status/worktree.c | 4 - 8 files changed, 260 insertions(+), 106 deletions(-) diff --git a/src/diff.c b/src/diff.c index f8a01086ca7..499b95b44ee 100644 --- a/src/diff.c +++ b/src/diff.c @@ -11,6 +11,7 @@ #include "fileops.h" #include "config.h" #include "attr_file.h" +#include "filter.h" static char *diff_prefix_from_pathspec(const git_strarray *pathspec) { @@ -63,8 +64,8 @@ static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) git_vector_foreach(&diff->pathspec, i, match) { int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0; - - if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) && + + if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) && result == FNM_NOMATCH) result = p_fnmatch(match->pattern, path, 0); @@ -262,12 +263,14 @@ static int diff_delta__from_two( delta = diff_delta__alloc(diff, status, old_entry->path); GITERR_CHECK_ALLOC(delta); - delta->old_file.mode = old_mode; git_oid_cpy(&delta->old_file.oid, &old_entry->oid); + delta->old_file.size = old_entry->file_size; + delta->old_file.mode = old_mode; delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; - delta->new_file.mode = new_mode; git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid); + delta->new_file.size = new_entry->file_size; + delta->new_file.mode = new_mode; if (new_oid || !git_oid_iszero(&new_entry->oid)) delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; @@ -440,14 +443,22 @@ static int oid_for_workdir_item( giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); result = -1; } else { - int fd = git_futils_open_ro(full_path.ptr); - if (fd < 0) - result = fd; - else { - result = git_odb__hashfd( - oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB); - p_close(fd); + git_vector filters = GIT_VECTOR_INIT; + + result = git_filters_load( + &filters, repo, item->path, GIT_FILTER_TO_ODB); + if (result >= 0) { + int fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) + result = fd; + else { + result = git_odb__hashfd_filtered( + oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB, &filters); + p_close(fd); + } } + + git_filters_free(&filters); } git_buf_free(&full_path); diff --git a/src/diff.h b/src/diff.h index 2785fa425b8..def74632366 100644 --- a/src/diff.h +++ b/src/diff.h @@ -25,6 +25,8 @@ enum { GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ }; +#define MAX_DIFF_FILESIZE 0x20000000 + struct git_diff_list { git_refcount rc; git_repository *repo; diff --git a/src/diff_output.c b/src/diff_output.c index 2c64b92eebb..e2ca8cf3ea1 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -22,7 +22,18 @@ * git_diff_foreach() call it is an emphemeral structure that is filled * in to execute each diff. In the case of a git_diff_iterator, it holds * most of the information for the diff in progress. - */ + * + * As each delta is processed, it goes through 3 phases: prep, load, exec. + * + * - In the prep phase, we just set the delta and quickly check the file + * attributes to see if it should be treated as binary. + * - In the load phase, we actually load the file content into memory. + * At this point, if we had deferred calculating OIDs, we might have to + * correct the delta to be UNMODIFIED. + * - In the exec phase, we actually run the diff and execute the callbacks. + * For foreach, this is just a pass-through to the user's callbacks. For + * iterators, we record the hunks and data spans into memory. + */ typedef struct { git_repository *repo; git_diff_options *opts; @@ -263,18 +274,40 @@ static void setup_xdiff_options( static int get_blob_content( git_repository *repo, - const git_oid *oid, + git_diff_file *file, git_map *map, git_blob **blob) { - if (git_oid_iszero(oid)) + int error; + git_odb *odb; + size_t len; + git_otype type; + + if (git_oid_iszero(&file->oid)) return 0; - if (git_blob_lookup(blob, repo, oid) < 0) - return -1; + /* peek at object header to avoid loading if too large */ + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + (error = git_odb_read_header(&len, &type, odb, &file->oid)) < 0) + return error; + + assert(type == GIT_OBJ_BLOB); + + /* if blob is too large to diff, mark as binary */ + if (len > MAX_DIFF_FILESIZE) { + file->flags |= GIT_DIFF_FILE_BINARY; + return 0; + } + + if (!file->size) + file->size = len; + + if ((error = git_blob_lookup(blob, repo, &file->oid)) < 0) + return error; map->data = (void *)git_blob_rawcontent(*blob); map->len = git_blob_rawsize(*blob); + return 0; } @@ -307,13 +340,66 @@ static int get_workdir_content( if (read_len < 0) { giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path); error = -1; - } else - map->len = read_len; + goto cleanup; + } + + map->len = read_len; } else { - error = git_futils_mmap_ro_file(map, path.ptr); - file->flags |= GIT_DIFF_FILE_UNMAP_DATA; + git_file fd = git_futils_open_ro(path.ptr); + git_vector filters = GIT_VECTOR_INIT; + + if (fd < 0) { + error = fd; + goto cleanup; + } + + if (!file->size) + file->size = git_futils_filesize(fd); + + /* if file is too large to diff, mark as binary */ + if (file->size > MAX_DIFF_FILESIZE) { + file->flags |= GIT_DIFF_FILE_BINARY; + goto close_and_cleanup; + } + + error = git_filters_load(&filters, repo, file->path, GIT_FILTER_TO_ODB); + if (error < 0) + goto close_and_cleanup; + + if (error == 0) { /* note: git_filters_load returns filter count */ + error = git_futils_mmap_ro(map, fd, 0, (size_t)file->size); + file->flags |= GIT_DIFF_FILE_UNMAP_DATA; + } else { + git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT; + + if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) && + !(error = git_filters_apply(&filtered, &raw, &filters))) + { + map->len = git_buf_len(&filtered); + map->data = git_buf_detach(&filtered); + + file->flags |= GIT_DIFF_FILE_FREE_DATA; + } + + git_buf_free(&raw); + git_buf_free(&filtered); + } + +close_and_cleanup: + git_filters_free(&filters); + p_close(fd); + } + + /* once data is loaded, update OID if we didn't have it previously */ + if (!error && (file->flags & GIT_DIFF_FILE_VALID_OID) == 0) { + error = git_odb_hash( + &file->oid, map->data, map->len, GIT_OBJ_BLOB); + if (!error) + file->flags |= GIT_DIFF_FILE_VALID_OID; } + +cleanup: git_buf_free(&path); return error; } @@ -393,7 +479,9 @@ static int diff_delta_prep(diff_delta_context *ctxt) static int diff_delta_load(diff_delta_context *ctxt) { int error = 0; + git_repository *repo = ctxt->repo; git_diff_delta *delta = ctxt->delta; + bool load_old = false, load_new = false, check_if_unmodified = false; if (ctxt->loaded || !ctxt->delta) return 0; @@ -405,75 +493,77 @@ static int diff_delta_load(diff_delta_context *ctxt) ctxt->old_data.len = 0; ctxt->old_blob = NULL; - if (!error && delta->binary != 1 && - (delta->status == GIT_DELTA_DELETED || - delta->status == GIT_DELTA_MODIFIED)) - { - if (ctxt->old_src == GIT_ITERATOR_WORKDIR) - error = get_workdir_content( - ctxt->repo, &delta->old_file, &ctxt->old_data); - else { - error = get_blob_content( - ctxt->repo, &delta->old_file.oid, - &ctxt->old_data, &ctxt->old_blob); - - if (ctxt->new_src == GIT_ITERATOR_WORKDIR) { - /* TODO: convert crlf of blob content */ - } - } - } - ctxt->new_data.data = ""; ctxt->new_data.len = 0; ctxt->new_blob = NULL; - if (!error && delta->binary != 1 && - (delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_MODIFIED)) - { - if (ctxt->new_src == GIT_ITERATOR_WORKDIR) - error = get_workdir_content( - ctxt->repo, &delta->new_file, &ctxt->new_data); - else { - error = get_blob_content( - ctxt->repo, &delta->new_file.oid, - &ctxt->new_data, &ctxt->new_blob); - - if (ctxt->old_src == GIT_ITERATOR_WORKDIR) { - /* TODO: convert crlf of blob content */ - } - } + if (delta->binary == 1) + goto cleanup; - if (!error && !(delta->new_file.flags & GIT_DIFF_FILE_VALID_OID)) { - error = git_odb_hash( - &delta->new_file.oid, ctxt->new_data.data, - ctxt->new_data.len, GIT_OBJ_BLOB); - if (error < 0) - goto cleanup; + switch (delta->status) { + case GIT_DELTA_ADDED: load_new = true; break; + case GIT_DELTA_DELETED: load_old = true; break; + case GIT_DELTA_MODIFIED: load_new = load_old = true; break; + default: break; + } - delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; + check_if_unmodified = + (load_old && (delta->old_file.flags & GIT_DIFF_FILE_VALID_OID) == 0) || + (load_new && (delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0); - /* since we did not have the definitive oid, we may have - * incorrect status and need to skip this item. - */ - if (delta->old_file.mode == delta->new_file.mode && - !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)) - { - delta->status = GIT_DELTA_UNMODIFIED; + /* Always try to load workdir content first, since it may need to be + * filtered (and hence use 2x memory) and we want to minimize the max + * memory footprint during diff. + */ - if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - goto cleanup; - } - } + if (load_old && ctxt->old_src == GIT_ITERATOR_WORKDIR) { + if ((error = get_workdir_content( + repo, &delta->old_file, &ctxt->old_data)) < 0) + goto cleanup; + + if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0) + goto cleanup; + } + + if (load_new && ctxt->new_src == GIT_ITERATOR_WORKDIR) { + if ((error = get_workdir_content( + repo, &delta->new_file, &ctxt->new_data)) < 0) + goto cleanup; + + if ((delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) + goto cleanup; } + if (load_old && ctxt->old_src != GIT_ITERATOR_WORKDIR && + (error = get_blob_content( + repo, &delta->old_file, &ctxt->old_data, &ctxt->old_blob)) < 0) + goto cleanup; + + if (load_new && ctxt->new_src != GIT_ITERATOR_WORKDIR && + (error = get_blob_content( + repo, &delta->new_file, &ctxt->new_data, &ctxt->new_blob)) < 0) + goto cleanup; + + /* if we did not previously have the definitive oid, we may have + * incorrect status and need to switch this to UNMODIFIED. + */ + if (check_if_unmodified && + delta->old_file.mode == delta->new_file.mode && + !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)) + { + delta->status = GIT_DELTA_UNMODIFIED; + + if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + goto cleanup; + } + +cleanup: /* if we have not already decided whether file is binary, * check the first 4K for nul bytes to decide... */ if (!error && delta->binary == -1) error = diff_delta_is_binary_by_content(ctxt); -cleanup: ctxt->loaded = !error; /* flag if we would want to diff the contents of these files */ diff --git a/src/fileops.c b/src/fileops.c index 95eacb5f164..d4def1a9ac5 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -115,10 +115,47 @@ mode_t git_futils_canonical_mode(mode_t raw_mode) return 0; } -int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated) +#define MAX_READ_STALLS 10 + +int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) +{ + int stalls = MAX_READ_STALLS; + + git_buf_clear(buf); + + if (git_buf_grow(buf, len + 1) < 0) + return -1; + + buf->ptr[len] = '\0'; + + while (len > 0) { + ssize_t read_size = p_read(fd, buf->ptr + buf->size, len); + + if (read_size < 0) { + giterr_set(GITERR_OS, "Failed to read descriptor"); + return -1; + } + + if (read_size == 0) { + stalls--; + + if (!stalls) { + giterr_set(GITERR_OS, "Too many stalls reading descriptor"); + return -1; + } + } + + len -= read_size; + buf->size += read_size; + } + + return 0; +} + +int git_futils_readbuffer_updated( + git_buf *buf, const char *path, time_t *mtime, int *updated) { git_file fd; - size_t len; struct stat st; assert(buf && path && *path); @@ -147,30 +184,11 @@ int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, if (mtime != NULL) *mtime = st.st_mtime; - len = (size_t) st.st_size; - - git_buf_clear(buf); - - if (git_buf_grow(buf, len + 1) < 0) { + if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { p_close(fd); return -1; } - buf->ptr[len] = '\0'; - - while (len > 0) { - ssize_t read_size = p_read(fd, buf->ptr, len); - - if (read_size < 0) { - p_close(fd); - giterr_set(GITERR_OS, "Failed to read descriptor for '%s'", path); - return -1; - } - - len -= read_size; - buf->size += read_size; - } - p_close(fd); if (updated != NULL) diff --git a/src/fileops.h b/src/fileops.h index 5c23ce30b5f..d2944f4603b 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -19,6 +19,7 @@ */ extern int git_futils_readbuffer(git_buf *obj, const char *path); extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated); +extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len); /** * File utils diff --git a/src/odb.c b/src/odb.c index 34033d15c70..83c7a80fc06 100644 --- a/src/odb.c +++ b/src/odb.c @@ -12,6 +12,7 @@ #include "hash.h" #include "odb.h" #include "delta-apply.h" +#include "filter.h" #include "git2/odb_backend.h" #include "git2/oid.h" @@ -118,11 +119,12 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) hdr_len = format_object_header(hdr, sizeof(hdr), size, type); ctx = git_hash_new_ctx(); + GITERR_CHECK_ALLOC(ctx); git_hash_update(ctx, hdr, hdr_len); while (size > 0) { - ssize_t read_len = read(fd, buffer, sizeof(buffer)); + ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); if (read_len < 0) { git_hash_free_ctx(ctx); @@ -140,6 +142,33 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) return 0; } +int git_odb__hashfd_filtered( + git_oid *out, git_file fd, size_t size, git_otype type, git_vector *filters) +{ + int error; + git_buf raw = GIT_BUF_INIT; + git_buf filtered = GIT_BUF_INIT; + + if (!filters || !filters->length) + return git_odb__hashfd(out, fd, size, type); + + /* size of data is used in header, so we have to read the whole file + * into memory to apply filters before beginning to calculate the hash + */ + + if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) + error = git_filters_apply(&filtered, &raw, filters); + + git_buf_free(&raw); + + if (!error) + error = git_odb_hash(out, filtered.ptr, filtered.size, type); + + git_buf_free(&filtered); + + return error; +} + int git_odb__hashlink(git_oid *out, const char *path) { struct stat st; @@ -171,7 +200,7 @@ int git_odb__hashlink(git_oid *out, const char *path) result = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB); git__free(link_data); - } else { + } else { int fd = git_futils_open_ro(path); if (fd < 0) return -1; diff --git a/src/odb.h b/src/odb.h index 263e4c30b30..696e1294398 100644 --- a/src/odb.h +++ b/src/odb.h @@ -58,12 +58,19 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj); int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type); /* - * Hash a `path`, assuming it could be a POSIX symlink: if the path is a symlink, - * then the raw contents of the symlink will be hashed. Otherwise, this will - * fallback to `git_odb__hashfd`. + * Hash an open file descriptor applying an array of filters + * Acts just like git_odb__hashfd with the addition of filters... + */ +int git_odb__hashfd_filtered( + git_oid *out, git_file fd, size_t len, git_otype type, git_vector *filters); + +/* + * Hash a `path`, assuming it could be a POSIX symlink: if the path is a + * symlink, then the raw contents of the symlink will be hashed. Otherwise, + * this will fallback to `git_odb__hashfd`. * - * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may only - * point to blobs. + * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may + * only point to blobs. */ int git_odb__hashlink(git_oid *out, const char *path); diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index c0412ef96b2..05e396e1f3f 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -839,9 +839,5 @@ void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void cl_git_pass(git_status_file(&status, repo, "current_file")); -#ifdef GIT_WIN32 cl_assert_equal_i(GIT_STATUS_CURRENT, status); -#else - cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); -#endif } From 3a3deea80bb6555706f58006bdee8e878b0fd651 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 6 Sep 2012 15:45:50 -0700 Subject: [PATCH 27/72] Clean up blob diff path Previously when diffing blobs, the diff code just ran with a NULL repository object. Of course, that's not necessary and the test for a NULL repo was confusing. This makes the blob diff run with the repo that contains the blobs and clarifies the test that it is possible to be diffing data where the path is unknown. --- src/diff_output.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/diff_output.c b/src/diff_output.c index e2ca8cf3ea1..6ff880e9549 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -151,7 +151,8 @@ static int update_file_is_binary_by_attr( { const char *value; - if (!repo) + /* because of blob diffs, cannot assume path is set */ + if (!file->path || !strlen(file->path)) return 0; if (git_attr_get(&value, repo, 0, file->path, "diff") < 0) @@ -1028,6 +1029,7 @@ int git_diff_blobs( diff_delta_context ctxt; git_diff_delta delta; git_blob *new, *old; + git_repository *repo; new = new_blob; old = old_blob; @@ -1038,8 +1040,15 @@ int git_diff_blobs( new = swap; } + if (new) + repo = git_object_owner((git_object *)new); + else if (old) + repo = git_object_owner((git_object *)old); + else + repo = NULL; + diff_delta_init_context( - &ctxt, NULL, options, GIT_ITERATOR_TREE, GIT_ITERATOR_TREE); + &ctxt, repo, options, GIT_ITERATOR_TREE, GIT_ITERATOR_TREE); /* populate a "fake" delta record */ From 17b06f4d47bfd9fae8073c85d71751df94e50050 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 7 Sep 2012 15:49:08 -0700 Subject: [PATCH 28/72] Add missing accessor for fetchRecurseSubmodules When `git_submodule` became an opaque structure, I forgot to add accessor functions for the fetchRecurseSubmodules config setting. This fixes that. --- include/git2/submodule.h | 29 +++++++++++++++++++++++++++++ src/submodule.c | 20 ++++++++++++++++++++ tests-clar/submodule/modify.c | 11 +++++++++++ 3 files changed, 60 insertions(+) diff --git a/include/git2/submodule.h b/include/git2/submodule.h index fe7f26cfe98..28057d26f6e 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -395,6 +395,35 @@ GIT_EXTERN(git_submodule_update_t) git_submodule_set_update( git_submodule *submodule, git_submodule_update_t update); +/** + * Read the fetchRecurseSubmodules rule for a submodule. + * + * This accesses the submodule..fetchRecurseSubmodules value for + * the submodule that controls fetching behavior for the submodule. + * + * Note that at this time, libgit2 does not honor this setting and the + * fetch functionality current ignores submodules. + * + * @return 0 if fetchRecurseSubmodules is false, 1 if true + */ +GIT_EXTERN(int) git_submodule_fetch_recurse_submodules( + git_submodule *submodule); + +/** + * Set the fetchRecurseSubmodules rule for a submodule. + * + * This sets the submodule..fetchRecurseSubmodules value for + * the submodule. You should call `git_submodule_save()` if you want + * to persist the new value. + * + * @param submodule The submodule to modify + * @param fetch_recurse_submodules Boolean value + * @return old value for fetchRecurseSubmodules + */ +GIT_EXTERN(int) git_submodule_set_fetch_recurse_submodules( + git_submodule *submodule, + int fetch_recurse_submodules); + /** * Copy submodule info into ".git/config" file. * diff --git a/src/submodule.c b/src/submodule.c index 66f1f84b45b..5ae38bccd99 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -595,6 +595,26 @@ git_submodule_update_t git_submodule_set_update( return old; } +int git_submodule_fetch_recurse_submodules( + git_submodule *submodule) +{ + assert(submodule); + return submodule->fetch_recurse; +} + +int git_submodule_set_fetch_recurse_submodules( + git_submodule *submodule, + int fetch_recurse_submodules) +{ + int old; + + assert(submodule); + + old = submodule->fetch_recurse; + submodule->fetch_recurse = (fetch_recurse_submodules != 0); + return old; +} + int git_submodule_init(git_submodule *submodule, int overwrite) { int error; diff --git a/tests-clar/submodule/modify.c b/tests-clar/submodule/modify.c index ffbbe891c43..0fd732cc3e2 100644 --- a/tests-clar/submodule/modify.c +++ b/tests-clar/submodule/modify.c @@ -183,6 +183,7 @@ void test_submodule_modify__edit_and_save(void) git_submodule_ignore_t old_ignore; git_submodule_update_t old_update; git_repository *r2; + int old_fetchrecurse; cl_git_pass(git_submodule_lookup(&sm1, g_repo, "sm_changed_head")); @@ -192,12 +193,14 @@ void test_submodule_modify__edit_and_save(void) cl_git_pass(git_submodule_set_url(sm1, SM_LIBGIT2_URL)); old_ignore = git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_UNTRACKED); old_update = git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_REBASE); + old_fetchrecurse = git_submodule_set_fetch_recurse_submodules(sm1, 1); cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm1)); cl_assert_equal_i( (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1)); cl_assert_equal_i( (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1)); + cl_assert_equal_i(1, git_submodule_fetch_recurse_submodules(sm1)); /* revert without saving (and confirm setters return old value) */ cl_git_pass(git_submodule_set_url(sm1, old_url)); @@ -207,16 +210,21 @@ void test_submodule_modify__edit_and_save(void) cl_assert_equal_i( (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT)); + cl_assert_equal_i( + 1, git_submodule_set_fetch_recurse_submodules(sm1, old_fetchrecurse)); /* check that revert was successful */ cl_assert_equal_s(old_url, git_submodule_url(sm1)); cl_assert_equal_i((int)old_ignore, (int)git_submodule_ignore(sm1)); cl_assert_equal_i((int)old_update, (int)git_submodule_update(sm1)); + cl_assert_equal_i( + old_fetchrecurse, git_submodule_fetch_recurse_submodules(sm1)); /* modify properties of submodule (again) */ cl_git_pass(git_submodule_set_url(sm1, SM_LIBGIT2_URL)); git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_UNTRACKED); git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_REBASE); + git_submodule_set_fetch_recurse_submodules(sm1, 1); /* call save */ cl_git_pass(git_submodule_save(sm1)); @@ -232,6 +240,7 @@ void test_submodule_modify__edit_and_save(void) (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1)); cl_assert_equal_i( (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1)); + cl_assert_equal_i(1, git_submodule_fetch_recurse_submodules(sm1)); /* call reload and check that the new values are loaded */ cl_git_pass(git_submodule_reload(sm1)); @@ -241,6 +250,7 @@ void test_submodule_modify__edit_and_save(void) (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1)); cl_assert_equal_i( (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1)); + cl_assert_equal_i(1, git_submodule_fetch_recurse_submodules(sm1)); /* open a second copy of the repo and compare submodule */ cl_git_pass(git_repository_open(&r2, "submod2")); @@ -251,6 +261,7 @@ void test_submodule_modify__edit_and_save(void) (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm2)); cl_assert_equal_i( (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm2)); + cl_assert_equal_i(1, git_submodule_fetch_recurse_submodules(sm2)); git_repository_free(r2); git__free(old_url); From 857323d4db63751517bdb2e63336aae4e82bb78a Mon Sep 17 00:00:00 2001 From: Sascha Cunz Date: Sun, 9 Sep 2012 15:53:57 +0200 Subject: [PATCH 29/72] git_mergebase: Constness-Fix for consistency --- include/git2/merge.h | 2 +- src/revwalk.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/git2/merge.h b/include/git2/merge.h index c80803d3673..37b1c787d41 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -28,7 +28,7 @@ GIT_BEGIN_DECL * @param one one of the commits * @param two the other commit */ -GIT_EXTERN(int) git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two); +GIT_EXTERN(int) git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two); /** * Find a merge base given a list of commits diff --git a/src/revwalk.c b/src/revwalk.c index 1a092771957..8141d177b33 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -419,7 +419,7 @@ int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_ return error; } -int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two) +int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two) { git_revwalk *walk; git_vector list; From b36effa22e015871948daeea250b4996c663e11a Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 10 Sep 2012 09:59:14 -0700 Subject: [PATCH 30/72] Replace git_diff_iterator_num_files with progress The `git_diff_iterator_num_files` API was problematic, since we don't actually know the exact number of files to be iterated over until we load those files into memory. This replaces it with a new `git_diff_iterator_progress` API that goes from 0 to 1, and moves and renamed the old API for the internal places that can tolerate a max value instead of an exact value. --- include/git2/diff.h | 24 +++++++----------------- src/diff.h | 22 ++++++++++++++++++++++ src/diff_output.c | 18 +++++++----------- tests-clar/diff/diff_helpers.c | 8 +++----- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 2898f3b2020..7a86d2463ba 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -384,26 +384,16 @@ GIT_EXTERN(int) git_diff_iterator_new( GIT_EXTERN(void) git_diff_iterator_free(git_diff_iterator *iterator); /** - * Return the number of files in the diff. + * Return progress value for traversing the diff. * - * NOTE: This number has to be treated as an upper bound on the number of - * files that have changed if the diff is with the working directory. + * This returns a value between 0.0 and 1.0 that represents the progress + * through the diff iterator. The value is monotonically increasing and + * will advance gradually as you progress through the iteration. * - * Why?! For efficiency, we defer loading the file contents as long as - * possible, so if a file has been "touched" in the working directory and - * then reverted to the original content, it may get stored in the diff list - * as MODIFIED along with a flag that the status should be reconfirmed when - * it is actually loaded into memory. When that load happens, it could get - * flipped to UNMODIFIED. If unmodified files are being skipped, then the - * iterator will skip that file and this number may be too high. - * - * This behavior is true of `git_diff_foreach` as well, but the only - * implication there is that the `progress` value would not advance evenly. - * - * @param iterator The iterator object - * @return The maximum number of files to be iterated over + * @param iterator The diff iterator + * @return Value between 0.0 and 1.0 */ -GIT_EXTERN(int) git_diff_iterator_num_files(git_diff_iterator *iterator); +GIT_EXTERN(float) git_diff_iterator_progress(git_diff_iterator *iterator); /** * Return the number of hunks in the current file diff --git a/src/diff.h b/src/diff.h index def74632366..ea38a678f57 100644 --- a/src/diff.h +++ b/src/diff.h @@ -42,5 +42,27 @@ struct git_diff_list { extern void git_diff__cleanup_modes( uint32_t diffcaps, uint32_t *omode, uint32_t *nmode); +/** + * Return the maximum possible number of files in the diff. + * + * NOTE: This number has to be treated as an upper bound on the number of + * files that have changed if the diff is with the working directory. + * + * Why?! For efficiency, we defer loading the file contents as long as + * possible, so if a file has been "touched" in the working directory and + * then reverted to the original content, it may get stored in the diff list + * as MODIFIED along with a flag that the status should be reconfirmed when + * it is actually loaded into memory. When that load happens, it could get + * flipped to UNMODIFIED. If unmodified files are being skipped, then the + * iterator will skip that file and this number may be too high. + * + * This behavior is true of `git_diff_foreach` as well, but the only + * implication there is that the `progress` value would not advance evenly. + * + * @param iterator The iterator object + * @return The maximum number of files to be iterated over + */ +int git_diff_iterator__max_files(git_diff_iterator *iterator); + #endif diff --git a/src/diff_output.c b/src/diff_output.c index 6ff880e9549..f65d0057fb7 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -1115,7 +1115,6 @@ struct git_diff_iterator { diff_delta_context ctxt; size_t file_index; size_t next_index; - size_t file_count; git_pool hunks; size_t hunk_count; diffiter_hunk *hunk_head; @@ -1239,8 +1238,6 @@ int git_diff_iterator_new( git_diff_iterator **iterator_ptr, git_diff_list *diff) { - size_t i; - git_diff_delta *delta; git_diff_iterator *iter; assert(diff && iterator_ptr); @@ -1261,12 +1258,6 @@ int git_diff_iterator_new( git_pool_init(&iter->lines, sizeof(diffiter_line), 0) < 0) goto fail; - git_vector_foreach(&diff->deltas, i, delta) { - if (diff_delta_should_skip(iter->ctxt.opts, delta)) - continue; - iter->file_count++; - } - *iterator_ptr = iter; return 0; @@ -1284,9 +1275,14 @@ void git_diff_iterator_free(git_diff_iterator *iter) git__free(iter); } -int git_diff_iterator_num_files(git_diff_iterator *iter) +float git_diff_iterator_progress(git_diff_iterator *iter) +{ + return (float)iter->next_index / (float)iter->diff->deltas.length; +} + +int git_diff_iterator__max_files(git_diff_iterator *iter) { - return (int)iter->file_count; + return (int)iter->diff->deltas.length; } int git_diff_iterator_num_hunks_in_file(git_diff_iterator *iter) diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index 59e01802c1f..ef59b686f26 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -111,23 +111,21 @@ int diff_foreach_via_iterator( git_diff_hunk_fn hunk_cb, git_diff_data_fn line_cb) { - int error, curr, total; + int error; git_diff_iterator *iter; git_diff_delta *delta; if ((error = git_diff_iterator_new(&iter, diff)) < 0) return error; - curr = 0; - total = git_diff_iterator_num_files(iter); - while (!(error = git_diff_iterator_next_file(&delta, iter))) { git_diff_range *range; const char *hdr; size_t hdr_len; + float progress = git_diff_iterator_progress(iter); /* call file_cb for this file */ - if (file_cb != NULL && file_cb(data, delta, (float)curr / total) != 0) + if (file_cb != NULL && file_cb(data, delta, progress) != 0) goto abort; if (!hunk_cb && !line_cb) From e597b1890ebd43e398d84b7bf4ca366365b24d27 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 10 Sep 2012 11:49:12 -0700 Subject: [PATCH 31/72] Move diff max_size to public API This commit adds a max_size value in the public `git_diff_options` structure so that the user can automatically flag blobs over a certain size as binary regardless of other properties. Also, and perhaps more importantly, this moves binary detection to be as early as possible in the diff traversal inner loop and makes sure that we stop loading objects as soon as we decide that they are binary. --- include/git2/diff.h | 9 ++- src/diff_output.c | 148 +++++++++++++++++++++++++------------------- 2 files changed, 91 insertions(+), 66 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 7a86d2463ba..4b4591a9e12 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -56,7 +56,13 @@ enum { * values. Similarly, passing NULL for the options structure will * give the defaults. The default values are marked below. * - * @todo Most of the parameters here are not actually supported at this time. + * - flags: a combination of the GIT_DIFF_... values above + * - context_lines: number of lines of context to show around diffs + * - interhunk_lines: min lines between diff hunks to merge them + * - old_prefix: "directory" to prefix to old file names (default "a") + * - new_prefix: "directory" to prefix to new file names (default "b") + * - pathspec: array of paths / patterns to constrain diff + * - max_size: maximum blob size to diff, above this treated as binary */ typedef struct { uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */ @@ -65,6 +71,7 @@ typedef struct { char *old_prefix; /**< defaults to "a" */ char *new_prefix; /**< defaults to "b" */ git_strarray pathspec; /**< defaults to show all paths */ + git_off_t max_size; /**< defaults to 512Mb */ } git_diff_options; /** diff --git a/src/diff_output.c b/src/diff_output.c index f65d0057fb7..8873a4dc7b7 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -172,7 +172,7 @@ static void update_delta_is_binary(git_diff_delta *delta) if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 || (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) delta->binary = 1; - else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 || + else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 && (delta->new_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0) delta->binary = 0; /* otherwise leave delta->binary value untouched */ @@ -219,34 +219,46 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) return error; } -static int diff_delta_is_binary_by_content(diff_delta_context *ctxt) +static int diff_delta_is_binary_by_content( + diff_delta_context *ctxt, git_diff_file *file, git_map *map) { - git_diff_delta *delta = ctxt->delta; git_buf search; - if ((delta->old_file.flags & BINARY_DIFF_FLAGS) == 0) { - search.ptr = ctxt->old_data.data; - search.size = min(ctxt->old_data.len, 4000); + if ((file->flags & BINARY_DIFF_FLAGS) == 0) { + search.ptr = map->data; + search.size = min(map->len, 4000); if (git_buf_is_binary(&search)) - delta->old_file.flags |= GIT_DIFF_FILE_BINARY; + file->flags |= GIT_DIFF_FILE_BINARY; else - delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + file->flags |= GIT_DIFF_FILE_NOT_BINARY; } - if ((delta->new_file.flags & BINARY_DIFF_FLAGS) == 0) { - search.ptr = ctxt->new_data.data; - search.size = min(ctxt->new_data.len, 4000); + update_delta_is_binary(ctxt->delta); - if (git_buf_is_binary(&search)) - delta->new_file.flags |= GIT_DIFF_FILE_BINARY; - else - delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + return 0; +} + +static int diff_delta_is_binary_by_size( + diff_delta_context *ctxt, git_diff_file *file) +{ + git_off_t threshold = MAX_DIFF_FILESIZE; + + if ((file->flags & BINARY_DIFF_FLAGS) != 0) + return 0; + + if (ctxt && ctxt->opts) { + if (ctxt->opts->max_size < 0) + return 0; + + if (ctxt->opts->max_size > 0) + threshold = ctxt->opts->max_size; } - update_delta_is_binary(delta); + if (file->size > threshold) + file->flags |= GIT_DIFF_FILE_BINARY; - /* TODO: if value != NULL, implement diff drivers */ + update_delta_is_binary(ctxt->delta); return 0; } @@ -274,53 +286,56 @@ static void setup_xdiff_options( } static int get_blob_content( - git_repository *repo, + diff_delta_context *ctxt, git_diff_file *file, git_map *map, git_blob **blob) { int error; - git_odb *odb; - size_t len; - git_otype type; if (git_oid_iszero(&file->oid)) return 0; - /* peek at object header to avoid loading if too large */ - if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || - (error = git_odb_read_header(&len, &type, odb, &file->oid)) < 0) - return error; + if (!file->size) { + git_odb *odb; + size_t len; + git_otype type; - assert(type == GIT_OBJ_BLOB); + /* peek at object header to avoid loading if too large */ + if ((error = git_repository_odb__weakptr(&odb, ctxt->repo)) < 0 || + (error = git_odb_read_header(&len, &type, odb, &file->oid)) < 0) + return error; - /* if blob is too large to diff, mark as binary */ - if (len > MAX_DIFF_FILESIZE) { - file->flags |= GIT_DIFF_FILE_BINARY; - return 0; - } + assert(type == GIT_OBJ_BLOB); - if (!file->size) file->size = len; + } - if ((error = git_blob_lookup(blob, repo, &file->oid)) < 0) + /* if blob is too large to diff, mark as binary */ + if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0) + return error; + if (ctxt->delta->binary == 1) + return 0; + + if ((error = git_blob_lookup(blob, ctxt->repo, &file->oid)) < 0) return error; map->data = (void *)git_blob_rawcontent(*blob); map->len = git_blob_rawsize(*blob); - return 0; + return diff_delta_is_binary_by_content(ctxt, file, map); } static int get_workdir_content( - git_repository *repo, + diff_delta_context *ctxt, git_diff_file *file, git_map *map) { int error = 0; git_buf path = GIT_BUF_INIT; + const char *wd = git_repository_workdir(ctxt->repo); - if (git_buf_joinpath(&path, git_repository_workdir(repo), file->path) < 0) + if (git_buf_joinpath(&path, wd, file->path) < 0) return -1; if (S_ISLNK(file->mode)) { @@ -358,13 +373,12 @@ static int get_workdir_content( if (!file->size) file->size = git_futils_filesize(fd); - /* if file is too large to diff, mark as binary */ - if (file->size > MAX_DIFF_FILESIZE) { - file->flags |= GIT_DIFF_FILE_BINARY; + if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0 || + ctxt->delta->binary == 1) goto close_and_cleanup; - } - error = git_filters_load(&filters, repo, file->path, GIT_FILTER_TO_ODB); + error = git_filters_load( + &filters, ctxt->repo, file->path, GIT_FILTER_TO_ODB); if (error < 0) goto close_and_cleanup; @@ -400,6 +414,9 @@ static int get_workdir_content( file->flags |= GIT_DIFF_FILE_VALID_OID; } + if (!error) + error = diff_delta_is_binary_by_content(ctxt, file, map); + cleanup: git_buf_free(&path); return error; @@ -480,7 +497,6 @@ static int diff_delta_prep(diff_delta_context *ctxt) static int diff_delta_load(diff_delta_context *ctxt) { int error = 0; - git_repository *repo = ctxt->repo; git_diff_delta *delta = ctxt->delta; bool load_old = false, load_new = false, check_if_unmodified = false; @@ -519,31 +535,35 @@ static int diff_delta_load(diff_delta_context *ctxt) if (load_old && ctxt->old_src == GIT_ITERATOR_WORKDIR) { if ((error = get_workdir_content( - repo, &delta->old_file, &ctxt->old_data)) < 0) + ctxt, &delta->old_file, &ctxt->old_data)) < 0) goto cleanup; - - if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0) + if (delta->binary == 1) goto cleanup; } if (load_new && ctxt->new_src == GIT_ITERATOR_WORKDIR) { if ((error = get_workdir_content( - repo, &delta->new_file, &ctxt->new_data)) < 0) + ctxt, &delta->new_file, &ctxt->new_data)) < 0) goto cleanup; - - if ((delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) + if (delta->binary == 1) goto cleanup; } - if (load_old && ctxt->old_src != GIT_ITERATOR_WORKDIR && - (error = get_blob_content( - repo, &delta->old_file, &ctxt->old_data, &ctxt->old_blob)) < 0) - goto cleanup; + if (load_old && ctxt->old_src != GIT_ITERATOR_WORKDIR) { + if ((error = get_blob_content( + ctxt, &delta->old_file, &ctxt->old_data, &ctxt->old_blob)) < 0) + goto cleanup; + if (delta->binary == 1) + goto cleanup; + } - if (load_new && ctxt->new_src != GIT_ITERATOR_WORKDIR && - (error = get_blob_content( - repo, &delta->new_file, &ctxt->new_data, &ctxt->new_blob)) < 0) - goto cleanup; + if (load_new && ctxt->new_src != GIT_ITERATOR_WORKDIR) { + if ((error = get_blob_content( + ctxt, &delta->new_file, &ctxt->new_data, &ctxt->new_blob)) < 0) + goto cleanup; + if (delta->binary == 1) + goto cleanup; + } /* if we did not previously have the definitive oid, we may have * incorrect status and need to switch this to UNMODIFIED. @@ -559,12 +579,6 @@ static int diff_delta_load(diff_delta_context *ctxt) } cleanup: - /* if we have not already decided whether file is binary, - * check the first 4K for nul bytes to decide... - */ - if (!error && delta->binary == -1) - error = diff_delta_is_binary_by_content(ctxt); - ctxt->loaded = !error; /* flag if we would want to diff the contents of these files */ @@ -1069,9 +1083,13 @@ int git_diff_blobs( if ((error = diff_delta_prep(&ctxt)) < 0) goto cleanup; - if (delta.binary == -1 && - (error = diff_delta_is_binary_by_content(&ctxt)) < 0) - goto cleanup; + if (delta.binary == -1) { + if ((error = diff_delta_is_binary_by_content( + &ctxt, &delta.old_file, &ctxt.old_data)) < 0 || + (error = diff_delta_is_binary_by_content( + &ctxt, &delta.new_file, &ctxt.new_data)) < 0) + goto cleanup; + } ctxt.loaded = 1; ctxt.diffable = (delta.binary != 1 && delta.status != GIT_DELTA_UNMODIFIED); From c6ac28fdc57d04a9a5eba129cfd267c7adde43b3 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 10 Sep 2012 12:24:05 -0700 Subject: [PATCH 32/72] Reorg internal odb read header and object lookup Often `git_odb_read_header` will "fail" and have to read the entire object into memory instead of just the header. When this happens, the object is loaded and then disposed of immediately, which makes it difficult to efficiently use the header information to decide if the object should be loaded (since attempting to do so will often result in loading the object twice). This commit takes the existing code and reorganizes it to have two new functions: - `git_odb__read_header_or_object` which acts just like the old read header function except that it returns the object, too, if it was forced to load the whole thing. It then becomes the callers responsibility to free the `git_odb_object`. - `git_object__from_odb_object` which was extracted from the old `git_object_lookup` and creates a subclass of `git_object` from an existing `git_odb_object` (separating the ODB lookup from the `git_object` creation). This allows you to use the first header reading function efficiently without instantiating the `git_odb_object` twice. There is no net change to the behavior of any of the existing functions, but this allows internal code to tap into the ODB lookup and object creation to be more efficient. --- src/diff_output.c | 13 ++++++- src/object.c | 98 ++++++++++++++++++++++++++--------------------- src/object.h | 39 +++++++++++++++++++ src/odb.c | 24 ++++++++++-- src/odb.h | 8 ++++ src/repository.h | 25 +----------- 6 files changed, 135 insertions(+), 72 deletions(-) create mode 100644 src/object.h diff --git a/src/diff_output.c b/src/diff_output.c index 8873a4dc7b7..dbef7ddc19e 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -292,6 +292,7 @@ static int get_blob_content( git_blob **blob) { int error; + git_odb_object *odb_obj = NULL; if (git_oid_iszero(&file->oid)) return 0; @@ -303,7 +304,8 @@ static int get_blob_content( /* peek at object header to avoid loading if too large */ if ((error = git_repository_odb__weakptr(&odb, ctxt->repo)) < 0 || - (error = git_odb_read_header(&len, &type, odb, &file->oid)) < 0) + (error = git_odb__read_header_or_object( + &odb_obj, &len, &type, odb, &file->oid)) < 0) return error; assert(type == GIT_OBJ_BLOB); @@ -317,7 +319,14 @@ static int get_blob_content( if (ctxt->delta->binary == 1) return 0; - if ((error = git_blob_lookup(blob, ctxt->repo, &file->oid)) < 0) + if (odb_obj != NULL) { + error = git_object__from_odb_object( + (git_object **)blob, ctxt->repo, odb_obj, GIT_OBJ_BLOB); + git_odb_object_free(odb_obj); + } else + error = git_blob_lookup(blob, ctxt->repo, &file->oid); + + if (error) return error; map->data = (void *)git_blob_rawcontent(*blob); diff --git a/src/object.c b/src/object.c index 5130d97acab..2e45eb86aae 100644 --- a/src/object.c +++ b/src/object.c @@ -77,6 +77,58 @@ static int create_object(git_object **object_out, git_otype type) return 0; } +int git_object__from_odb_object( + git_object **object_out, + git_repository *repo, + git_odb_object *odb_obj, + git_otype type) +{ + int error; + git_object *object = NULL; + + if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) { + giterr_set(GITERR_ODB, "The requested type does not match the type in the ODB"); + return GIT_ENOTFOUND; + } + + type = odb_obj->raw.type; + + if ((error = create_object(&object, type)) < 0) + return error; + + /* Initialize parent object */ + git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); + object->repo = repo; + + switch (type) { + case GIT_OBJ_COMMIT: + error = git_commit__parse((git_commit *)object, odb_obj); + break; + + case GIT_OBJ_TREE: + error = git_tree__parse((git_tree *)object, odb_obj); + break; + + case GIT_OBJ_TAG: + error = git_tag__parse((git_tag *)object, odb_obj); + break; + + case GIT_OBJ_BLOB: + error = git_blob__parse((git_blob *)object, odb_obj); + break; + + default: + break; + } + + if (error < 0) + git_object__free(object); + else + *object_out = git_cache_try_store(&repo->objects, object); + + return error; +} + int git_object_lookup_prefix( git_object **object_out, git_repository *repo, @@ -148,53 +200,11 @@ int git_object_lookup_prefix( if (error < 0) return error; - if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) { - git_odb_object_free(odb_obj); - giterr_set(GITERR_ODB, "The given type does not match the type on the ODB"); - return GIT_ENOTFOUND; - } - - type = odb_obj->raw.type; - - if (create_object(&object, type) < 0) { - git_odb_object_free(odb_obj); - return -1; - } - - /* Initialize parent object */ - git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); - object->repo = repo; - - switch (type) { - case GIT_OBJ_COMMIT: - error = git_commit__parse((git_commit *)object, odb_obj); - break; - - case GIT_OBJ_TREE: - error = git_tree__parse((git_tree *)object, odb_obj); - break; - - case GIT_OBJ_TAG: - error = git_tag__parse((git_tag *)object, odb_obj); - break; - - case GIT_OBJ_BLOB: - error = git_blob__parse((git_blob *)object, odb_obj); - break; - - default: - break; - } + error = git_object__from_odb_object(object_out, repo, odb_obj, type); git_odb_object_free(odb_obj); - if (error < 0) { - git_object__free(object); - return -1; - } - - *object_out = git_cache_try_store(&repo->objects, object); - return 0; + return error; } int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) { diff --git a/src/object.h b/src/object.h new file mode 100644 index 00000000000..bc12aad04d9 --- /dev/null +++ b/src/object.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_object_h__ +#define INCLUDE_object_h__ + +/** Base git object for inheritance */ +struct git_object { + git_cached_obj cached; + git_repository *repo; + git_otype type; +}; + +/* fully free the object; internal method, DO NOT EXPORT */ +void git_object__free(void *object); + +GIT_INLINE(int) git_object__dup(git_object **dest, git_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + +int git_object__from_odb_object( + git_object **object_out, + git_repository *repo, + git_odb_object *odb_obj, + git_otype type); + +int git_object__resolve_to_type(git_object **obj, git_otype type); + +int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); + +void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); + +#endif diff --git a/src/odb.c b/src/odb.c index 83c7a80fc06..0e03e40eefa 100644 --- a/src/odb.c +++ b/src/odb.c @@ -513,20 +513,37 @@ int git_odb_exists(git_odb *db, const git_oid *id) } int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id) +{ + int error; + git_odb_object *object; + + error = git_odb__read_header_or_object(&object, len_p, type_p, db, id); + + if (object) + git_odb_object_free(object); + + return error; +} + +int git_odb__read_header_or_object( + git_odb_object **out, size_t *len_p, git_otype *type_p, + git_odb *db, const git_oid *id) { unsigned int i; int error = GIT_ENOTFOUND; git_odb_object *object; - assert(db && id); + assert(db && id && out && len_p && type_p); if ((object = git_cache_get(&db->cache, id)) != NULL) { *len_p = object->raw.len; *type_p = object->raw.type; - git_odb_object_free(object); + *out = object; return 0; } + *out = NULL; + for (i = 0; i < db->backends.length && error < 0; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; @@ -547,7 +564,8 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git *len_p = object->raw.len; *type_p = object->raw.type; - git_odb_object_free(object); + *out = object; + return 0; } diff --git a/src/odb.h b/src/odb.h index 696e1294398..e9e33dde8f6 100644 --- a/src/odb.h +++ b/src/odb.h @@ -84,4 +84,12 @@ int git_odb__error_notfound(const char *message, const git_oid *oid); */ int git_odb__error_ambiguous(const char *message); +/* + * Attempt to read object header or just return whole object if it could + * not be read. + */ +int git_odb__read_header_or_object( + git_odb_object **out, size_t *len_p, git_otype *type_p, + git_odb *db, const git_oid *id); + #endif diff --git a/src/repository.h b/src/repository.h index 4695edf3a6c..82988ba0a0d 100644 --- a/src/repository.h +++ b/src/repository.h @@ -18,6 +18,7 @@ #include "refs.h" #include "buffer.h" #include "odb.h" +#include "object.h" #include "attr.h" #include "strmap.h" @@ -75,13 +76,6 @@ enum { GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18), }; -/** Base git object for inheritance */ -struct git_object { - git_cached_obj cached; - git_repository *repo; - git_otype type; -}; - /** Internal structure for repository object */ struct git_repository { git_odb *_odb; @@ -102,21 +96,6 @@ struct git_repository { git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; }; -/* fully free the object; internal method, DO NOT EXPORT */ -void git_object__free(void *object); - -GIT_INLINE(int) git_object__dup(git_object **dest, git_object *source) -{ - git_cached_obj_incref(source); - *dest = source; - return 0; -} - -int git_object__resolve_to_type(git_object **obj, git_otype type); - -int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); -void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); - GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) { return &repo->attrcache; @@ -136,7 +115,7 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo); int git_repository_index__weakptr(git_index **out, git_repository *repo); /* - * CVAR cache + * CVAR cache * * Efficient access to the most used config variables of a repository. * The cache is cleared everytime the config backend is replaced. From 6ee6861123ccb599af584377dd8b75eeea24858b Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Mon, 10 Sep 2012 21:29:07 +0200 Subject: [PATCH 33/72] cache: fix race condition Example: a cached node is owned only by the cache (refcount == 1). Thread A holds the lock and determines that the entry which should get cached equals the node (git_oid_cmp(&node->oid, &entry->oid) == 0). It frees the given entry to instead return the cached node to the user (entry = node). Now, before Thread A happens to increment the refcount of the node *outside* the cache lock, Thread B tries to store another entry and hits the slot of the node before, decrements its refcount and frees it *before* Thread A gets a chance to increment for the user. git_cached_obj_incref(entry); git_mutex_lock(&cache->lock); { git_cached_obj *node = cache->nodes[hash & cache->size_mask]; if (node == NULL) { cache->nodes[hash & cache->size_mask] = entry; } else if (git_oid_cmp(&node->oid, &entry->oid) == 0) { git_cached_obj_decref(entry, cache->free_obj); entry = node; } else { git_cached_obj_decref(node, cache->free_obj); // Thread B is here cache->nodes[hash & cache->size_mask] = entry; } } git_mutex_unlock(&cache->lock); // Thread A is here /* increase the refcount again, because we are * returning it to the user */ git_cached_obj_incref(entry); --- src/cache.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cache.c b/src/cache.c index 3aa14f012ec..1f5b8872c01 100644 --- a/src/cache.c +++ b/src/cache.c @@ -89,12 +89,13 @@ void *git_cache_try_store(git_cache *cache, void *_entry) git_cached_obj_decref(node, cache->free_obj); cache->nodes[hash & cache->size_mask] = entry; } + + /* increase the refcount again, because we are + * returning it to the user */ + git_cached_obj_incref(entry); + } git_mutex_unlock(&cache->lock); - /* increase the refcount again, because we are - * returning it to the user */ - git_cached_obj_incref(entry); - return entry; } From 1f35e89dbf6e0be8952cc4324a45fd600be5ca05 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 11 Sep 2012 12:03:33 -0700 Subject: [PATCH 34/72] Fix diff binary file detection In the process of adding tests for the max file size threshold (which treats files over a certain size as binary) there seem to be a number of problems in the new code with detecting binaries. This should fix those up, as well as add a test for the file size threshold stuff. Also, this un-deprecates `GIT_DIFF_LINE_ADD_EOFNL`, since I finally found a legitimate situation where it would be returned. --- include/git2/diff.h | 19 +++++++- src/diff_output.c | 56 +++++++++++++++------- src/fileops.c | 32 ++++--------- tests-clar/diff/diff_helpers.c | 3 +- tests-clar/diff/diffiter.c | 85 ++++++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 42 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 4b4591a9e12..05825c50db8 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -79,13 +79,28 @@ typedef struct { */ typedef struct git_diff_list git_diff_list; +/** + * Flags that can be set for the file on side of a diff. + * + * Most of the flags are just for internal consumption by libgit2, + * but some of them may be interesting to external users. They are: + * + * - VALID_OID - the `oid` value is computed and correct + * - FREE_PATH - the `path` string is separated allocated memory + * - BINARY - this file should be considered binary data + * - NOT_BINARY - this file should be considered text data + * - FREE_DATA - the internal file data is kept in allocated memory + * - UNMAP_DATA - the internal file data is kept in mmap'ed memory + * - NO_DATA - this side of the diff should not be loaded + */ enum { GIT_DIFF_FILE_VALID_OID = (1 << 0), GIT_DIFF_FILE_FREE_PATH = (1 << 1), GIT_DIFF_FILE_BINARY = (1 << 2), GIT_DIFF_FILE_NOT_BINARY = (1 << 3), GIT_DIFF_FILE_FREE_DATA = (1 << 4), - GIT_DIFF_FILE_UNMAP_DATA = (1 << 5) + GIT_DIFF_FILE_UNMAP_DATA = (1 << 5), + GIT_DIFF_FILE_NO_DATA = (1 << 6), }; /** @@ -176,7 +191,7 @@ enum { GIT_DIFF_LINE_CONTEXT = ' ', GIT_DIFF_LINE_ADDITION = '+', GIT_DIFF_LINE_DELETION = '-', - GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< DEPRECATED - will not be returned */ + GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< Removed line w/o LF & added one with */ GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */ /* The following values will only be sent to a `git_diff_data_fn` when diff --git a/src/diff_output.c b/src/diff_output.c index dbef7ddc19e..ea40c335521 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -172,9 +172,13 @@ static void update_delta_is_binary(git_diff_delta *delta) if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 || (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) delta->binary = 1; - else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 && - (delta->new_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0) + +#define NOT_BINARY_FLAGS (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA) + + else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 && + (delta->new_file.flags & NOT_BINARY_FLAGS) != 0) delta->binary = 0; + /* otherwise leave delta->binary value untouched */ } @@ -507,7 +511,7 @@ static int diff_delta_load(diff_delta_context *ctxt) { int error = 0; git_diff_delta *delta = ctxt->delta; - bool load_old = false, load_new = false, check_if_unmodified = false; + bool check_if_unmodified = false; if (ctxt->loaded || !ctxt->delta) return 0; @@ -527,22 +531,33 @@ static int diff_delta_load(diff_delta_context *ctxt) goto cleanup; switch (delta->status) { - case GIT_DELTA_ADDED: load_new = true; break; - case GIT_DELTA_DELETED: load_old = true; break; - case GIT_DELTA_MODIFIED: load_new = load_old = true; break; - default: break; + case GIT_DELTA_ADDED: + delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA; + break; + case GIT_DELTA_DELETED: + delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA; + break; + case GIT_DELTA_MODIFIED: + break; + default: + delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA; + delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA; + break; } +#define CHECK_UNMODIFIED (GIT_DIFF_FILE_NO_DATA | GIT_DIFF_FILE_VALID_OID) + check_if_unmodified = - (load_old && (delta->old_file.flags & GIT_DIFF_FILE_VALID_OID) == 0) || - (load_new && (delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0); + (delta->old_file.flags & CHECK_UNMODIFIED) == 0 && + (delta->new_file.flags & CHECK_UNMODIFIED) == 0; /* Always try to load workdir content first, since it may need to be * filtered (and hence use 2x memory) and we want to minimize the max * memory footprint during diff. */ - if (load_old && ctxt->old_src == GIT_ITERATOR_WORKDIR) { + if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && + ctxt->old_src == GIT_ITERATOR_WORKDIR) { if ((error = get_workdir_content( ctxt, &delta->old_file, &ctxt->old_data)) < 0) goto cleanup; @@ -550,7 +565,8 @@ static int diff_delta_load(diff_delta_context *ctxt) goto cleanup; } - if (load_new && ctxt->new_src == GIT_ITERATOR_WORKDIR) { + if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && + ctxt->new_src == GIT_ITERATOR_WORKDIR) { if ((error = get_workdir_content( ctxt, &delta->new_file, &ctxt->new_data)) < 0) goto cleanup; @@ -558,7 +574,8 @@ static int diff_delta_load(diff_delta_context *ctxt) goto cleanup; } - if (load_old && ctxt->old_src != GIT_ITERATOR_WORKDIR) { + if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && + ctxt->old_src != GIT_ITERATOR_WORKDIR) { if ((error = get_blob_content( ctxt, &delta->old_file, &ctxt->old_data, &ctxt->old_blob)) < 0) goto cleanup; @@ -566,7 +583,8 @@ static int diff_delta_load(diff_delta_context *ctxt) goto cleanup; } - if (load_new && ctxt->new_src != GIT_ITERATOR_WORKDIR) { + if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && + ctxt->new_src != GIT_ITERATOR_WORKDIR) { if ((error = get_blob_content( ctxt, &delta->new_file, &ctxt->new_data, &ctxt->new_blob)) < 0) goto cleanup; @@ -588,6 +606,10 @@ static int diff_delta_load(diff_delta_context *ctxt) } cleanup: + /* last change to update binary flag */ + if (delta->binary == -1) + update_delta_is_binary(delta); + ctxt->loaded = !error; /* flag if we would want to diff the contents of these files */ @@ -629,9 +651,10 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len) } if (len == 3 && !ctxt->cb_error) { - /* This should only happen if we are adding a line that does not - * have a newline at the end and the old code did. In that case, - * we have a ADD with a DEL_EOFNL as a pair. + /* If we have a '+' and a third buf, then we have added a line + * without a newline and the old code had one, so DEL_EOFNL. + * If we have a '-' and a third buf, then we have removed a line + * with out a newline but added a blank line, so ADD_EOFNL. */ char origin = (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : @@ -1036,6 +1059,7 @@ static void set_data_from_blob( } else { map->data = ""; file->size = map->len = 0; + file->flags |= GIT_DIFF_FILE_NO_DATA; } } diff --git a/src/fileops.c b/src/fileops.c index d4def1a9ac5..cbe3d47820f 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -115,40 +115,26 @@ mode_t git_futils_canonical_mode(mode_t raw_mode) return 0; } -#define MAX_READ_STALLS 10 - int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) { - int stalls = MAX_READ_STALLS; + ssize_t read_size; git_buf_clear(buf); if (git_buf_grow(buf, len + 1) < 0) return -1; - buf->ptr[len] = '\0'; - - while (len > 0) { - ssize_t read_size = p_read(fd, buf->ptr + buf->size, len); - - if (read_size < 0) { - giterr_set(GITERR_OS, "Failed to read descriptor"); - return -1; - } - - if (read_size == 0) { - stalls--; + /* p_read loops internally to read len bytes */ + read_size = p_read(fd, buf->ptr, len); - if (!stalls) { - giterr_set(GITERR_OS, "Too many stalls reading descriptor"); - return -1; - } - } - - len -= read_size; - buf->size += read_size; + if (read_size < 0) { + giterr_set(GITERR_OS, "Failed to read descriptor"); + return -1; } + buf->ptr[read_size] = '\0'; + buf->size = read_size; + return 0; } diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index ef59b686f26..767b34392a7 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -89,7 +89,8 @@ int diff_line_fn( e->line_adds++; break; case GIT_DIFF_LINE_ADD_EOFNL: - assert(0); + /* technically not a line add, but we'll count it as such */ + e->line_adds++; break; case GIT_DIFF_LINE_DELETION: e->line_dels++; diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c index 56c25474168..23071e48b18 100644 --- a/tests-clar/diff/diffiter.c +++ b/tests-clar/diff/diffiter.c @@ -114,3 +114,88 @@ void test_diff_diffiter__iterate_files_and_hunks(void) git_diff_iterator_free(iter); git_diff_list_free(diff); } + +void test_diff_diffiter__max_size_threshold(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + git_diff_iterator *iter; + git_diff_delta *delta; + int error, file_count = 0, binary_count = 0, hunk_count = 0; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + + cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta); + + file_count++; + + hunk_count += git_diff_iterator_num_hunks_in_file(iter); + + assert(delta->binary == 0 || delta->binary == 1); + + binary_count += delta->binary; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + + cl_assert_equal_i(13, file_count); + cl_assert_equal_i(0, binary_count); + cl_assert_equal_i(8, hunk_count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); + + /* try again with low file size threshold */ + + file_count = 0; + binary_count = 0; + hunk_count = 0; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; + opts.max_size = 50; /* treat anything over 50 bytes as binary! */ + + cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta); + + file_count++; + + hunk_count += git_diff_iterator_num_hunks_in_file(iter); + + assert(delta->binary == 0 || delta->binary == 1); + + binary_count += delta->binary; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + + cl_assert_equal_i(13, file_count); + + /* Three files are over the 50 byte threshold: + * - staged_changes_file_deleted + * - staged_changes_modified_file + * - staged_new_file_modified_file + */ + cl_assert_equal_i(3, binary_count); + + cl_assert_equal_i(5, hunk_count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); + +} From c859184bb459d9801a394dc44f5b0b0e55453263 Mon Sep 17 00:00:00 2001 From: Vicent Marti Date: Tue, 11 Sep 2012 23:05:24 +0200 Subject: [PATCH 35/72] Properly handle p_reads --- src/amiga/map.c | 11 ++++------- src/blob.c | 19 +++++++++---------- src/filebuf.c | 9 +++++++-- src/fileops.c | 2 +- src/odb.c | 20 +++++++++++--------- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/amiga/map.c b/src/amiga/map.c index 2fb065c8b15..c601de7243d 100755 --- a/src/amiga/map.c +++ b/src/amiga/map.c @@ -24,18 +24,15 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs return -1; } - if((out->data = malloc(len))) { - p_lseek(fd, offset, SEEK_SET); - p_read(fd, out->data, len); - } + out->data = malloc(len); + GITERR_CHECK_ALLOC(out->data); - if (!out->data || (out->data == MAP_FAILED)) { - giterr_set(GITERR_OS, "Failed to mmap. Could not write data"); + if (p_lseek(fd, offset, SEEK_SET) < 0 || p_read(fd, out->data, len) != len) + giterr_set(GITERR_OS, "mmap emulation failed"); return -1; } out->len = len; - return 0; } diff --git a/src/blob.c b/src/blob.c index 699adec6b7f..6267ae7b2d0 100644 --- a/src/blob.c +++ b/src/blob.c @@ -68,6 +68,7 @@ static int write_file_stream( int fd, error; char buffer[4096]; git_odb_stream *stream = NULL; + ssize_t read_len, written = 0; if ((error = git_odb_open_wstream( &stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0) @@ -78,20 +79,18 @@ static int write_file_stream( return -1; } - while (!error && file_size > 0) { - ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); - - if (read_len < 0) { - giterr_set( - GITERR_OS, "Failed to create blob. Can't read whole file"); - error = -1; - } - else if (!(error = stream->write(stream, buffer, read_len))) - file_size -= read_len; + while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { + error = stream->write(stream, buffer, read_len); + written += read_len; } p_close(fd); + if (written != file_size || read_len < 0) { + giterr_set(GITERR_OS, "Failed to read file into stream"); + error = -1; + } + if (!error) error = stream->finalize_write(oid, stream); diff --git a/src/filebuf.c b/src/filebuf.c index cfc8528e62a..b9b908c8da1 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -73,7 +73,7 @@ static int lock_file(git_filebuf *file, int flags) if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) { git_file source; char buffer[2048]; - size_t read_bytes; + ssize_t read_bytes; source = p_open(file->path_original, O_RDONLY); if (source < 0) { @@ -83,13 +83,18 @@ static int lock_file(git_filebuf *file, int flags) return -1; } - while ((read_bytes = p_read(source, buffer, 2048)) > 0) { + while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { p_write(file->fd, buffer, read_bytes); if (file->digest) git_hash_update(file->digest, buffer, read_bytes); } p_close(source); + + if (read_bytes < 0) { + giterr_set(GITERR_OS, "Failed to read file '%s'", file->path_original); + return -1; + } } return 0; diff --git a/src/fileops.c b/src/fileops.c index cbe3d47820f..8ccf063d5d6 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -127,7 +127,7 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) /* p_read loops internally to read len bytes */ read_size = p_read(fd, buf->ptr, len); - if (read_size < 0) { + if (read_size != (ssize_t)len) { giterr_set(GITERR_OS, "Failed to read descriptor"); return -1; } diff --git a/src/odb.c b/src/odb.c index 0e03e40eefa..0d3d809f7bc 100644 --- a/src/odb.c +++ b/src/odb.c @@ -115,6 +115,7 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) int hdr_len; char hdr[64], buffer[2048]; git_hash_ctx *ctx; + ssize_t read_len; hdr_len = format_object_header(hdr, sizeof(hdr), size, type); @@ -123,19 +124,20 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) git_hash_update(ctx, hdr, hdr_len); - while (size > 0) { - ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); - - if (read_len < 0) { - git_hash_free_ctx(ctx); - giterr_set(GITERR_OS, "Error reading file"); - return -1; - } - + while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { git_hash_update(ctx, buffer, read_len); size -= read_len; } + /* If p_read returned an error code, the read obviously failed. + * If size is not zero, the file was truncated after we originally + * stat'd it, so we consider this a read failure too */ + if (read_len < 0 || size > 0) { + git_hash_free_ctx(ctx); + giterr_set(GITERR_OS, "Error reading file for hashing"); + return -1; + } + git_hash_final(out, ctx); git_hash_free_ctx(ctx); From 47bfa0be6d509b60eda92705b57d3f7ba89c1c6b Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 7 Sep 2012 13:27:49 -0700 Subject: [PATCH 36/72] Add git_repository_hashfile to hash with filters The existing `git_odb_hashfile` does not apply text filtering rules because it doesn't have a repository context to evaluate the correct rules to apply. This adds a new hashfile function that will apply repository-specific filters (based on config, attributes, and filename) before calculating the hash. --- include/git2/repository.h | 25 +++++++++++++++ src/crlf.c | 5 +-- src/repository.c | 65 ++++++++++++++++++++++++++++++++++++++ tests-clar/repo/hashfile.c | 55 ++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 tests-clar/repo/hashfile.c diff --git a/include/git2/repository.h b/include/git2/repository.h index f520d543363..ebea3b0d4d3 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -481,6 +481,31 @@ GIT_EXTERN(int) git_repository_message(char *buffer, size_t len, git_repository */ GIT_EXTERN(int) git_repository_message_remove(git_repository *repo); +/** + * Calculate hash of file using repository filtering rules. + * + * If you simply want to calculate the hash of a file on disk with no filters, + * you can just use the `git_odb_hashfile()` API. However, if you want to + * hash a file in the repository and you want to apply filtering rules (e.g. + * crlf filters) before generating the SHA, then use this function. + * + * @param out Output value of calculated SHA + * @param repo Repository pointer. NULL is allowed to just use global and + * system attributes for choosing filters. + * @param path Path to file on disk whose contents should be hashed. If the + * repository is not NULL, this can be a relative path. + * @param type The object type to hash as (e.g. GIT_OBJ_BLOB) + * @param as_path The path to use to look up filtering rules. If this is + * NULL, then the `path` parameter will be used instead. If + * this is passed as the empty string, then no filters will be + * applied when calculating the hash. + */ +GIT_EXTERN(int) git_repository_hashfile( + git_oid *out, + git_repository *repo, + const char *path, + git_otype type, + const char *as_path); /** @} */ GIT_END_DECL diff --git a/src/crlf.c b/src/crlf.c index 1b6898ba6b5..5e86b4eb66d 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -263,8 +263,9 @@ static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf return convert_line_endings(dest, source, workdir_ending); } -static int find_and_add_filter(git_vector *filters, git_repository *repo, const char *path, - int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source)) +static int find_and_add_filter( + git_vector *filters, git_repository *repo, const char *path, + int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source)) { struct crlf_attrs ca; struct crlf_filter *filter; diff --git a/src/repository.c b/src/repository.c index b9d180da450..ab139a723a5 100644 --- a/src/repository.c +++ b/src/repository.c @@ -17,6 +17,8 @@ #include "fileops.h" #include "config.h" #include "refs.h" +#include "filter.h" +#include "odb.h" #define GIT_FILE_CONTENT_PREFIX "gitdir:" @@ -1372,3 +1374,66 @@ int git_repository_message_remove(git_repository *repo) return error; } + +int git_repository_hashfile( + git_oid *out, + git_repository *repo, + const char *path, + git_otype type, + const char *as_path) +{ + int error; + git_vector filters = GIT_VECTOR_INIT; + git_file fd; + git_off_t len; + git_buf full_path = GIT_BUF_INIT; + + assert(out && path); /* repo and as_path can be NULL */ + + error = git_path_join_unrooted( + &full_path, path, repo ? git_repository_workdir(repo) : NULL, NULL); + if (error < 0) + return error; + + if (!as_path) + as_path = path; + + /* passing empty string for "as_path" indicated --no-filters */ + if (strlen(as_path) > 0) { + error = git_filters_load(&filters, repo, as_path, GIT_FILTER_TO_ODB); + if (error < 0) + return error; + } else { + error = 0; + } + + /* at this point, error is a count of the number of loaded filters */ + + fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) { + error = fd; + goto cleanup; + } + + len = git_futils_filesize(fd); + if (len < 0) { + error = len; + goto cleanup; + } + + if (!git__is_sizet(len)) { + giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + error = -1; + goto cleanup; + } + + error = git_odb__hashfd_filtered(out, fd, len, type, &filters); + +cleanup: + p_close(fd); + git_filters_free(&filters); + git_buf_free(&full_path); + + return error; +} + diff --git a/tests-clar/repo/hashfile.c b/tests-clar/repo/hashfile.c new file mode 100644 index 00000000000..9fa0d9b0ef7 --- /dev/null +++ b/tests-clar/repo/hashfile.c @@ -0,0 +1,55 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +static git_repository *_repo; + +void test_repo_hashfile__initialize(void) +{ + _repo = cl_git_sandbox_init("status"); +} + +void test_repo_hashfile__cleanup(void) +{ + cl_git_sandbox_cleanup(); + _repo = NULL; +} + +void test_repo_hashfile__simple(void) +{ + git_oid a, b; + git_buf full = GIT_BUF_INIT; + + cl_git_pass(git_odb_hashfile(&a, "status/current_file", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "current_file", GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_equal(&a, &b)); + + cl_git_pass(git_buf_joinpath(&full, git_repository_workdir(_repo), "current_file")); + + cl_git_pass(git_odb_hashfile(&a, full.ptr, GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_equal(&a, &b)); + + git_buf_free(&full); +} + +void test_repo_hashfile__filtered(void) +{ + git_oid a, b; + git_config *config; + + cl_git_pass(git_repository_config(&config, _repo)); + cl_git_pass(git_config_set_bool(config, "core.autocrlf", true)); + git_config_free(config); + + cl_git_append2file("status/.gitattributes", "*.txt text\n*.bin binary\n\n"); + + cl_git_mkfile("status/testfile.txt", "content\r\n"); /* Content with CRLF */ + + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_cmp(&a, &b)); /* not equal */ + + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, "testfile.bin")); + cl_assert(git_oid_equal(&a, &b)); /* equal when 'binary' 'as_file' name is used */ +} From a13fb55afdbf9d74c3d4b6aa76476a005da49486 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 11 Sep 2012 17:26:21 -0700 Subject: [PATCH 37/72] Add tests and improve param checks Fixed some minor `git_repository_hashfile` issues: - Fixed incorrect doc (saying that repo could be NULL) - Added checking of object type value to acceptable ones - Added more tests for various parameter permutations --- include/git2/repository.h | 3 +-- src/odb.c | 5 +++++ src/repository.c | 7 ++++++- tests-clar/repo/hashfile.c | 41 ++++++++++++++++++++++++++++++++++---- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/include/git2/repository.h b/include/git2/repository.h index ebea3b0d4d3..32ec58dae45 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -490,8 +490,7 @@ GIT_EXTERN(int) git_repository_message_remove(git_repository *repo); * crlf filters) before generating the SHA, then use this function. * * @param out Output value of calculated SHA - * @param repo Repository pointer. NULL is allowed to just use global and - * system attributes for choosing filters. + * @param repo Repository pointer * @param path Path to file on disk whose contents should be hashed. If the * repository is not NULL, this can be a relative path. * @param type The object type to hash as (e.g. GIT_OBJ_BLOB) diff --git a/src/odb.c b/src/odb.c index 0d3d809f7bc..943ffedaa6f 100644 --- a/src/odb.c +++ b/src/odb.c @@ -117,6 +117,11 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) git_hash_ctx *ctx; ssize_t read_len; + if (!git_object_typeisloose(type)) { + giterr_set(GITERR_INVALID, "Invalid object type for hash"); + return -1; + } + hdr_len = format_object_header(hdr, sizeof(hdr), size, type); ctx = git_hash_new_ctx(); diff --git a/src/repository.c b/src/repository.c index ab139a723a5..bcc6b150398 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1388,7 +1388,12 @@ int git_repository_hashfile( git_off_t len; git_buf full_path = GIT_BUF_INIT; - assert(out && path); /* repo and as_path can be NULL */ + assert(out && path && repo); /* as_path can be NULL */ + + /* At some point, it would be nice if repo could be NULL to just + * apply filter rules defined in system and global files, but for + * now that is not possible because git_filters_load() needs it. + */ error = git_path_join_unrooted( &full_path, path, repo ? git_repository_workdir(repo) : NULL, NULL); diff --git a/tests-clar/repo/hashfile.c b/tests-clar/repo/hashfile.c index 9fa0d9b0ef7..129e5d371b9 100644 --- a/tests-clar/repo/hashfile.c +++ b/tests-clar/repo/hashfile.c @@ -19,16 +19,22 @@ void test_repo_hashfile__simple(void) git_oid a, b; git_buf full = GIT_BUF_INIT; + /* hash with repo relative path */ cl_git_pass(git_odb_hashfile(&a, "status/current_file", GIT_OBJ_BLOB)); cl_git_pass(git_repository_hashfile(&b, _repo, "current_file", GIT_OBJ_BLOB, NULL)); cl_assert(git_oid_equal(&a, &b)); cl_git_pass(git_buf_joinpath(&full, git_repository_workdir(_repo), "current_file")); + /* hash with full path */ cl_git_pass(git_odb_hashfile(&a, full.ptr, GIT_OBJ_BLOB)); cl_git_pass(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJ_BLOB, NULL)); cl_assert(git_oid_equal(&a, &b)); + /* hash with invalid type */ + cl_git_fail(git_odb_hashfile(&a, full.ptr, GIT_OBJ_ANY)); + cl_git_fail(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJ_OFS_DELTA, NULL)); + git_buf_free(&full); } @@ -43,13 +49,40 @@ void test_repo_hashfile__filtered(void) cl_git_append2file("status/.gitattributes", "*.txt text\n*.bin binary\n\n"); - cl_git_mkfile("status/testfile.txt", "content\r\n"); /* Content with CRLF */ + /* create some sample content with CRLF in it */ + cl_git_mkfile("status/testfile.txt", "content\r\n"); + cl_git_mkfile("status/testfile.bin", "other\r\nstuff\r\n"); + /* not equal hashes because of filtering */ cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB)); cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, NULL)); - cl_assert(git_oid_cmp(&a, &b)); /* not equal */ + cl_assert(git_oid_cmp(&a, &b)); + + /* equal hashes because filter is binary */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_equal(&a, &b)); + /* equal hashes when 'as_file' points to binary filtering */ cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB)); - cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, "testfile.bin")); - cl_assert(git_oid_equal(&a, &b)); /* equal when 'binary' 'as_file' name is used */ + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, "foo.bin")); + cl_assert(git_oid_equal(&a, &b)); + + /* not equal hashes when 'as_file' points to text filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, "foo.txt")); + cl_assert(git_oid_cmp(&a, &b)); + + /* equal hashes when 'as_file' is empty and turns off filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, "")); + cl_assert(git_oid_equal(&a, &b)); + + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, "")); + cl_assert(git_oid_equal(&a, &b)); + + /* some hash type failures */ + cl_git_fail(git_odb_hashfile(&a, "status/testfile.txt", 0)); + cl_git_fail(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_ANY, NULL)); } From ab8a0402aeac9767e5bb1b022a6c9ad27cf78f32 Mon Sep 17 00:00:00 2001 From: David Michael Barr Date: Wed, 12 Sep 2012 14:26:31 +1000 Subject: [PATCH 38/72] odb_pack: try lookup before refreshing packs This reduces the rate of syscalls for the common case of sequences of object reads from the same pack. Best of 5 timings for libgit2_clar before this patch: real 0m5.375s user 0m0.392s sys 0m3.564s After applying this patch: real 0m5.285s user 0m0.356s sys 0m3.544s 0.6% improvement in system time. 9.2% improvement in user time. 1.7% improvement in elapsed time. Confirmed a 0.6% reduction in number of system calls with strace. Expect greater improvement for graph-traversal with large packs. --- src/odb_pack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/odb_pack.c b/src/odb_pack.c index 6e3d3eefd24..d33d064560f 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -268,13 +268,13 @@ static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backen int error; unsigned int i; - if ((error = packfile_refresh_all(backend)) < 0) - return error; - if (backend->last_found && git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0) return 0; + if ((error = packfile_refresh_all(backend)) < 0) + return error; + for (i = 0; i < backend->packs.length; ++i) { struct git_pack_file *p; From 13faa77c57d3fe9ddcfbfdf35c0cdd631521a280 Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Thu, 13 Sep 2012 17:57:45 +0200 Subject: [PATCH 39/72] Fix -Wuninitialized warning --- src/blob.c | 2 +- src/odb.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blob.c b/src/blob.c index a5a0b6dde65..6137746e148 100644 --- a/src/blob.c +++ b/src/blob.c @@ -68,7 +68,7 @@ static int write_file_stream( int fd, error; char buffer[4096]; git_odb_stream *stream = NULL; - ssize_t read_len, written = 0; + ssize_t read_len = -1, written = 0; if ((error = git_odb_open_wstream( &stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0) diff --git a/src/odb.c b/src/odb.c index 0d3d809f7bc..c027c12c386 100644 --- a/src/odb.c +++ b/src/odb.c @@ -115,7 +115,7 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) int hdr_len; char hdr[64], buffer[2048]; git_hash_ctx *ctx; - ssize_t read_len; + ssize_t read_len = -1; hdr_len = format_object_header(hdr, sizeof(hdr), size, type); From 49d34c1c0c706eea09380b2165bb3ad4e506dc30 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Sep 2012 13:17:38 -0700 Subject: [PATCH 40/72] Fix problems in diff iterator record chaining There is a bug in building the linked list of line records in the diff iterator and also an off by one element error in the hunk counts. This fixes both of these, adds some test data with more complex sets of hunk and line diffs to exercise this code better. --- src/diff_output.c | 44 ++++++--- tests-clar/diff/tree.c | 82 ++++++++++++++++ tests-clar/diff/workdir.c | 91 ++++++++++++++++++ tests-clar/resources/diff/.gitted/HEAD | 1 + tests-clar/resources/diff/.gitted/config | 6 ++ tests-clar/resources/diff/.gitted/description | 1 + tests-clar/resources/diff/.gitted/index | Bin 0 -> 225 bytes .../resources/diff/.gitted/info/exclude | 6 ++ tests-clar/resources/diff/.gitted/logs/HEAD | 2 + .../diff/.gitted/logs/refs/heads/master | 2 + .../29/ab7053bb4dde0298e03e2c179e890b7dd465a7 | Bin 0 -> 730 bytes .../3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 | Bin 0 -> 1108 bytes .../54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 | Bin 0 -> 1110 bytes .../7a/9e0b02e63179929fed24f0a3e0f19168114d10 | Bin 0 -> 160 bytes .../7b/808f723a8ca90df319682c221187235af76693 | Bin 0 -> 922 bytes .../88/789109439c1e1c3cd45224001edee5304ed53c | 1 + .../cb/8294e696339863df760b2ff5d1e275bee72455 | Bin 0 -> 86 bytes .../d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 | 1 + .../resources/diff/.gitted/refs/heads/master | 1 + tests-clar/resources/diff/another.txt | 38 ++++++++ tests-clar/resources/diff/readme.txt | 36 +++++++ 21 files changed, 299 insertions(+), 13 deletions(-) create mode 100644 tests-clar/resources/diff/.gitted/HEAD create mode 100644 tests-clar/resources/diff/.gitted/config create mode 100644 tests-clar/resources/diff/.gitted/description create mode 100644 tests-clar/resources/diff/.gitted/index create mode 100644 tests-clar/resources/diff/.gitted/info/exclude create mode 100644 tests-clar/resources/diff/.gitted/logs/HEAD create mode 100644 tests-clar/resources/diff/.gitted/logs/refs/heads/master create mode 100644 tests-clar/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 create mode 100644 tests-clar/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 create mode 100644 tests-clar/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 create mode 100644 tests-clar/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 create mode 100644 tests-clar/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 create mode 100644 tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c create mode 100644 tests-clar/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 create mode 100644 tests-clar/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 create mode 100644 tests-clar/resources/diff/.gitted/refs/heads/master create mode 100644 tests-clar/resources/diff/another.txt create mode 100644 tests-clar/resources/diff/readme.txt diff --git a/src/diff_output.c b/src/diff_output.c index ea40c335521..50e3cc1de43 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -1204,13 +1204,17 @@ static int diffiter_hunk_cb( if (info->last_hunk) info->last_hunk->next = hunk; info->last_hunk = hunk; + info->last_line = NULL; memcpy(&hunk->range, range, sizeof(hunk->range)); iter->hunk_count++; - if (iter->hunk_head == NULL) - iter->hunk_curr = iter->hunk_head = hunk; + /* adding first hunk to list */ + if (iter->hunk_head == NULL) { + iter->hunk_head = hunk; + iter->hunk_curr = NULL; + } return 0; } @@ -1345,9 +1349,14 @@ int git_diff_iterator_num_hunks_in_file(git_diff_iterator *iter) int git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iter) { int error = diffiter_do_diff_file(iter); - if (!error && iter->hunk_curr) - error = iter->hunk_curr->line_count; - return error; + if (error) + return error; + + if (iter->hunk_curr) + return iter->hunk_curr->line_count; + if (iter->hunk_head) + return iter->hunk_head->line_count; + return 0; } int git_diff_iterator_next_file( @@ -1386,7 +1395,7 @@ int git_diff_iterator_next_file( } if (iter->ctxt.delta == NULL) { - iter->hunk_curr = NULL; + iter->hunk_curr = iter->hunk_head = NULL; iter->line_curr = NULL; } @@ -1409,11 +1418,13 @@ int git_diff_iterator_next_hunk( return error; if (iter->hunk_curr == NULL) { - if (range_ptr) *range_ptr = NULL; - if (header) *header = NULL; - if (header_len) *header_len = 0; - iter->line_curr = NULL; - return GIT_ITEROVER; + if (iter->hunk_head == NULL) + goto no_more_hunks; + iter->hunk_curr = iter->hunk_head; + } else { + if (iter->hunk_curr->next == NULL) + goto no_more_hunks; + iter->hunk_curr = iter->hunk_curr->next; } range = &iter->hunk_curr->range; @@ -1436,9 +1447,16 @@ int git_diff_iterator_next_hunk( } iter->line_curr = iter->hunk_curr->line_head; - iter->hunk_curr = iter->hunk_curr->next; return error; + +no_more_hunks: + if (range_ptr) *range_ptr = NULL; + if (header) *header = NULL; + if (header_len) *header_len = 0; + iter->line_curr = NULL; + + return GIT_ITEROVER; } int git_diff_iterator_next_line( @@ -1453,7 +1471,7 @@ int git_diff_iterator_next_line( return error; /* if the user has not called next_hunk yet, call it implicitly (OK?) */ - if (iter->hunk_curr == iter->hunk_head) { + if (iter->hunk_curr == NULL) { error = git_diff_iterator_next_hunk(NULL, NULL, NULL, iter); if (error) return error; diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c index 3003374a52b..f5e72cadc9c 100644 --- a/tests-clar/diff/tree.c +++ b/tests-clar/diff/tree.c @@ -256,3 +256,85 @@ void test_diff_tree__merge(void) git_diff_list_free(diff1); } + +void test_diff_tree__larger_hunks(void) +{ + const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; + const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; + git_tree *a, *b; + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + git_diff_iterator *iter = NULL; + git_diff_delta *delta; + diff_expects exp; + int error, num_files = 0; + + g_repo = cl_git_sandbox_init("diff"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 0; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + /* this should be exact */ + cl_assert(git_diff_iterator_progress(iter) == 0.0f); + + /* You wouldn't actually structure an iterator loop this way, but + * I have here for testing purposes of the return value + */ + while (!(error = git_diff_iterator_next_file(&delta, iter))) { + git_diff_range *range; + const char *header; + size_t header_len; + int actual_hunks = 0, num_hunks; + float expected_progress; + + num_files++; + + expected_progress = (float)num_files / 2.0f; + cl_assert(expected_progress == git_diff_iterator_progress(iter)); + + num_hunks = git_diff_iterator_num_hunks_in_file(iter); + + while (!(error = git_diff_iterator_next_hunk( + &range, &header, &header_len, iter))) + { + int actual_lines = 0; + int num_lines = git_diff_iterator_num_lines_in_hunk(iter); + char origin; + const char *line; + size_t line_len; + + while (!(error = git_diff_iterator_next_line( + &origin, &line, &line_len, iter))) + { + actual_lines++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(actual_lines, num_lines); + + actual_hunks++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(actual_hunks, num_hunks); + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(2, num_files); + cl_assert(git_diff_iterator_progress(iter) == 1.0f); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index eac7eb87dff..40a8885442b 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -670,3 +670,94 @@ void test_diff_workdir__eof_newline_changes(void) * * Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR */ + + +void test_diff_workdir__larger_hunks(void) +{ + const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; + const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; + git_tree *a, *b; + git_diff_options opts = {0}; + int i, error; + + g_repo = cl_git_sandbox_init("diff"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 0; + + for (i = 0; i <= 2; ++i) { + git_diff_list *diff = NULL; + git_diff_iterator *iter = NULL; + git_diff_delta *delta; + int num_files = 0; + + /* okay, this is a bit silly, but oh well */ + switch (i) { + case 0: + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + break; + case 1: + cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); + break; + case 2: + cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, b, &diff)); + break; + } + + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + cl_assert(git_diff_iterator_progress(iter) == 0.0f); + + while (!(error = git_diff_iterator_next_file(&delta, iter))) { + git_diff_range *range; + const char *header; + size_t header_len; + int actual_hunks = 0, num_hunks; + float expected_progress; + + num_files++; + + expected_progress = (float)num_files / 2.0f; + cl_assert(expected_progress == git_diff_iterator_progress(iter)); + + num_hunks = git_diff_iterator_num_hunks_in_file(iter); + + while (!(error = git_diff_iterator_next_hunk( + &range, &header, &header_len, iter))) + { + int actual_lines = 0; + int num_lines = git_diff_iterator_num_lines_in_hunk(iter); + char origin; + const char *line; + size_t line_len; + + while (!(error = git_diff_iterator_next_line( + &origin, &line, &line_len, iter))) + { + actual_lines++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(actual_lines, num_lines); + + actual_hunks++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(actual_hunks, num_hunks); + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(2, num_files); + cl_assert(git_diff_iterator_progress(iter) == 1.0f); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); + } + + git_tree_free(a); + git_tree_free(b); +} diff --git a/tests-clar/resources/diff/.gitted/HEAD b/tests-clar/resources/diff/.gitted/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/diff/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/diff/.gitted/config b/tests-clar/resources/diff/.gitted/config new file mode 100644 index 00000000000..77a27ef1d58 --- /dev/null +++ b/tests-clar/resources/diff/.gitted/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = false diff --git a/tests-clar/resources/diff/.gitted/description b/tests-clar/resources/diff/.gitted/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/diff/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/diff/.gitted/index b/tests-clar/resources/diff/.gitted/index new file mode 100644 index 0000000000000000000000000000000000000000..e1071874e268731d83cb6c8d98c0eeb17d1019f8 GIT binary patch literal 225 zcmZ?q402{*U|<5_fFR|mK$-zY^D!{6FuLfTWMF7q!oa}z6(}Xbz`&6cl2aTnws?#0 z^cMCQ9p2SiC-+NsGH@s6<(Fin7U`8#lz5}$28-C zosRgtPVU+(smmF-ic%9(a#N9vV+aXybp;wI$zY^lz|~PPk<)pOoQ%yCpe=Iuo*MXF bwORhx;$F$KQ|EaCrZ0Y!VBQ#TE 1347559804 -0700 commit (initial): initial commit +d70d245ed97ed2aa596dd1af6536e4bfdb047b69 7a9e0b02e63179929fed24f0a3e0f19168114d10 Russell Belfer 1347560491 -0700 commit: some changes diff --git a/tests-clar/resources/diff/.gitted/logs/refs/heads/master b/tests-clar/resources/diff/.gitted/logs/refs/heads/master new file mode 100644 index 00000000000..8c6f6fd18b6 --- /dev/null +++ b/tests-clar/resources/diff/.gitted/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 d70d245ed97ed2aa596dd1af6536e4bfdb047b69 Russell Belfer 1347559804 -0700 commit (initial): initial commit +d70d245ed97ed2aa596dd1af6536e4bfdb047b69 7a9e0b02e63179929fed24f0a3e0f19168114d10 Russell Belfer 1347560491 -0700 commit: some changes diff --git a/tests-clar/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 b/tests-clar/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 new file mode 100644 index 0000000000000000000000000000000000000000..94f9a676defa4c774ba2a2ff227c2e3c7f668ae2 GIT binary patch literal 730 zcmV<00ww);0ZmlPlG8v8#WywNi0pFEt#ImzXU-_h zI#D`sM#15H$SY|XCDYftPmL}G00Nlc_l(-isT)H&(xkn_ebaa^fa}G`F(q}nWIl?~ zstXmRk}?G+lfQM|H{C%vt-T+ca?6 z?5<9Da~CCa(R_jt@NdveMXWpsHK@rP8=K%e2Bogjt9oADaR2h_5cmEXf{pff91fV& zp6=X2p(K~bQ8+@i+E4G(141`h8i(_t!(G!lShvW8rzmESXO~bgrIx!>9R|P(eoDEB zB#+Rv^Uu`ODaqDo3lprVCGh9I>roK3>O;SI02&$T4XMr)!rlg()wo5X-u;{<2cKnnccL)3X*Y<d0NE z(;Nf(m`>uWgB`H{?vbrlWU?K#+tW~eXhJ$`JVTf7(t;pN3fMjjL+G&i+slx%=37KR z$-cf&c#V7=ya7%-1PM4;?mj>_5@Kt;2j1Pt$zK%r2wZ_)XH%D2GWcN@_c4-{qHs=4 MDEp`X0Iq4%(`Pbl!2kdN literal 0 HcmV?d00001 diff --git a/tests-clar/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 b/tests-clar/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 new file mode 100644 index 0000000000000000000000000000000000000000..9fed523dc6c22b4dcdb19e208f63df5e4ee8d9da GIT binary patch literal 1108 zcmV-a1graa0d-YNZ`?Kz?wP-0ZoVkn2N0mw)Ic9V>K5H1IghAWi8VzEB(=)s*Y_Jz zvNtIT7_cpg^Y~uueeCIn@87=t<`-AUWx8-KC;I5hM0nCs5KjI^>^;Rr5*HUkCQd^5 z?-J821Nmt2{?^gxt}cuQm6)b=6v~U>Jr7q7h4Ft3WF?fu-hIZ4e9J}VOl9Ii7KIDG zL$`^qLM0j-=XnjzauM2eB&ICsDv5HOWt$|)$rzn{<4pbPe8Dl=VtUqAWfFSt!upAx zB?a*v&B6f_wuU%1;n>83_L9p*Y;jkq{lnmeJSWVYWQp08n4AEf6lWUvhVpV!gcP$a zxl$yM3-s8TB*SW36D*8jCqajJp|pnLW)$I^qjgwnXj@Hg#42yRpt5;p)o&p_#p zR(xllXriHj=zPfUb39AkZz!dQ6$q4!$ZMT#ZJ1Qd}0eboak3<4e{!86;_kB@o@uG zN9#dCoLyiDwYq^J$$5dsPNb-!*1ULB(S{G&(UpgabvEpC1e(^b18j%)DpTEgS@T3N zd5eNF#dQQ|5lwfM)emoRh2>U1P>97~(V*JL+qWWhFLs0K<7PPzKWG#nfMv-9h0RU0~XnM_(uD6rQ`2 zXu2!(Ys0>}zdPa#?op?fE#lVz`)m7|$KZ+zL~jUihRG`fUE6F8_>$Zph#=rKxOP!s zRnutt163E#2(U)-o}F!3Wo@^!LmQ_>e4XRCa}E<|uHoY$Zb3;qau#ejC>oOT@CGJsXpH>OP_Al5~ z8g;DN>)!MTIyhcy?Ir_sE#Fj1sQ)5e2o-nY!Suj|cb@}^DmI-$dtiPHvIlEb)o#oh zPHJVn)$jtJ9c+WZ_TU0ZRsCUO^A%{j$d(lUf!a^>ceRuEYsQ5Q=<20`Ytq>|PrLiL a=q@$DGzWGJDegG}{ni@!2Aa#pgkvy-cJ&83%3M6&P<=1zX zlyO4Cs!uIla7LL@;74dDK3(@xEL~V z62gC%m}VKsM~nBjjz)KNVKk`3fu?m7%8TFw9jOl zDQR1BrAQzb=&>_NhSjzv0E}TLL5Fyuw1(nl6ycnsbpSOqRFfM4<&76KH=e_|f~$O@ zb7Lh6*opRbyy_ka=~=nj_&QJ zGv-#}EhgK*C@3zpE{dj}^%3*yK*yMUa&JpJAZs?$7&Vt0SeUj5!;&CPA*99 zYN5Z@{`N}dO;?5mFAfK=vwA@JJZiQ@Xv{5w_;8%8TWI(poXg!Z0~j zg6(6o;PWZMeAS?3g4Xe)5c*0DD5eZUb8@i)Q8B!Eb!qi%h)4(?OfztxpTGP4mv7-# zwBFnRnZWo#t*5*8|y#h^d>HY+t^Fgd&v)cAO#jOrjxozQ~isIM`R zb+M@f8P1D=GGOKx&-|zCP72p#z^09P^mUR>>A5?Jrn^F6_pCsGsib%xn%$8Wa1X^~ z$`ys-8zyr_y=t_hDhN{eZa-=dYo(ux^w>1=-Dr1PmJK@#9$${aJ4Wi(EXcDN0|7Ixd~VrJ8H_c!*n2RUJ7Cxj0B03WY%t zGYpaJB*p+EsAfHc4d6Sj6TQs_eAU?2*W4ql$xet$qz>$x)o{vo2JeY`l-Z|Mg<(A} z^j1=ItlH~7mB0ofNo(yXfZetLR^m|q#anO^?!-fi0~g+d2uxHr=oH!m^J9>m{VMxC zIcj=p*66K<7x?VhAr#ggT%h@C5Fj>Rk+!>FN%0@p@kD=Dzk9!CT-czlDht=7vvrL#>HAb?rXwk(MCT9#GYGDy)k%XNs>YJKu9XKExa>6kPo0MGi%sP$USud1F z&PXBqsM68r+TeZcyDSU5WErKwz2)ch>GP3qrLGUqmV)z0QNtAjqB`mM*H87|QtfpW OD0A8-+0_qJbw!$C0Z#n@ literal 0 HcmV?d00001 diff --git a/tests-clar/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 b/tests-clar/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 new file mode 100644 index 0000000000000000000000000000000000000000..2fd266be66d9951a4bb7c6c9dd3b94318a678463 GIT binary patch literal 922 zcmV;L17-Yp0cBN9kJ~m7?U}z~ZrcK011%5~=&{=-y|jlUK+q#4jV)#=jzChYD5w00 z{KEW_yhqvIEqbwGc{qIYK1N-%PJaINmsdZ$A64FjDkt@I$trd9RkHdRH<|pS7QvsT z`ZS22eMVunNs{`E0)5iFiPcfFynFcD*;3I!(I)U*kuLkxk0G69vO(j~Io^flwiaPe zO}#DIpK;G-i&T@*KdDShtYMBS(Rv>*ZBWzD^*%|bPE!q8fg+?qXT?XVVkc4Gk2|KL zaJnAT)L`i#ljbw99NNu*qkN1bu8vl4S4g-59R|!5ZJyY$HV%sRmWBfS??;R_sZSR$ zL`^`00UVkaw0YFuoO_)^&;C(Mkq1jvKUI0RhT8Zz$^&7k_{(q{y^>5&h<0I6e~{j$ ziF~<}u2RFKWY1WMPVDw54 zxq^%p>|*M-7WMvjU(o%5LXxSY{6(pr2cUC&T>Vx6JXIgS5$4~35&xWx`k9-v4NGH& zI_%zQvT}y~*46SO?!|N`%Tl#G!z#3#kFHEG5nN}Kj8%i*NC&@o9GG8U9KFN-fQKj8 z?nZn9r5DQf4$U`wdW`}A-DP!bM+d_y#hOd0cTqo4T8#Hn5qcVM>IjF{RW&gW(}ADK zHOql*8g##!^05T`TyH%qa?^Mib`Ag|BZ?t4gJQ5hLYb}j4)OT&CS0@lZMbJq?1i}K zcWBuTpDFEKao``qa5`EEgYMb*xRi<%ARx^G_u@MYW~mh-Qmp*;vc!@7u8g-AC0L;aTytcHZ=Ui4e*~M-~{Td zY_*FCyP-Af_?ue>JE39&(cp-mVpKJ*FZ^Vv=_)3=2q{CvX{x-DCf_mffPE2Rj(|4W z+IG~%9f}+S;7zC(*%ZrehY!A+9*y07Q*jaVV2dQ6_#O*ojsa8_o7$k*Sv#nUDrGBG zC#;!nU*%0|7t)i(CrHQLx4^xVq3sY(r_f_lH>{eo<$I(%>9M_0_P0m708e{l1%0rL whX832f`X}#7U1qjpZEr#4S*HhoBi4n)(m{G+chcRBI4zfT&PF)9c<)o4UJ^Y_5c6? literal 0 HcmV?d00001 diff --git a/tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c b/tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c new file mode 100644 index 00000000000..7598b591411 --- /dev/null +++ b/tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c @@ -0,0 +1 @@ +x+)JMU07g040031QHÌË/ÉH-Ò+©(aÉ)Ž[¼Åwz {Œïj­“û%;¡ÊŠRSrSÁª4Wïö½Ç4ãŽø¼NîÚ+©Ë¶a \ No newline at end of file diff --git a/tests-clar/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 b/tests-clar/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 new file mode 100644 index 0000000000000000000000000000000000000000..86ebe04fed9f553f544550a852412fde027e28cf GIT binary patch literal 86 zcmV-c0IC0Y0V^p=O;s>AXD~D{Ff%bxNX*MG$w)2IE2$`9u!}yuRx9J_o`j{=%^mNS sT1i#yaEB@@N=;13O$Do}Zs;$v>RHMASu#UMNw8fx>U-K`01 Date: Thu, 13 Sep 2012 22:22:40 +0200 Subject: [PATCH 41/72] refspec: No remote tracking ref from a fetchspec-less remote --- src/branch.c | 8 +++++--- tests-clar/network/remotelocal.c | 4 ++-- tests-clar/network/remotes.c | 4 ++-- tests-clar/refs/branches/foreach.c | 4 ++-- tests-clar/refs/branches/tracking.c | 11 +++++++++++ tests-clar/refs/foreachglob.c | 4 ++-- tests-clar/resources/testrepo.git/config | 5 +++++ .../resources/testrepo.git/refs/heads/cannot-fetch | 1 + 8 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 tests-clar/resources/testrepo.git/refs/heads/cannot-fetch diff --git a/src/branch.c b/src/branch.c index cd5c10ede16..103dfe6212f 100644 --- a/src/branch.c +++ b/src/branch.c @@ -248,9 +248,11 @@ int git_branch_tracking( goto cleanup; refspec = git_remote_fetchspec(remote); - if (refspec == NULL) { - error = GIT_ENOTFOUND; - goto cleanup; + if (refspec == NULL + || refspec->src == NULL + || refspec->dst == NULL) { + error = GIT_ENOTFOUND; + goto cleanup; } if (git_refspec_transform_r(&buf, refspec, merge_name) < 0) diff --git a/tests-clar/network/remotelocal.c b/tests-clar/network/remotelocal.c index 63016db5f4e..3ff6197485f 100644 --- a/tests-clar/network/remotelocal.c +++ b/tests-clar/network/remotelocal.c @@ -107,7 +107,7 @@ void test_network_remotelocal__retrieve_advertised_references(void) cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); - cl_assert_equal_i(how_many_refs, 25); + cl_assert_equal_i(how_many_refs, 26); } void test_network_remotelocal__retrieve_advertised_references_from_spaced_repository(void) @@ -121,7 +121,7 @@ void test_network_remotelocal__retrieve_advertised_references_from_spaced_reposi cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); - cl_assert_equal_i(how_many_refs, 25); + cl_assert_equal_i(how_many_refs, 26); git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ remote = NULL; diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index f1d6f47c6e2..c7ee863e78e 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -186,13 +186,13 @@ void test_network_remotes__list(void) git_config *cfg; cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 2); + cl_assert(list.count == 3); git_strarray_free(&list); cl_git_pass(git_repository_config(&cfg, _repo)); cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 3); + cl_assert(list.count == 4); git_strarray_free(&list); git_config_free(cfg); diff --git a/tests-clar/refs/branches/foreach.c b/tests-clar/refs/branches/foreach.c index aca11ecd981..92d5b1f651e 100644 --- a/tests-clar/refs/branches/foreach.c +++ b/tests-clar/refs/branches/foreach.c @@ -47,7 +47,7 @@ static void assert_retrieval(unsigned int flags, unsigned int expected_count) void test_refs_branches_foreach__retrieve_all_branches(void) { - assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 13); + assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 14); } void test_refs_branches_foreach__retrieve_remote_branches(void) @@ -57,7 +57,7 @@ void test_refs_branches_foreach__retrieve_remote_branches(void) void test_refs_branches_foreach__retrieve_local_branches(void) { - assert_retrieval(GIT_BRANCH_LOCAL, 11); + assert_retrieval(GIT_BRANCH_LOCAL, 12); } struct expectations { diff --git a/tests-clar/refs/branches/tracking.c b/tests-clar/refs/branches/tracking.c index 8f70194377d..9cf435e8839 100644 --- a/tests-clar/refs/branches/tracking.c +++ b/tests-clar/refs/branches/tracking.c @@ -67,3 +67,14 @@ void test_refs_branches_tracking__trying_to_retrieve_a_remote_tracking_reference git_reference_free(branch); } + +void test_refs_branches_tracking__trying_to_retrieve_a_remote_tracking_reference_from_a_branch_with_no_fetchspec_returns_GIT_ENOTFOUND(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/cannot-fetch")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_tracking(&tracking, branch)); + + git_reference_free(branch); +} diff --git a/tests-clar/refs/foreachglob.c b/tests-clar/refs/foreachglob.c index 054846fe68d..12134293335 100644 --- a/tests-clar/refs/foreachglob.c +++ b/tests-clar/refs/foreachglob.c @@ -46,7 +46,7 @@ static void assert_retrieval(const char *glob, unsigned int flags, int expected_ void test_refs_foreachglob__retrieve_all_refs(void) { /* 8 heads (including one packed head) + 1 note + 2 remotes + 6 tags */ - assert_retrieval("*", GIT_REF_LISTALL, 20); + assert_retrieval("*", GIT_REF_LISTALL, 21); } void test_refs_foreachglob__retrieve_remote_branches(void) @@ -56,7 +56,7 @@ void test_refs_foreachglob__retrieve_remote_branches(void) void test_refs_foreachglob__retrieve_local_branches(void) { - assert_retrieval("refs/heads/*", GIT_REF_LISTALL, 11); + assert_retrieval("refs/heads/*", GIT_REF_LISTALL, 12); } void test_refs_foreachglob__retrieve_partially_named_references(void) diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config index 04ab38776e3..54ff6109bcc 100644 --- a/tests-clar/resources/testrepo.git/config +++ b/tests-clar/resources/testrepo.git/config @@ -6,6 +6,8 @@ [remote "test"] url = git://github.com/libgit2/libgit2 fetch = +refs/heads/*:refs/remotes/test/* +[remote "joshaber"] + url = git://github.com/libgit2/libgit2 [remote "test_with_pushurl"] url = git://github.com/libgit2/fetchlibgit2 @@ -18,3 +20,6 @@ [branch "track-local"] remote = . merge = refs/heads/master +[branch "cannot-fetch"] + remote = joshaber + merge = refs/heads/cannot-fetch diff --git a/tests-clar/resources/testrepo.git/refs/heads/cannot-fetch b/tests-clar/resources/testrepo.git/refs/heads/cannot-fetch new file mode 100644 index 00000000000..aab87e5e723 --- /dev/null +++ b/tests-clar/resources/testrepo.git/refs/heads/cannot-fetch @@ -0,0 +1 @@ +a4a7dce85cf63874e984719f4fdd239f5145052f From 12b6af1718f6f2e1da02870f1a5f5817bed77c0c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Sep 2012 14:15:07 -0700 Subject: [PATCH 42/72] Forgot to reset hunk & line between files The last change tweaked the way we use the hunk_curr pointer during iteration, but failed to reset the value back to NULL when switching files. --- src/diff_output.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/diff_output.c b/src/diff_output.c index 50e3cc1de43..37cceff9223 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -1285,8 +1285,9 @@ static void diffiter_do_unload_file(git_diff_iterator *iter) } iter->ctxt.delta = NULL; - iter->hunk_head = NULL; + iter->hunk_curr = iter->hunk_head = NULL; iter->hunk_count = 0; + iter->line_curr = NULL; } int git_diff_iterator_new( From 13b554e3769a04a9fdfdfe7676eea0b867aba10d Mon Sep 17 00:00:00 2001 From: Sascha Cunz Date: Thu, 13 Sep 2012 23:30:31 +0200 Subject: [PATCH 43/72] Fix error text s/buffer too long/buffer too short/ --- src/repository.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repository.c b/src/repository.c index bcc6b150398..87022523c59 100644 --- a/src/repository.c +++ b/src/repository.c @@ -430,7 +430,7 @@ int git_repository_discover( if (size < (size_t)(path.size + 1)) { giterr_set(GITERR_REPOSITORY, - "The given buffer is too long to store the discovered path"); + "The given buffer is too small to store the discovered path"); git_buf_free(&path); return -1; } From 3ce22c748511c5b12a8a9731d6b9b2888379bd35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 26 Aug 2012 19:22:34 +0200 Subject: [PATCH 44/72] http: use WinHTTP on Windows Wondows has its own HTTP library. Use that one when possible instead of our own. As we don't depend on them anymore, remove the http-parser library from the Windows build, as well as the search for OpenSSL. --- CMakeLists.txt | 10 +- src/transports/http.c | 226 ++++++++++++++++++++++++++++++++++++------ src/util.c | 2 +- 3 files changed, 204 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a0ffdd42a5..7a7a943e582 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,12 @@ ENDIF() # Find required dependencies INCLUDE_DIRECTORIES(src include deps/http-parser) -FILE(GLOB SRC_HTTP deps/http-parser/*.c) +IF (WIN32 AND NOT MINGW) + ADD_DEFINITIONS(-DGIT_WINHTTP) +ELSE () + FIND_PACKAGE(OpenSSL) + FILE(GLOB SRC_HTTP deps/http-parser/*.c) +ENDIF() # Specify sha1 implementation IF (SHA1_TYPE STREQUAL "ppc") @@ -75,7 +80,7 @@ OPTION (PROFILE "Generate profiling information" OFF) # Platform specific compilation flags IF (MSVC) - # Not using __stdcall with the CRT causes problems + # Default to stdcall, as that's what the CLR expects and how the Windows API is built OPTION (STDCALL "Buildl libgit2 with the __stdcall convention" ON) SET(CMAKE_C_FLAGS "/W4 /MP /nologo /Zi ${CMAKE_C_FLAGS}") @@ -106,7 +111,6 @@ IF (NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) ENDIF () -FIND_PACKAGE(OpenSSL) IF (OPENSSL_FOUND) ADD_DEFINITIONS(-DGIT_SSL) INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) diff --git a/src/transports/http.c b/src/transports/http.c index de33f56ea19..f1619c51f88 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -4,7 +4,6 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ - #include #include "git2.h" #include "http_parser.h" @@ -20,6 +19,13 @@ #include "filebuf.h" #include "repository.h" #include "protocol.h" +#if GIT_WINHTTP +# include +# pragma comment(lib, "winhttp.lib") +#endif + +#define WIDEN2(s) L ## s +#define WIDEN(s) WIDEN2(s) enum last_cb { NONE, @@ -47,6 +53,11 @@ typedef struct { #ifdef GIT_WIN32 WSADATA wsd; #endif +#ifdef GIT_WINHTTP + HINTERNET session; + HINTERNET connection; + HINTERNET request; +#endif } transport_http; static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, @@ -77,17 +88,158 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c return 0; } -static int do_connect(transport_http *t, const char *host, const char *port) +static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls) { +#ifndef GIT_WINHTTP + git_buf request = GIT_BUF_INIT; + const char *verb; + + verb = ls ? "GET" : "POST"; + /* Generate and send the HTTP request */ + if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } + + + if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0) { + git_buf_free(&request); + return -1; + } + + if (content_length) { + if (gitno_send((git_transport *) t, data, content_length, 0) < 0) + return -1; + } + + return 0; +#else + wchar_t *url, *verb, *ct; + git_buf buf = GIT_BUF_INIT; + BOOL ret; + DWORD flags; + void *buffer; + wchar_t *types[] = { + L"*/*", + NULL, + }; + + verb = ls ? L"GET" : L"POST"; + buffer = data ? data : WINHTTP_NO_REQUEST_DATA; + flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0; + + if (ls) + git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service); + else + git_buf_printf(&buf, "%s/git-%s", t->path, service); + + if (git_buf_oom(&buf)) + return -1; + + url = gitwin_to_utf16(git_buf_cstr(&buf)); + if (!url) + goto on_error; + + t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags); + git__free(url); + if (t->request == NULL) { + git_buf_free(&buf); + giterr_set(GITERR_OS, "Failed to open request"); + return -1; + } + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0) + goto on_error; + ct = gitwin_to_utf16(git_buf_cstr(&buf)); + if (!ct) + goto on_error; + + if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + + if (!t->parent.check_cert) { + int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; + if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) { + giterr_set(GITERR_OS, "Failed to set options to ignore cert errors"); + goto on_error; + } + } + + if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, + data, content_length, content_length, 0) == FALSE) { + giterr_set(GITERR_OS, "Failed to send request"); + goto on_error; + } + + ret = WinHttpReceiveResponse(t->request, NULL); + if (ret == FALSE) { + giterr_set(GITERR_OS, "Failed to receive response"); + goto on_error; + } + + return 0; + +on_error: + git_buf_free(&buf); + if (t->request) + WinHttpCloseHandle(t->request); + t->request = NULL; + return -1; +#endif +} + +static int do_connect(transport_http *t) +{ +#ifndef GIT_WINHTTP if (t->parent.connected && http_should_keep_alive(&t->parser)) return 0; - if (gitno_connect((git_transport *) t, host, port) < 0) + if (gitno_connect((git_transport *) t, t->host, t->port) < 0) return -1; t->parent.connected = 1; return 0; +#else + wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; + wchar_t *host; + int32_t port; + + t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + + if (t->session == NULL) { + giterr_set(GITERR_OS, "Failed to init WinHTTP"); + goto on_error; + } + + host = gitwin_to_utf16(t->host); + if (host == NULL) + goto on_error; + + if (git__strtol32(&port, t->port, NULL, 10) < 0) + goto on_error; + + t->connection = WinHttpConnect(t->session, host, port, 0); + git__free(host); + if (t->connection == NULL) { + giterr_set(GITERR_OS, "Failed to connect to host"); + goto on_error; + } + + t->parent.connected = 1; + return 0; + +on_error: + if (t->session) { + WinHttpCloseHandle(t->session); + t->session = NULL; + } + return -1; +#endif } /* @@ -216,13 +368,18 @@ static int http_recv_cb(gitno_buffer *buf) git_transport *transport = (git_transport *) buf->cb_data; transport_http *t = (transport_http *) transport; size_t old_len; - gitno_buffer inner; char buffer[2048]; +#ifdef GIT_WINHTTP + DWORD recvd; +#else + gitno_buffer inner; int error; +#endif if (t->transfer_finished) return 0; +#ifndef GIT_WINHTTP gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer)); if ((error = gitno_recv(&inner)) < 0) @@ -232,6 +389,21 @@ static int http_recv_cb(gitno_buffer *buf) http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset); if (t->error < 0) return t->error; +#else + old_len = buf->offset; + if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) { + giterr_set(GITERR_OS, "Failed to read data from the network"); + return t->error = -1; + } + + if (buf->len - buf->offset < recvd) { + giterr_set(GITERR_NET, "Can't fit data in the buffer"); + return t->error = -1; + } + + memcpy(buf->data + buf->offset, buffer, recvd); + buf->offset += recvd; +#endif return (int)(buf->offset - old_len); } @@ -241,6 +413,8 @@ static void setup_gitno_buffer(git_transport *transport) { transport_http *t = (transport_http *) transport; + /* WinHTTP takes care of this for us */ +#ifndef GIT_WINHTTP http_parser_init(&t->parser, HTTP_RESPONSE); t->parser.data = t; t->transfer_finished = 0; @@ -250,6 +424,7 @@ static void setup_gitno_buffer(git_transport *transport) t->settings.on_headers_complete = on_headers_complete; t->settings.on_body = on_body_fill_buffer; t->settings.on_message_complete = on_message_complete; +#endif gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t); } @@ -289,17 +464,10 @@ static int http_connect(git_transport *transport, int direction) t->service = git__strdup(service); GITERR_CHECK_ALLOC(t->service); - if ((ret = do_connect(t, t->host, t->port)) < 0) + if ((ret = do_connect(t)) < 0) goto cleanup; - /* Generate and send the HTTP request */ - if ((ret = gen_request(&request, t->path, t->host, "GET", service, 0, 1)) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); - goto cleanup; - } - - - if (gitno_send(transport, request.ptr, request.size, 0) < 0) + if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0) goto cleanup; setup_gitno_buffer(transport); @@ -332,36 +500,24 @@ static int http_connect(git_transport *transport, int direction) static int http_negotiation_step(struct git_transport *transport, void *data, size_t len) { transport_http *t = (transport_http *) transport; - git_buf request = GIT_BUF_INIT; int ret; /* First, send the data as a HTTP POST request */ - if ((ret = do_connect(t, t->host, t->port)) < 0) + if ((ret = do_connect(t)) < 0) return -1; - if ((ret = gen_request(&request, t->path, t->host, "POST", "upload-pack", len, 0)) < 0) - goto on_error; - - if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0) - goto on_error; - - if ((ret = gitno_send(transport, data, len, 0)) < 0) - goto on_error; - - git_buf_free(&request); + if (send_request(t, "upload-pack", data, len, 0) < 0) + return -1; /* Then we need to set up the buffer to grab data from the HTTP response */ setup_gitno_buffer(transport); return 0; - -on_error: - git_buf_free(&request); - return -1; } static int http_close(git_transport *transport) { +#ifndef GIT_WINHTTP if (gitno_ssl_teardown(transport) < 0) return -1; @@ -369,6 +525,16 @@ static int http_close(git_transport *transport) giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); return -1; } +#else + transport_http *t = (transport_http *) transport; + + if (t->request) + WinHttpCloseHandle(t->request); + if (t->connection) + WinHttpCloseHandle(t->connection); + if (t->session) + WinHttpCloseHandle(t->session); +#endif transport->connected = 0; @@ -445,7 +611,7 @@ int git_transport_http(git_transport **out) int git_transport_https(git_transport **out) { -#ifdef GIT_SSL +#if defined(GIT_SSL) || defined(GIT_WINHTTP) transport_http *t; if (git_transport_http((git_transport **)&t) < 0) return -1; diff --git a/src/util.c b/src/util.c index 51bf843dec8..719714105fc 100644 --- a/src/util.c +++ b/src/util.c @@ -28,7 +28,7 @@ int git_libgit2_capabilities() #ifdef GIT_THREADS | GIT_CAP_THREADS #endif -#ifdef GIT_SSL +#if defined(GIT_SSL) || defined(GIT_WINHTTP) | GIT_CAP_HTTPS #endif ; From 687ec68be4afbc060b499c2198c16c39685f1aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 14 Sep 2012 00:51:29 +0200 Subject: [PATCH 45/72] http: use the new unicode functions The winhttp branch was based on a version before these existed, so the build broke on Windows. --- src/transports/http.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/transports/http.c b/src/transports/http.c index f1619c51f88..456b85e3fff 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -114,7 +114,8 @@ static int send_request(transport_http *t, const char *service, void *data, ssiz return 0; #else - wchar_t *url, *verb, *ct; + wchar_t *verb; + wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH]; git_buf buf = GIT_BUF_INIT; BOOL ret; DWORD flags; @@ -136,12 +137,9 @@ static int send_request(transport_http *t, const char *service, void *data, ssiz if (git_buf_oom(&buf)) return -1; - url = gitwin_to_utf16(git_buf_cstr(&buf)); - if (!url) - goto on_error; + git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf)); t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags); - git__free(url); if (t->request == NULL) { git_buf_free(&buf); giterr_set(GITERR_OS, "Failed to open request"); @@ -151,9 +149,8 @@ static int send_request(transport_http *t, const char *service, void *data, ssiz git_buf_clear(&buf); if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0) goto on_error; - ct = gitwin_to_utf16(git_buf_cstr(&buf)); - if (!ct) - goto on_error; + + git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf)); if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) { giterr_set(GITERR_OS, "Failed to add a header to the request"); @@ -205,7 +202,7 @@ static int do_connect(transport_http *t) return 0; #else wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - wchar_t *host; + wchar_t host[GIT_WIN_PATH]; int32_t port; t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, @@ -216,15 +213,12 @@ static int do_connect(transport_http *t) goto on_error; } - host = gitwin_to_utf16(t->host); - if (host == NULL) - goto on_error; + git__utf8_to_16(host, GIT_WIN_PATH, t->host); if (git__strtol32(&port, t->port, NULL, 10) < 0) goto on_error; t->connection = WinHttpConnect(t->session, host, port, 0); - git__free(host); if (t->connection == NULL) { giterr_set(GITERR_OS, "Failed to connect to host"); goto on_error; From 60ecdf59d3af87125467fbed97b575f783129f70 Mon Sep 17 00:00:00 2001 From: David Michael Barr Date: Mon, 10 Sep 2012 11:48:21 +1000 Subject: [PATCH 46/72] pack: iterate objects in offset order Compute the ordering on demand and persist until the index is freed. --- src/pack.c | 48 ++++++++++++++++++++++++++++++++++++------------ src/pack.h | 1 + 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/pack.c b/src/pack.c index e1fa085fd6a..9346aced61c 100644 --- a/src/pack.c +++ b/src/pack.c @@ -54,6 +54,10 @@ static int packfile_error(const char *message) static void pack_index_free(struct git_pack_file *p) { + if (p->oids) { + git__free(p->oids); + p->oids = NULL; + } if (p->index_map.data) { git_futils_mmap_free(&p->index_map); p->index_map.data = NULL; @@ -686,13 +690,16 @@ static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_ } } +static int git__memcmp4(const void *a, const void *b) { + return memcmp(a, b, 4); +} + int git_pack_foreach_entry( struct git_pack_file *p, int (*cb)(git_oid *oid, void *data), void *data) { const unsigned char *index = p->index_map.data, *current; - unsigned stride; uint32_t i; if (index == NULL) { @@ -712,21 +719,38 @@ int git_pack_foreach_entry( index += 4 * 256; - if (p->index_version > 1) { - stride = 20; - } else { - stride = 24; - index += 4; - } + if (p->oids == NULL) { + git_vector offsets, oids; + int error; - current = index; - for (i = 0; i < p->num_objects; i++) { - if (cb((git_oid *)current, data)) - return GIT_EUSER; + if ((error = git_vector_init(&oids, p->num_objects, NULL))) + return error; + + if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4))) + return error; - current += stride; + if (p->index_version > 1) { + const unsigned char *off = index + 24 * p->num_objects; + for (i = 0; i < p->num_objects; i++) + git_vector_insert(&offsets, (void*)&off[4 * i]); + git_vector_sort(&offsets); + git_vector_foreach(&offsets, i, current) + git_vector_insert(&oids, (void*)&index[5 * (current - off)]); + } else { + for (i = 0; i < p->num_objects; i++) + git_vector_insert(&offsets, (void*)&index[24 * i]); + git_vector_sort(&offsets); + git_vector_foreach(&offsets, i, current) + git_vector_insert(&oids, (void*)¤t[4]); + } + git_vector_free(&offsets); + p->oids = (git_oid **)oids.contents; } + for (i = 0; i < p->num_objects; i++) + if (cb(p->oids[i], data)) + return GIT_EUSER; + return 0; } diff --git a/src/pack.h b/src/pack.h index 178545675d9..af87b7cd5ab 100644 --- a/src/pack.h +++ b/src/pack.h @@ -64,6 +64,7 @@ struct git_pack_file { unsigned pack_local:1, pack_keep:1, has_cache:1; git_oid sha1; git_vector cache; + git_oid **oids; /* something like ".git/objects/pack/xxxxx.pack" */ char pack_name[GIT_FLEX_ARRAY]; /* more */ From 75050223976bce6fd87d5fb38fb3b70adf760c3c Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 14 Sep 2012 11:47:43 +0300 Subject: [PATCH 47/72] Fix MSVC compilation warnings --- src/repository.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/repository.c b/src/repository.c index 87022523c59..20a623a8500 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1422,7 +1422,7 @@ int git_repository_hashfile( len = git_futils_filesize(fd); if (len < 0) { - error = len; + error = (int)len; goto cleanup; } @@ -1432,7 +1432,7 @@ int git_repository_hashfile( goto cleanup; } - error = git_odb__hashfd_filtered(out, fd, len, type, &filters); + error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, &filters); cleanup: p_close(fd); From f4ea176fa83297925cf145082b8f76ad44f88a7c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 14 Sep 2012 10:31:40 -0700 Subject: [PATCH 48/72] Remove unnecessary include I don't think clone.c needs in #include dirent.h and it is not portable, so let's just get rid of it. --- src/clone.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/clone.c b/src/clone.c index e06e9ada8b2..c4f6ec97e0e 100644 --- a/src/clone.c +++ b/src/clone.c @@ -7,10 +7,6 @@ #include -#ifndef GIT_WIN32 -#include -#endif - #include "git2/clone.h" #include "git2/remote.h" #include "git2/revparse.h" From b200a813c090c2ccf12ee4b5a99b45300fead2e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 14 Sep 2012 20:43:47 +0200 Subject: [PATCH 49/72] config: fix Unicode BOM detection Defining the BOM as a string makes the array include the NUL-terminator, which means that the memcpy is going to check for that as well and thus never match for a nonempty file. Define the array as three chars, which makes the size correct. --- src/config_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config_file.c b/src/config_file.c index c575649afb8..4ba83d1d98e 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -820,7 +820,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out) static int skip_bom(diskfile_backend *cfg) { - static const char utf8_bom[] = "\xef\xbb\xbf"; + static const char utf8_bom[] = { '\xef', '\xbb', '\xbf' }; if (cfg->reader.buffer.size < sizeof(utf8_bom)) return 0; From c2948c7754b8bd8059d2a5252ea419c937bbb1ca Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 14 Sep 2012 21:36:49 +0200 Subject: [PATCH 50/72] refs: prevent locked refs from being enumerated Fix #936 --- src/refs.c | 4 ++++ tests-clar/refs/list.c | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/refs.c b/src/refs.c index cdf3cb96ec4..74c40e85089 100644 --- a/src/refs.c +++ b/src/refs.c @@ -494,6 +494,10 @@ static int _dirent_loose_listall(void *_data, git_buf *full_path) return 0; /* we are filtering out this reference */ } + /* Locked references aren't returned */ + if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION)) + return 0; + if (data->callback(file_path, data->callback_payload)) data->callback_error = GIT_EUSER; diff --git a/tests-clar/refs/list.c b/tests-clar/refs/list.c index f92bf4862b0..2daa3941e0e 100644 --- a/tests-clar/refs/list.c +++ b/tests-clar/refs/list.c @@ -51,3 +51,18 @@ void test_refs_list__symbolic_only(void) git_strarray_free(&ref_list); } + +void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_extension(void) +{ + git_strarray ref_list; + + /* Create a fake locked reference */ + cl_git_mkfile( + "./testrepo/.git/refs/heads/hanwen.lock", + "144344043ba4d4a405da03de3844aa829ae8be0e\n"); + + cl_git_pass(git_reference_list(&ref_list, g_repo, GIT_REF_LISTALL)); + cl_assert_equal_i((int)ref_list.count, 10); + + git_strarray_free(&ref_list); +} From 3d7617e49e22053e3a34061fc6f109d27c67d1d2 Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Fri, 14 Sep 2012 21:33:50 +0200 Subject: [PATCH 51/72] odb_pack: fix race condition last_found is the last packfile a wanted object was found in. Since last_found is shared among all searching threads, it might changes while we're searching. As suggested by @arrbee, put a copy on the stack to fix the race condition. --- src/odb_pack.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/odb_pack.c b/src/odb_pack.c index d33d064560f..b4f958b6f41 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -267,9 +267,10 @@ static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backen { int error; unsigned int i; + struct git_pack_file *last_found = backend->last_found; - if (backend->last_found && - git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0) + if (last_found && + git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0) return 0; if ((error = packfile_refresh_all(backend)) < 0) @@ -279,7 +280,7 @@ static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backen struct git_pack_file *p; p = git_vector_get(&backend->packs, i); - if (p == backend->last_found) + if (p == last_found) continue; if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) { @@ -300,12 +301,13 @@ static int pack_entry_find_prefix( int error; unsigned int i; unsigned found = 0; + struct git_pack_file *last_found = backend->last_found; if ((error = packfile_refresh_all(backend)) < 0) return error; - if (backend->last_found) { - error = git_pack_entry_find(e, backend->last_found, short_oid, len); + if (last_found) { + error = git_pack_entry_find(e, last_found, short_oid, len); if (error == GIT_EAMBIGUOUS) return error; if (!error) @@ -316,7 +318,7 @@ static int pack_entry_find_prefix( struct git_pack_file *p; p = git_vector_get(&backend->packs, i); - if (p == backend->last_found) + if (p == last_found) continue; error = git_pack_entry_find(e, p, short_oid, len); From e8776d30f7edb570f435cf746d712c696b862bdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 16 Sep 2012 00:10:07 +0200 Subject: [PATCH 52/72] odb: don't overflow the link path buffer Allocate a buffer large enough to store the path plus the terminator instead of letting readlink write beyond the end. --- src/odb.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/odb.c b/src/odb.c index 29c56a5bfe6..d1ffff652c5 100644 --- a/src/odb.c +++ b/src/odb.c @@ -196,10 +196,11 @@ int git_odb__hashlink(git_oid *out, const char *path) char *link_data; ssize_t read_len; - link_data = git__malloc((size_t)size); + link_data = git__malloc((size_t)(size + 1)); GITERR_CHECK_ALLOC(link_data); - read_len = p_readlink(path, link_data, (size_t)(size + 1)); + read_len = p_readlink(path, link_data, (size_t)size); + link_data[size] = '\0'; if (read_len != (ssize_t)size) { giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path); return -1; From 3aa443a9511f5b9848d314337b226c41ef3eef84 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 20 Aug 2012 16:56:45 +0200 Subject: [PATCH 53/72] checkout: introduce git_checkout_tree() --- include/git2/checkout.h | 29 ++- src/checkout.c | 407 +++++++++++++++++++++------------ src/clone.c | 3 +- src/filter.c | 34 +-- src/filter.h | 7 +- tests-clar/checkout/checkout.c | 206 ----------------- tests-clar/checkout/tree.c | 268 ++++++++++++++++++++++ 7 files changed, 570 insertions(+), 384 deletions(-) delete mode 100644 tests-clar/checkout/checkout.c create mode 100644 tests-clar/checkout/tree.c diff --git a/include/git2/checkout.h b/include/git2/checkout.h index deb82872248..21b68e3ab09 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -32,10 +32,16 @@ typedef struct git_checkout_opts { int dir_mode; /* default is 0755 */ int file_mode; /* default is 0644 */ int file_open_flags; /* default is O_CREAT | O_TRUNC | O_WRONLY */ + + /* when not NULL, arrays of fnmatch pattern specifying + * which paths should be taken into account + */ + git_strarray *paths; } git_checkout_opts; /** - * Updates files in the working tree to match the commit pointed to by HEAD. + * Updates files in the index and the working tree to match the content of the + * commit pointed at by HEAD. * * @param repo repository to check out (must be non-bare) * @param opts specifies checkout options (may be NULL) @@ -49,7 +55,9 @@ GIT_EXTERN(int) git_checkout_head( git_indexer_stats *stats); /** - * Updates files in the working tree to match a commit pointed to by a ref. + * Updates files in the index and the working tree to match the content of the + * commit pointed at by the reference. + * * * @param ref reference to follow to a commit * @param opts specifies checkout options (may be NULL) @@ -62,6 +70,23 @@ GIT_EXTERN(int) git_checkout_reference( git_checkout_opts *opts, git_indexer_stats *stats); +/** + * Updates files in the index and working tree to match the content of the + * tree pointed at by the treeish. + * + * @param repo repository to check out (must be non-bare) + * @param treeish a commit, tag or tree which content will be used to update + * the working directory + * @param opts specifies checkout options (may be NULL) + * @param stats structure through which progress information is reported + * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information + * about the error) + */ +GIT_EXTERN(int) git_checkout_tree( + git_repository *repo, + git_object *treeish, + git_checkout_opts *opts, + git_indexer_stats *stats); /** @} */ GIT_END_DECL diff --git a/src/checkout.c b/src/checkout.c index d1720fcf370..663a362fdb0 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -11,9 +11,9 @@ #include "git2/repository.h" #include "git2/refs.h" #include "git2/tree.h" -#include "git2/commit.h" #include "git2/blob.h" #include "git2/config.h" +#include "git2/diff.h" #include "common.h" #include "refs.h" @@ -22,204 +22,321 @@ #include "filter.h" #include "blob.h" -typedef struct tree_walk_data +struct checkout_diff_data { + git_buf *path; + int workdir_len; + git_checkout_opts *checkout_opts; git_indexer_stats *stats; - git_checkout_opts *opts; - git_repository *repo; - git_odb *odb; - bool no_symlinks; -} tree_walk_data; + git_repository *owner; + bool can_symlink; +}; + +static int buffer_to_file( + git_buf *buffer, + const char *path, + int dir_mode, + int file_open_flags, + mode_t file_mode) +{ + int fd, error_write, error_close; + if (git_futils_mkpath2file(path, dir_mode) < 0) + return -1; -static int blob_contents_to_link(tree_walk_data *data, git_buf *fnbuf, - const git_oid *id) -{ - int retcode = GIT_ERROR; - git_blob *blob; + if ((fd = p_open(path, file_open_flags, file_mode)) < 0) + return -1; - /* Get the link target */ - if (!(retcode = git_blob_lookup(&blob, data->repo, id))) { - git_buf linktarget = GIT_BUF_INIT; - if (!(retcode = git_blob__getbuf(&linktarget, blob))) { - /* Create the link */ - const char *new = git_buf_cstr(&linktarget), - *old = git_buf_cstr(fnbuf); - retcode = data->no_symlinks - ? git_futils_fake_symlink(new, old) - : p_symlink(new, old); - } - git_buf_free(&linktarget); - git_blob_free(blob); - } + error_write = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer)); + error_close = p_close(fd); - return retcode; + return error_write ? error_write : error_close; } - -static int blob_contents_to_file(git_repository *repo, git_buf *fnbuf, - const git_tree_entry *entry, tree_walk_data *data) +static int blob_content_to_file( + git_blob *blob, + const char *path, + unsigned int entry_filemode, + git_checkout_opts *opts) { - int retcode = GIT_ERROR; - int fd = -1; - git_buf contents = GIT_BUF_INIT; - const git_oid *id = git_tree_entry_id(entry); - int file_mode = data->opts->file_mode; - - /* Deal with pre-existing files */ - if (git_path_exists(git_buf_cstr(fnbuf)) && - data->opts->existing_file_action == GIT_CHECKOUT_SKIP_EXISTING) - return 0; + int retcode; + git_buf content = GIT_BUF_INIT; + int file_mode = opts->file_mode; /* Allow disabling of filters */ - if (data->opts->disable_filters) { - git_blob *blob; - if (!(retcode = git_blob_lookup(&blob, repo, id))) { - retcode = git_blob__getbuf(&contents, blob); - git_blob_free(blob); - } - } else { - retcode = git_filter_blob_contents(&contents, repo, id, git_buf_cstr(fnbuf)); - } - if (retcode < 0) goto bctf_cleanup; + if (opts->disable_filters) + retcode = git_blob__getbuf(&content, blob); + else + retcode = git_filter_blob_content(&content, blob, path); + + if (retcode < 0) + goto cleanup; /* Allow overriding of file mode */ if (!file_mode) - file_mode = git_tree_entry_filemode(entry); + file_mode = entry_filemode; - if ((retcode = git_futils_mkpath2file(git_buf_cstr(fnbuf), data->opts->dir_mode)) < 0) - goto bctf_cleanup; + retcode = buffer_to_file(&content, path, opts->dir_mode, opts->file_open_flags, file_mode); - fd = p_open(git_buf_cstr(fnbuf), data->opts->file_open_flags, file_mode); - if (fd < 0) goto bctf_cleanup; +cleanup: + git_buf_free(&content); + return retcode; +} + +static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink) +{ + git_buf linktarget = GIT_BUF_INIT; + int error; - if (!p_write(fd, git_buf_cstr(&contents), git_buf_len(&contents))) - retcode = 0; + if (git_blob__getbuf(&linktarget, blob) < 0) + return -1; + + if (can_symlink) + error = p_symlink(git_buf_cstr(&linktarget), path); else - retcode = GIT_ERROR; - p_close(fd); + error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path); -bctf_cleanup: - git_buf_free(&contents); - return retcode; + git_buf_free(&linktarget); + + return error; } -static int checkout_walker(const char *path, const git_tree_entry *entry, void *payload) +static int checkout_blob( + git_repository *repo, + git_oid *blob_oid, + const char *path, + unsigned int filemode, + bool can_symlink, + git_checkout_opts *opts) { - int retcode = 0; - tree_walk_data *data = (tree_walk_data*)payload; - int attr = git_tree_entry_filemode(entry); - git_buf fnbuf = GIT_BUF_INIT; - git_buf_join_n(&fnbuf, '/', 3, - git_repository_workdir(data->repo), - path, - git_tree_entry_name(entry)); - - switch(git_tree_entry_type(entry)) - { - case GIT_OBJ_TREE: - /* Nothing to do; the blob handling creates necessary directories. */ - break; + git_blob *blob; + int error; - case GIT_OBJ_COMMIT: - /* Submodule */ - git_futils_mkpath2file(git_buf_cstr(&fnbuf), data->opts->dir_mode); - retcode = p_mkdir(git_buf_cstr(&fnbuf), data->opts->dir_mode); + if (git_blob_lookup(&blob, repo, blob_oid) < 0) + return -1; /* Add an error message */ + + if (S_ISLNK(filemode)) + error = blob_content_to_link(blob, path, can_symlink); + else + error = blob_content_to_file(blob, path, filemode, opts); + + git_blob_free(blob); + + return error; +} + +static int checkout_diff_fn( + void *cb_data, + git_diff_delta *delta, + float progress) +{ + struct checkout_diff_data *data; + int error = -1; + + data = (struct checkout_diff_data *)cb_data; + + data->stats->processed = (unsigned int)(data->stats->total * progress); + + git_buf_truncate(data->path, data->workdir_len); + if (git_buf_joinpath(data->path, git_buf_cstr(data->path), delta->new_file.path) < 0) + return -1; + + switch (delta->status) { + case GIT_DELTA_UNTRACKED: + if (!git__suffixcmp(delta->new_file.path, "/")) + error = git_futils_rmdir_r(git_buf_cstr(data->path), GIT_DIRREMOVAL_FILES_AND_DIRS); + else + error = p_unlink(git_buf_cstr(data->path)); break; - case GIT_OBJ_BLOB: - if (S_ISLNK(attr)) { - retcode = blob_contents_to_link(data, &fnbuf, - git_tree_entry_id(entry)); - } else { - retcode = blob_contents_to_file(data->repo, &fnbuf, entry, data); - } + case GIT_DELTA_MODIFIED: + /* Deal with pre-existing files */ + if (data->checkout_opts->existing_file_action == GIT_CHECKOUT_SKIP_EXISTING) + return 0; + + case GIT_DELTA_DELETED: + if (checkout_blob( + data->owner, + &delta->old_file.oid, + git_buf_cstr(data->path), + delta->old_file.mode, + data->can_symlink, + data->checkout_opts) < 0) + goto cleanup; + break; default: - retcode = -1; - break; + giterr_set(GITERR_INVALID, "Unexpected status (%d) for path '%s'.", delta->status, delta->new_file.path); + goto cleanup; } - git_buf_free(&fnbuf); - data->stats->processed++; - return retcode; -} + error = 0; +cleanup: + return error; +} -int git_checkout_head(git_repository *repo, git_checkout_opts *opts, git_indexer_stats *stats) +static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink) { - int retcode = GIT_ERROR; - git_indexer_stats dummy_stats; - git_checkout_opts default_opts = {0}; - git_tree *tree; - tree_walk_data payload; git_config *cfg; + int error; - assert(repo); - if (!opts) opts = &default_opts; - if (!stats) stats = &dummy_stats; + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks"); + + /* + * When no "core.symlinks" entry is found in any of the configuration + * store (local, global or system), default value is "true". + */ + if (error == GIT_ENOTFOUND) { + *can_symlink = true; + error = 0; + } + + return error; +} + +static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *proposed) +{ + assert(normalized); + + if (!proposed) + memset(normalized, 0, sizeof(git_checkout_opts)); + else + memmove(normalized, proposed, sizeof(git_checkout_opts)); /* Default options */ - if (!opts->existing_file_action) - opts->existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + if (!normalized->existing_file_action) + normalized->existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + /* opts->disable_filters is false by default */ - if (!opts->dir_mode) opts->dir_mode = GIT_DIR_MODE; - if (!opts->file_open_flags) - opts->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; + if (!normalized->dir_mode) + normalized->dir_mode = GIT_DIR_MODE; + + if (!normalized->file_open_flags) + normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; +} + +int git_checkout_tree( + git_repository *repo, + git_object *treeish, + git_checkout_opts *opts, + git_indexer_stats *stats) +{ + git_index *index = NULL; + git_tree *tree = NULL; + git_diff_list *diff = NULL; + git_indexer_stats dummy_stats; + + git_diff_options diff_opts = {0}; + git_checkout_opts checkout_opts; + + struct checkout_diff_data data; + git_buf workdir = GIT_BUF_INIT; + + int error; + + assert(repo && treeish); - if (git_repository_is_bare(repo)) { - giterr_set(GITERR_INVALID, "Checkout is not allowed for bare repositories"); + if ((git_repository__ensure_not_bare(repo, "checkout")) < 0) + return GIT_EBAREREPO; + + if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { + giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree."); return GIT_ERROR; } - memset(&payload, 0, sizeof(payload)); + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; - /* Determine if symlinks should be handled */ - if (!git_repository_config__weakptr(&cfg, repo)) { - int temp = true; - if (!git_config_get_bool(&temp, cfg, "core.symlinks")) { - payload.no_symlinks = !temp; - } - } + if ((error = git_index_read_tree(index, tree, NULL)) < 0) + goto cleanup; + + if ((error = git_index_write(index)) < 0) + goto cleanup; - stats->total = stats->processed = 0; - payload.stats = stats; - payload.opts = opts; - payload.repo = repo; - if (git_repository_odb(&payload.odb, repo) < 0) return GIT_ERROR; - - if (!git_repository_head_tree(&tree, repo)) { - git_index *idx; - if (!(retcode = git_repository_index(&idx, repo))) { - if (!(retcode = git_index_read_tree(idx, tree, stats))) { - git_index_write(idx); - retcode = git_tree_walk(tree, checkout_walker, GIT_TREEWALK_POST, &payload); - } - git_index_free(idx); - } - git_tree_free(tree); + diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + + if (opts && opts->paths) { + diff_opts.pathspec.strings = opts->paths->strings; + diff_opts.pathspec.count = opts->paths->count; } - git_odb_free(payload.odb); - return retcode; + if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0) + goto cleanup; + + if ((error = git_buf_puts(&workdir, git_repository_workdir(repo))) < 0) + goto cleanup; + + normalize_options(&checkout_opts, opts); + + if (!stats) + stats = &dummy_stats; + + stats->processed = 0; + stats->total = git_index_entrycount(index); + + memset(&data, 0, sizeof(data)); + + data.path = &workdir; + data.workdir_len = git_buf_len(&workdir); + data.checkout_opts = &checkout_opts; + data.stats = stats; + data.owner = repo; + + if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0) + goto cleanup; + + error = git_diff_foreach(diff, &data, checkout_diff_fn, NULL, NULL); + +cleanup: + git_diff_list_free(diff); + git_index_free(index); + git_tree_free(tree); + git_buf_free(&workdir); + return error; } +int git_checkout_head( + git_repository *repo, + git_checkout_opts *opts, + git_indexer_stats *stats) +{ + int error; + git_tree *tree = NULL; + + assert(repo); + + if (git_repository_head_tree(&tree, repo) < 0) + return -1; + + error = git_checkout_tree(repo, (git_object *)tree, opts, stats); + + git_tree_free(tree); -int git_checkout_reference(git_reference *ref, - git_checkout_opts *opts, - git_indexer_stats *stats) + return error; +} + +int git_checkout_reference( + git_reference *ref, + git_checkout_opts *opts, + git_indexer_stats *stats) { git_repository *repo= git_reference_owner(ref); git_reference *head = NULL; - int retcode = GIT_ERROR; + int error; - if ((retcode = git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, - git_reference_name(ref), true)) < 0) - return retcode; + if ((error = git_reference_create_symbolic( + &head, repo, GIT_HEAD_FILE, git_reference_name(ref), true)) < 0) + return error; - retcode = git_checkout_head(git_reference_owner(ref), opts, stats); + error = git_checkout_head(git_reference_owner(ref), opts, stats); git_reference_free(head); - return retcode; + return error; } + + diff --git a/src/clone.c b/src/clone.c index c4f6ec97e0e..80a13d0f243 100644 --- a/src/clone.c +++ b/src/clone.c @@ -235,9 +235,8 @@ int git_clone(git_repository **out, assert(out && origin_url && workdir_path); - if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0))) { + if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0))) retcode = git_checkout_head(*out, checkout_opts, checkout_stats); - } return retcode; } diff --git a/src/filter.c b/src/filter.c index e9517a25910..5b6bb286a98 100644 --- a/src/filter.c +++ b/src/filter.c @@ -165,37 +165,21 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) return 0; } -static int unfiltered_blob_contents(git_buf *out, git_repository *repo, const git_oid *blob_id) +int git_filter_blob_content(git_buf *out, git_blob *blob, const char *hintpath) { - int retcode = GIT_ERROR; - git_blob *blob; - - if (!(retcode = git_blob_lookup(&blob, repo, blob_id))) - { - retcode = git_blob__getbuf(out, blob); - git_blob_free(blob); - } + git_buf unfiltered = GIT_BUF_INIT; + git_vector filters = GIT_VECTOR_INIT; + int retcode; - return retcode; -} + retcode = git_blob__getbuf(&unfiltered, blob); -int git_filter_blob_contents(git_buf *out, git_repository *repo, const git_oid *oid, const char *path) -{ - int retcode = GIT_ERROR; + git_buf_clear(out); - git_buf unfiltered = GIT_BUF_INIT; - if (!unfiltered_blob_contents(&unfiltered, repo, oid)) { - git_vector filters = GIT_VECTOR_INIT; - if (git_filters_load(&filters, - repo, path, GIT_FILTER_TO_WORKTREE) >= 0) { - git_buf_clear(out); + if (git_filters_load(&filters, git_object_owner((git_object *)blob), hintpath, GIT_FILTER_TO_WORKTREE) >= 0) retcode = git_filters_apply(out, &unfiltered, &filters); - } - - git_filters_free(&filters); - } + git_filters_free(&filters); git_buf_free(&unfiltered); + return retcode; } - diff --git a/src/filter.h b/src/filter.h index 5b7a25b045f..d58e173f97e 100644 --- a/src/filter.h +++ b/src/filter.h @@ -124,11 +124,10 @@ extern int git_text_is_binary(git_text_stats *stats); * Get the content of a blob after all filters have been run. * * @param out buffer to receive the contents - * @param repo repository containing the blob - * @param oid object id for the blob - * @param path path to the blob's output file, relative to the workdir root + * @param hintpath path to the blob's output file, relative to the workdir root. + * Used to determine what git filters should be applied to the content. * @return 0 on success, an error code otherwise */ -extern int git_filter_blob_contents(git_buf *out, git_repository *repo, const git_oid *oid, const char *path); +extern int git_filter_blob_content(git_buf *out, git_blob *blob, const char *hintpath); #endif diff --git a/tests-clar/checkout/checkout.c b/tests-clar/checkout/checkout.c deleted file mode 100644 index ba14194c4b3..00000000000 --- a/tests-clar/checkout/checkout.c +++ /dev/null @@ -1,206 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/checkout.h" -#include "repository.h" - - -static git_repository *g_repo; - -void test_checkout_checkout__initialize(void) -{ - const char *attributes = "* text eol=lf\n"; - - g_repo = cl_git_sandbox_init("testrepo"); - cl_git_mkfile("./testrepo/.gitattributes", attributes); -} - -void test_checkout_checkout__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - -static void test_file_contents(const char *path, const char *expectedcontents) -{ - int fd; - char buffer[1024] = {0}; - size_t expectedlen, actuallen; - - fd = p_open(path, O_RDONLY); - cl_assert(fd >= 0); - - expectedlen = strlen(expectedcontents); - actuallen = p_read(fd, buffer, 1024); - cl_git_pass(p_close(fd)); - - cl_assert_equal_sz(actuallen, expectedlen); - cl_assert_equal_s(buffer, expectedcontents); -} - - -void test_checkout_checkout__bare(void) -{ - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_fail(git_checkout_head(g_repo, NULL, NULL)); -} - -void test_checkout_checkout__default(void) -{ - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - - -void test_checkout_checkout__crlf(void) -{ - const char *attributes = - "branch_file.txt text eol=crlf\n" - "new.txt text eol=lf\n"; - git_config *cfg; - - cl_git_pass(git_repository_config__weakptr(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", false)); - cl_git_mkfile("./testrepo/.gitattributes", attributes); - - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); -} - - -void test_checkout_checkout__win32_autocrlf(void) -{ -#ifdef GIT_WIN32 - git_config *cfg; - const char *expected_readme_text = "hey there\r\n"; - - cl_must_pass(p_unlink("./testrepo/.gitattributes")); - cl_git_pass(git_repository_config__weakptr(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", true)); - - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - test_file_contents("./testrepo/README", expected_readme_text); -#endif -} - - -static void enable_symlinks(bool enable) -{ - git_config *cfg; - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "core.symlinks", enable)); - git_config_free(cfg); -} - -void test_checkout_checkout__symlinks(void) -{ - /* First try with symlinks forced on */ - enable_symlinks(true); - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - -#ifdef GIT_WIN32 - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); -#else - { - char link_data[1024]; - size_t link_size = 1024; - - link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); - link_data[link_size] = '\0'; - cl_assert_equal_i(link_size, strlen("new.txt")); - cl_assert_equal_s(link_data, "new.txt"); - test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); - } -#endif - - /* Now with symlinks forced off */ - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("testrepo"); - enable_symlinks(false); - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); -} - -void test_checkout_checkout__existing_file_skip(void) -{ - git_checkout_opts opts = {0}; - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); -} - -void test_checkout_checkout__existing_file_overwrite(void) -{ - git_checkout_opts opts = {0}; - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_checkout__disable_filters(void) -{ - git_checkout_opts opts = {0}; - cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); - /* TODO cl_git_pass(git_checkout_head(g_repo, &opts, NULL));*/ - /* TODO test_file_contents("./testrepo/new.txt", "my new file\r\n");*/ - opts.disable_filters = true; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_checkout__dir_modes(void) -{ -#ifndef GIT_WIN32 - git_checkout_opts opts = {0}; - struct stat st; - git_reference *ref; - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/dir")); - - opts.dir_mode = 0701; - cl_git_pass(git_checkout_reference(ref, &opts, NULL)); - cl_git_pass(p_stat("./testrepo/a", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0701); - - /* File-mode test, since we're on the 'dir' branch */ - cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0755); - - git_reference_free(ref); -#endif -} - -void test_checkout_checkout__override_file_modes(void) -{ -#ifndef GIT_WIN32 - git_checkout_opts opts = {0}; - struct stat st; - - opts.file_mode = 0700; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - cl_git_pass(p_stat("./testrepo/new.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0700); -#endif -} - -void test_checkout_checkout__open_flags(void) -{ - git_checkout_opts opts = {0}; - - cl_git_mkfile("./testrepo/new.txt", "hi\n"); - opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); -} - -void test_checkout_checkout__detached_head(void) -{ - /* TODO: write this when git_checkout_commit is implemented. */ -} diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c new file mode 100644 index 00000000000..d04bba0da55 --- /dev/null +++ b/tests-clar/checkout/tree.c @@ -0,0 +1,268 @@ +#include "clar_libgit2.h" + +#include "git2/checkout.h" +#include "repository.h" + + +static git_repository *g_repo; +static git_object *g_treeish; +static git_checkout_opts g_opts; + +void test_checkout_tree__initialize(void) +{ + memset(&g_opts, 0, sizeof(g_opts)); + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_rewritefile( + "./testrepo/.gitattributes", + "* text eol=lf\n"); + + cl_git_pass(git_repository_head_tree((git_tree **)&g_treeish, g_repo)); +} + +void test_checkout_tree__cleanup(void) +{ + git_object_free(g_treeish); + cl_git_sandbox_cleanup(); +} + +static void test_file_contents(const char *path, const char *expectedcontents) +{ + int fd; + char buffer[1024] = {0}; + size_t expectedlen, actuallen; + + fd = p_open(path, O_RDONLY); + cl_assert(fd >= 0); + + expectedlen = strlen(expectedcontents); + actuallen = p_read(fd, buffer, 1024); + cl_git_pass(p_close(fd)); + + cl_assert_equal_sz(actuallen, expectedlen); + cl_assert_equal_s(buffer, expectedcontents); +} + +void test_checkout_tree__cannot_checkout_a_bare_repository(void) +{ + test_checkout_tree__cleanup(); + + memset(&g_opts, 0, sizeof(g_opts)); + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_head_tree((git_tree **)&g_treeish, g_repo)); + + cl_git_fail(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); +} + +void test_checkout_tree__update_the_content_of_workdir_with_missing_files(void) +{ + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_tree__honor_the_specified_pathspecs(void) +{ + git_strarray paths; + char *entries[] = { "*.txt" }; + + paths.strings = entries; + paths.count = 1; + g_opts.paths = &paths; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +static void set_config_entry_to(const char *entry_name, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, entry_name, value)); + + git_config_free(cfg); +} + +static void set_core_autocrlf_to(bool value) +{ + set_config_entry_to("core.autocrlf", value); +} + +void test_checkout_tree__honor_the_gitattributes_directives(void) +{ + const char *attributes = + "branch_file.txt text eol=crlf\n" + "new.txt text eol=lf\n"; + + cl_git_mkfile("./testrepo/.gitattributes", attributes); + set_core_autocrlf_to(false); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); +} + +void test_checkout_tree__honor_coreautocrlf_setting_set_to_true(void) +{ +#ifdef GIT_WIN32 + const char *expected_readme_text = "hey there\r\n"; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + set_core_autocrlf_to(true); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + + test_file_contents("./testrepo/README", expected_readme_text); +#endif +} + +static void set_repo_symlink_handling_cap_to(bool value) +{ + set_config_entry_to("core.symlinks", value); +} + +void test_checkout_tree__honor_coresymlinks_setting_set_to_true(void) +{ + set_repo_symlink_handling_cap_to(true); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + +#ifdef GIT_WIN32 + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +#else + { + char link_data[1024]; + size_t link_size = 1024; + + link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); + link_data[link_size] = '\0'; + cl_assert_equal_i(link_size, strlen("new.txt")); + cl_assert_equal_s(link_data, "new.txt"); + test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); + } +#endif +} + +void test_checkout_tree__honor_coresymlinks_setting_set_to_false(void) +{ + set_repo_symlink_handling_cap_to(false); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +} + +void test_checkout_tree__options_skip_existing_file(void) +{ + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + g_opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); +} + +void test_checkout_tree__options_overwrite_existing_file(void) +{ + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + g_opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_tree__options_disable_filters(void) +{ + cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); + + g_opts.disable_filters = false; + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\r\n"); + + p_unlink("./testrepo/new.txt"); + + g_opts.disable_filters = true; + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_tree__options_dir_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + git_oid oid; + git_commit *commit; + + cl_git_pass(git_reference_name_to_oid(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + + g_opts.dir_mode = 0701; + cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/a", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0701); + + /* File-mode test, since we're on the 'dir' branch */ + cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0755); + + git_commit_free(commit); +#endif +} + +void test_checkout_tree__options_override_file_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + + g_opts.file_mode = 0700; + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/new.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0700); +#endif +} + +void test_checkout_tree__options_open_flags(void) +{ + cl_git_mkfile("./testrepo/new.txt", "hi\n"); + + g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); +} + +void test_checkout_tree__cannot_checkout_a_non_treeish(void) +{ + git_oid oid; + git_blob *blob; + + cl_git_pass(git_oid_fromstr(&oid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &oid)); + + cl_git_fail(git_checkout_tree(g_repo, (git_object *)blob, NULL, NULL)); + + git_blob_free(blob); +} From e93af304112735f02bfeb0833b58ed8230de2371 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 24 Aug 2012 10:40:17 +0200 Subject: [PATCH 54/72] checkout: introduce git_checkout_index() --- include/git2/checkout.h | 14 ++++++++++ src/checkout.c | 60 ++++++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 21b68e3ab09..3217ac9a032 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -70,6 +70,20 @@ GIT_EXTERN(int) git_checkout_reference( git_checkout_opts *opts, git_indexer_stats *stats); +/** + * Updates files in the working tree to match the content of the index. + * + * @param repo repository to check out (must be non-bare) + * @param opts specifies checkout options (may be NULL) + * @param stats structure through which progress information is reported + * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information + * about the error) + */ +GIT_EXTERN(int) git_checkout_index( + git_repository *repo, + git_checkout_opts *opts, + git_indexer_stats *stats); + /** * Updates files in the index and working tree to match the content of the * tree pointed at by the treeish. diff --git a/src/checkout.c b/src/checkout.c index 663a362fdb0..6e34e50ab5b 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -220,14 +220,12 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts * normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; } -int git_checkout_tree( +int git_checkout_index( git_repository *repo, - git_object *treeish, git_checkout_opts *opts, git_indexer_stats *stats) { git_index *index = NULL; - git_tree *tree = NULL; git_diff_list *diff = NULL; git_indexer_stats dummy_stats; @@ -239,25 +237,11 @@ int git_checkout_tree( int error; - assert(repo && treeish); + assert(repo); if ((git_repository__ensure_not_bare(repo, "checkout")) < 0) return GIT_EBAREREPO; - if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { - giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree."); - return GIT_ERROR; - } - - if ((error = git_repository_index(&index, repo)) < 0) - goto cleanup; - - if ((error = git_index_read_tree(index, tree, NULL)) < 0) - goto cleanup; - - if ((error = git_index_write(index)) < 0) - goto cleanup; - diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; if (opts && opts->paths) { @@ -277,6 +261,10 @@ int git_checkout_tree( stats = &dummy_stats; stats->processed = 0; + + if ((git_repository_index(&index, repo)) < 0) + goto cleanup; + stats->total = git_index_entrycount(index); memset(&data, 0, sizeof(data)); @@ -293,10 +281,44 @@ int git_checkout_tree( error = git_diff_foreach(diff, &data, checkout_diff_fn, NULL, NULL); cleanup: + git_index_free(index); git_diff_list_free(diff); + git_buf_free(&workdir); + return error; +} + +int git_checkout_tree( + git_repository *repo, + git_object *treeish, + git_checkout_opts *opts, + git_indexer_stats *stats) +{ + git_index *index = NULL; + git_tree *tree = NULL; + + int error; + + assert(repo && treeish); + + if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { + giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree."); + return GIT_ERROR; + } + + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; + + if ((error = git_index_read_tree(index, tree, NULL)) < 0) + goto cleanup; + + if ((error = git_index_write(index)) < 0) + goto cleanup; + + error = git_checkout_index(repo, opts, stats); + +cleanup: git_index_free(index); git_tree_free(tree); - git_buf_free(&workdir); return error; } From ee8bb8ba649118c4abb09cb02bd3c02e60b98e06 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 19 Aug 2012 21:24:51 +0200 Subject: [PATCH 55/72] reset: add support for GIT_RESET_HARD mode --- include/git2/reset.h | 3 +++ include/git2/types.h | 1 + src/reset.c | 15 +++++++++++++- tests-clar/reset/hard.c | 46 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests-clar/reset/hard.c diff --git a/include/git2/reset.h b/include/git2/reset.h index cd263fa99c3..cdcfb76710c 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -24,6 +24,9 @@ GIT_BEGIN_DECL * Specifying a Mixed kind of reset will trigger a Soft reset and the index will * be replaced with the content of the commit tree. * + * Specifying a Hard kind of reset will trigger a Mixed reset and the working + * directory will be replaced with the content of the index. + * * TODO: Implement remaining kinds of resets. * * @param repo Repository where to perform the reset operation. diff --git a/include/git2/types.h b/include/git2/types.h index d3a905372fa..26e9c57e78b 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -173,6 +173,7 @@ typedef enum { typedef enum { GIT_RESET_SOFT = 1, GIT_RESET_MIXED = 2, + GIT_RESET_HARD = 3, } git_reset_type; /** Valid modes for index and tree entries. */ diff --git a/src/reset.c b/src/reset.c index 5aaf9484003..efe3b6be9f4 100644 --- a/src/reset.c +++ b/src/reset.c @@ -9,6 +9,7 @@ #include "commit.h" #include "tag.h" #include "git2/reset.h" +#include "git2/checkout.h" #define ERROR_MSG "Cannot perform reset" @@ -29,7 +30,9 @@ int git_reset( int error = -1; assert(repo && target); - assert(reset_type == GIT_RESET_SOFT || reset_type == GIT_RESET_MIXED); + assert(reset_type == GIT_RESET_SOFT + || reset_type == GIT_RESET_MIXED + || reset_type == GIT_RESET_HARD); if (git_object_owner(target) != repo) return reset_error_invalid("The given target does not belong to this repository."); @@ -73,6 +76,16 @@ int git_reset( goto cleanup; } + if (reset_type == GIT_RESET_MIXED) { + error = 0; + goto cleanup; + } + + if (git_checkout_index(repo, NULL, NULL, NULL) < 0) { + giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG); + goto cleanup; + } + error = 0; cleanup: diff --git a/tests-clar/reset/hard.c b/tests-clar/reset/hard.c new file mode 100644 index 00000000000..ad3badb8a1e --- /dev/null +++ b/tests-clar/reset/hard.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "reset_helpers.h" +#include "path.h" +#include "fileops.h" + +static git_repository *repo; +static git_object *target; + +void test_reset_hard__initialize(void) +{ + repo = cl_git_sandbox_init("status"); + target = NULL; +} + +void test_reset_hard__cleanup(void) +{ + git_object_free(target); + cl_git_sandbox_cleanup(); +} + +void test_reset_hard__resetting_culls_empty_directories(void) +{ + git_buf subdir_path = GIT_BUF_INIT; + git_buf subfile_path = GIT_BUF_INIT; + git_buf newdir_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&newdir_path, git_repository_workdir(repo), "newdir/")); + + cl_git_pass(git_buf_joinpath(&subfile_path, git_buf_cstr(&newdir_path), "with/nested/file.txt")); + cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&subfile_path), 0755)); + cl_git_mkfile(git_buf_cstr(&subfile_path), "all anew...\n"); + + cl_git_pass(git_buf_joinpath(&subdir_path, git_repository_workdir(repo), "subdir/")); + cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == true); + + retrieve_target_from_oid(&target, repo, "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD)); + + cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == false); + cl_assert(git_path_isdir(git_buf_cstr(&newdir_path)) == false); + + git_buf_free(&subdir_path); + git_buf_free(&subfile_path); + git_buf_free(&newdir_path); +} From 020cda99c2bec386cb10200f6cfe1b150911ffc9 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 14 Sep 2012 16:45:24 +0200 Subject: [PATCH 56/72] checkout: separate tree from index related tests --- tests-clar/checkout/index.c | 273 ++++++++++++++++++++++++++++++++++++ tests-clar/checkout/tree.c | 239 ------------------------------- 2 files changed, 273 insertions(+), 239 deletions(-) create mode 100644 tests-clar/checkout/index.c diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c new file mode 100644 index 00000000000..b81aa917093 --- /dev/null +++ b/tests-clar/checkout/index.c @@ -0,0 +1,273 @@ +#include "clar_libgit2.h" + +#include "git2/checkout.h" +#include "repository.h" + +static git_repository *g_repo; +static git_checkout_opts g_opts; + +static void reset_index_to_treeish(git_object *treeish) +{ + git_object *tree; + git_index *index; + + cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJ_TREE)); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, (git_tree *)tree, NULL)); + cl_git_pass(git_index_write(index)); + + git_object_free(tree); + git_index_free(index); +} + +void test_checkout_index__initialize(void) +{ + git_tree *tree; + + memset(&g_opts, 0, sizeof(g_opts)); + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + reset_index_to_treeish((git_object *)tree); + git_tree_free(tree); + + cl_git_rewritefile( + "./testrepo/.gitattributes", + "* text eol=lf\n"); +} + +void test_checkout_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void test_file_contents(const char *path, const char *expectedcontents) +{ + int fd; + char buffer[1024] = {0}; + size_t expectedlen, actuallen; + + fd = p_open(path, O_RDONLY); + cl_assert(fd >= 0); + + expectedlen = strlen(expectedcontents); + actuallen = p_read(fd, buffer, 1024); + cl_git_pass(p_close(fd)); + + cl_assert_equal_sz(actuallen, expectedlen); + cl_assert_equal_s(buffer, expectedcontents); +} + +void test_checkout_index__cannot_checkout_a_bare_repository(void) +{ + test_checkout_index__cleanup(); + + memset(&g_opts, 0, sizeof(g_opts)); + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); +} + +void test_checkout_index__update_the_content_of_workdir_with_missing_files(void) +{ + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__honor_the_specified_pathspecs(void) +{ + git_strarray paths; + char *entries[] = { "*.txt" }; + + paths.strings = entries; + paths.count = 1; + g_opts.paths = &paths; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +static void set_config_entry_to(const char *entry_name, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, entry_name, value)); + + git_config_free(cfg); +} + +static void set_core_autocrlf_to(bool value) +{ + set_config_entry_to("core.autocrlf", value); +} + +void test_checkout_index__honor_the_gitattributes_directives(void) +{ + const char *attributes = + "branch_file.txt text eol=crlf\n" + "new.txt text eol=lf\n"; + + cl_git_mkfile("./testrepo/.gitattributes", attributes); + set_core_autocrlf_to(false); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); +} + +void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) +{ +#ifdef GIT_WIN32 + const char *expected_readme_text = "hey there\r\n"; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + set_core_autocrlf_to(true); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + + test_file_contents("./testrepo/README", expected_readme_text); +#endif +} + +static void set_repo_symlink_handling_cap_to(bool value) +{ + set_config_entry_to("core.symlinks", value); +} + +void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) +{ + set_repo_symlink_handling_cap_to(true); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + +#ifdef GIT_WIN32 + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +#else + { + char link_data[1024]; + size_t link_size = 1024; + + link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); + link_data[link_size] = '\0'; + cl_assert_equal_i(link_size, strlen("new.txt")); + cl_assert_equal_s(link_data, "new.txt"); + test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); + } +#endif +} + +void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) +{ + set_repo_symlink_handling_cap_to(false); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +} + +void test_checkout_index__options_skip_existing_file(void) +{ + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + g_opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); +} + +void test_checkout_index__options_overwrite_existing_file(void) +{ + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + g_opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_disable_filters(void) +{ + cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); + + g_opts.disable_filters = false; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\r\n"); + + p_unlink("./testrepo/new.txt"); + + g_opts.disable_filters = true; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_dir_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + git_oid oid; + git_commit *commit; + + cl_git_pass(git_reference_name_to_oid(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + + reset_index_to_treeish((git_object *)commit); + + g_opts.dir_mode = 0701; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/a", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0701); + + /* File-mode test, since we're on the 'dir' branch */ + cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0755); + + git_commit_free(commit); +#endif +} + +void test_checkout_index__options_override_file_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + + g_opts.file_mode = 0700; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/new.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0700); +#endif +} + +void test_checkout_index__options_open_flags(void) +{ + cl_git_mkfile("./testrepo/new.txt", "hi\n"); + + g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); +} diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index d04bba0da55..32b64e5d7a4 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -3,257 +3,18 @@ #include "git2/checkout.h" #include "repository.h" - static git_repository *g_repo; -static git_object *g_treeish; -static git_checkout_opts g_opts; void test_checkout_tree__initialize(void) { - memset(&g_opts, 0, sizeof(g_opts)); - g_repo = cl_git_sandbox_init("testrepo"); - - cl_git_rewritefile( - "./testrepo/.gitattributes", - "* text eol=lf\n"); - - cl_git_pass(git_repository_head_tree((git_tree **)&g_treeish, g_repo)); } void test_checkout_tree__cleanup(void) { - git_object_free(g_treeish); cl_git_sandbox_cleanup(); } -static void test_file_contents(const char *path, const char *expectedcontents) -{ - int fd; - char buffer[1024] = {0}; - size_t expectedlen, actuallen; - - fd = p_open(path, O_RDONLY); - cl_assert(fd >= 0); - - expectedlen = strlen(expectedcontents); - actuallen = p_read(fd, buffer, 1024); - cl_git_pass(p_close(fd)); - - cl_assert_equal_sz(actuallen, expectedlen); - cl_assert_equal_s(buffer, expectedcontents); -} - -void test_checkout_tree__cannot_checkout_a_bare_repository(void) -{ - test_checkout_tree__cleanup(); - - memset(&g_opts, 0, sizeof(g_opts)); - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_head_tree((git_tree **)&g_treeish, g_repo)); - - cl_git_fail(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); -} - -void test_checkout_tree__update_the_content_of_workdir_with_missing_files(void) -{ - cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); - cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); - cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_tree__honor_the_specified_pathspecs(void) -{ - git_strarray paths; - char *entries[] = { "*.txt" }; - - paths.strings = entries; - paths.count = 1; - g_opts.paths = &paths; - - cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); - cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); - cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); - test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -static void set_config_entry_to(const char *entry_name, bool value) -{ - git_config *cfg; - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, entry_name, value)); - - git_config_free(cfg); -} - -static void set_core_autocrlf_to(bool value) -{ - set_config_entry_to("core.autocrlf", value); -} - -void test_checkout_tree__honor_the_gitattributes_directives(void) -{ - const char *attributes = - "branch_file.txt text eol=crlf\n" - "new.txt text eol=lf\n"; - - cl_git_mkfile("./testrepo/.gitattributes", attributes); - set_core_autocrlf_to(false); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); -} - -void test_checkout_tree__honor_coreautocrlf_setting_set_to_true(void) -{ -#ifdef GIT_WIN32 - const char *expected_readme_text = "hey there\r\n"; - - cl_git_pass(p_unlink("./testrepo/.gitattributes")); - set_core_autocrlf_to(true); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - - test_file_contents("./testrepo/README", expected_readme_text); -#endif -} - -static void set_repo_symlink_handling_cap_to(bool value) -{ - set_config_entry_to("core.symlinks", value); -} - -void test_checkout_tree__honor_coresymlinks_setting_set_to_true(void) -{ - set_repo_symlink_handling_cap_to(true); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - -#ifdef GIT_WIN32 - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); -#else - { - char link_data[1024]; - size_t link_size = 1024; - - link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); - link_data[link_size] = '\0'; - cl_assert_equal_i(link_size, strlen("new.txt")); - cl_assert_equal_s(link_data, "new.txt"); - test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); - } -#endif -} - -void test_checkout_tree__honor_coresymlinks_setting_set_to_false(void) -{ - set_repo_symlink_handling_cap_to(false); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); -} - -void test_checkout_tree__options_skip_existing_file(void) -{ - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); -} - -void test_checkout_tree__options_overwrite_existing_file(void) -{ - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_tree__options_disable_filters(void) -{ - cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); - - g_opts.disable_filters = false; - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "my new file\r\n"); - - p_unlink("./testrepo/new.txt"); - - g_opts.disable_filters = true; - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_tree__options_dir_modes(void) -{ -#ifndef GIT_WIN32 - struct stat st; - git_oid oid; - git_commit *commit; - - cl_git_pass(git_reference_name_to_oid(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); - - g_opts.dir_mode = 0701; - cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &g_opts, NULL)); - - cl_git_pass(p_stat("./testrepo/a", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0701); - - /* File-mode test, since we're on the 'dir' branch */ - cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0755); - - git_commit_free(commit); -#endif -} - -void test_checkout_tree__options_override_file_modes(void) -{ -#ifndef GIT_WIN32 - struct stat st; - - g_opts.file_mode = 0700; - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - cl_git_pass(p_stat("./testrepo/new.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0700); -#endif -} - -void test_checkout_tree__options_open_flags(void) -{ - cl_git_mkfile("./testrepo/new.txt", "hi\n"); - - g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); -} - void test_checkout_tree__cannot_checkout_a_non_treeish(void) { git_oid oid; From c214fa1caff937f20ca3a388652352cda92ce85b Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 6 Sep 2012 15:15:46 +0200 Subject: [PATCH 57/72] checkout: segregate checkout strategies --- include/git2/checkout.h | 11 +++++++---- src/checkout.c | 24 +++++++++++++++++++---- src/reset.c | 9 ++++++++- tests-clar/checkout/index.c | 38 +++++++++++++++++++++++++++---------- 4 files changed, 63 insertions(+), 19 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 3217ac9a032..5707de0d7ba 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -21,13 +21,16 @@ */ GIT_BEGIN_DECL - -#define GIT_CHECKOUT_OVERWRITE_EXISTING 0 /* default */ -#define GIT_CHECKOUT_SKIP_EXISTING 1 +enum { + GIT_CHECKOUT_DEFAULT = (1 << 0), + GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1), + GIT_CHECKOUT_CREATE_MISSING = (1 << 2), + GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 3), +}; /* Use zeros to indicate default settings */ typedef struct git_checkout_opts { - int existing_file_action; /* default: GIT_CHECKOUT_OVERWRITE_EXISTING */ + unsigned int checkout_strategy; /* default: GIT_CHECKOUT_DEFAULT */ int disable_filters; int dir_mode; /* default is 0755 */ int file_mode; /* default is 0644 */ diff --git a/src/checkout.c b/src/checkout.c index 6e34e50ab5b..beb8b5a6339 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -143,6 +143,9 @@ static int checkout_diff_fn( switch (delta->status) { case GIT_DELTA_UNTRACKED: + if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED)) + return 0; + if (!git__suffixcmp(delta->new_file.path, "/")) error = git_futils_rmdir_r(git_buf_cstr(data->path), GIT_DIRREMOVAL_FILES_AND_DIRS); else @@ -150,11 +153,24 @@ static int checkout_diff_fn( break; case GIT_DELTA_MODIFIED: - /* Deal with pre-existing files */ - if (data->checkout_opts->existing_file_action == GIT_CHECKOUT_SKIP_EXISTING) + if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) return 0; + if (checkout_blob( + data->owner, + &delta->old_file.oid, + git_buf_cstr(data->path), + delta->old_file.mode, + data->can_symlink, + data->checkout_opts) < 0) + goto cleanup; + + break; + case GIT_DELTA_DELETED: + if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING)) + return 0; + if (checkout_blob( data->owner, &delta->old_file.oid, @@ -209,8 +225,8 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts * memmove(normalized, proposed, sizeof(git_checkout_opts)); /* Default options */ - if (!normalized->existing_file_action) - normalized->existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + if (!normalized->checkout_strategy) + normalized->checkout_strategy = GIT_CHECKOUT_DEFAULT; /* opts->disable_filters is false by default */ if (!normalized->dir_mode) diff --git a/src/reset.c b/src/reset.c index efe3b6be9f4..4ce21e2cfc8 100644 --- a/src/reset.c +++ b/src/reset.c @@ -28,6 +28,7 @@ int git_reset( git_index *index = NULL; git_tree *tree = NULL; int error = -1; + git_checkout_opts opts; assert(repo && target); assert(reset_type == GIT_RESET_SOFT @@ -81,7 +82,13 @@ int git_reset( goto cleanup; } - if (git_checkout_index(repo, NULL, NULL, NULL) < 0) { + memset(&opts, 0, sizeof(opts)); + opts.checkout_strategy = + GIT_CHECKOUT_CREATE_MISSING + | GIT_CHECKOUT_OVERWRITE_MODIFIED + | GIT_CHECKOUT_REMOVE_UNTRACKED; + + if (git_checkout_index(repo, &opts, NULL) < 0) { giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG); goto cleanup; } diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index b81aa917093..fad10be516e 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -26,6 +26,7 @@ void test_checkout_index__initialize(void) git_tree *tree; memset(&g_opts, 0, sizeof(g_opts)); + g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; g_repo = cl_git_sandbox_init("testrepo"); @@ -71,19 +72,34 @@ void test_checkout_index__cannot_checkout_a_bare_repository(void) cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); } -void test_checkout_index__update_the_content_of_workdir_with_missing_files(void) +void test_checkout_index__can_create_missing_files(void) { cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/README", "hey there\n"); test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); test_file_contents("./testrepo/new.txt", "my new file\n"); } +void test_checkout_index__can_remove_untracked_files(void) +{ + git_futils_mkdir("./testrepo/dir/subdir/subsubdir", NULL, 0755, GIT_MKDIR_PATH); + cl_git_mkfile("./testrepo/dir/one", "one\n"); + cl_git_mkfile("./testrepo/dir/subdir/two", "two\n"); + + cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir")); + + g_opts.checkout_strategy = GIT_CHECKOUT_REMOVE_UNTRACKED; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/dir")); +} + void test_checkout_index__honor_the_specified_pathspecs(void) { git_strarray paths; @@ -128,7 +144,7 @@ void test_checkout_index__honor_the_gitattributes_directives(void) cl_git_mkfile("./testrepo/.gitattributes", attributes); set_core_autocrlf_to(false); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/README", "hey there\n"); test_file_contents("./testrepo/new.txt", "my new file\n"); @@ -143,7 +159,7 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) cl_git_pass(p_unlink("./testrepo/.gitattributes")); set_core_autocrlf_to(true); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/README", expected_readme_text); #endif @@ -158,7 +174,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) { set_repo_symlink_handling_cap_to(true); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); #ifdef GIT_WIN32 test_file_contents("./testrepo/link_to_new.txt", "new.txt"); @@ -180,26 +196,26 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) { set_repo_symlink_handling_cap_to(false); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/link_to_new.txt", "new.txt"); } -void test_checkout_index__options_skip_existing_file(void) +void test_checkout_index__donot_overwrite_modified_file_by_default(void) { cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; + g_opts.checkout_strategy = 0; cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); } -void test_checkout_index__options_overwrite_existing_file(void) +void test_checkout_index__can_overwrite_modified_file(void) { cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + g_opts.checkout_strategy = GIT_CHECKOUT_OVERWRITE_MODIFIED; cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/new.txt", "my new file\n"); @@ -267,6 +283,8 @@ void test_checkout_index__options_open_flags(void) cl_git_mkfile("./testrepo/new.txt", "hi\n"); g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; + + g_opts.checkout_strategy |= GIT_CHECKOUT_OVERWRITE_MODIFIED; cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); From 5af61863dd735887e73d98e9a8cba699276303fd Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 14 Sep 2012 11:15:49 +0200 Subject: [PATCH 58/72] checkout: drop git_checkout_reference() --- include/git2/checkout.h | 16 ---------------- src/checkout.c | 20 -------------------- 2 files changed, 36 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 5707de0d7ba..b15b56a33be 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -57,22 +57,6 @@ GIT_EXTERN(int) git_checkout_head( git_checkout_opts *opts, git_indexer_stats *stats); -/** - * Updates files in the index and the working tree to match the content of the - * commit pointed at by the reference. - * - * - * @param ref reference to follow to a commit - * @param opts specifies checkout options (may be NULL) - * @param stats structure through which progress information is reported - * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information - * about the error) - */ -GIT_EXTERN(int) git_checkout_reference( - git_reference *ref, - git_checkout_opts *opts, - git_indexer_stats *stats); - /** * Updates files in the working tree to match the content of the index. * diff --git a/src/checkout.c b/src/checkout.c index beb8b5a6339..c39bccbaac6 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -358,23 +358,3 @@ int git_checkout_head( return error; } -int git_checkout_reference( - git_reference *ref, - git_checkout_opts *opts, - git_indexer_stats *stats) -{ - git_repository *repo= git_reference_owner(ref); - git_reference *head = NULL; - int error; - - if ((error = git_reference_create_symbolic( - &head, repo, GIT_HEAD_FILE, git_reference_name(ref), true)) < 0) - return error; - - error = git_checkout_head(git_reference_owner(ref), opts, stats); - - git_reference_free(head); - return error; -} - - From 10df95c3cac1c4e195bd39a0914de899245ee5e0 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 12:23:49 +0200 Subject: [PATCH 59/72] checkout: add test coverage of dirs and subtrees --- tests-clar/checkout/tree.c | 52 +++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 32b64e5d7a4..5f99043f9d2 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -4,26 +4,66 @@ #include "repository.h" static git_repository *g_repo; +static git_checkout_opts g_opts; +static git_object *g_object; void test_checkout_tree__initialize(void) { g_repo = cl_git_sandbox_init("testrepo"); + + memset(&g_opts, 0, sizeof(g_opts)); + g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; } void test_checkout_tree__cleanup(void) { + git_object_free(g_object); + cl_git_sandbox_cleanup(); } void test_checkout_tree__cannot_checkout_a_non_treeish(void) { - git_oid oid; - git_blob *blob; + /* blob */ + cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + + cl_git_fail(git_checkout_tree(g_repo, g_object, NULL, NULL)); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) +{ + git_strarray paths; + char *entries[] = { "ab/de/" }; + + paths.strings = entries; + paths.count = 1; + g_opts.paths = &paths; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + + cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt")); + cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt")); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) +{ + git_strarray paths; + char *entries[] = { "de/" }; + + paths.strings = entries; + paths.count = 1; + g_opts.paths = &paths; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab")); - cl_git_pass(git_oid_fromstr(&oid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); - cl_git_pass(git_blob_lookup(&blob, g_repo, &oid)); + cl_assert_equal_i(false, git_path_isdir("./testrepo/de/")); - cl_git_fail(git_checkout_tree(g_repo, (git_object *)blob, NULL, NULL)); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); - git_blob_free(blob); + cl_assert_equal_i(true, git_path_isfile("./testrepo/de/2.txt")); + cl_assert_equal_i(true, git_path_isfile("./testrepo/de/fgh/1.txt")); } From f1ad25f6df10b1ca96a1f5fe3fc1a478c19043f5 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 12:44:07 +0200 Subject: [PATCH 60/72] repository: separate head related tests --- tests-clar/repo/getters.c | 46 ---------------------------------- tests-clar/repo/head.c | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 46 deletions(-) create mode 100644 tests-clar/repo/head.c diff --git a/tests-clar/repo/getters.c b/tests-clar/repo/getters.c index 966de1f168d..ffcd171f277 100644 --- a/tests-clar/repo/getters.c +++ b/tests-clar/repo/getters.c @@ -23,52 +23,6 @@ void test_repo_getters__empty(void) git_repository_free(repo_empty); } -void test_repo_getters__head_detached(void) -{ - git_repository *repo; - git_reference *ref; - git_oid oid; - - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_assert(git_repository_head_detached(repo) == 0); - - /* detach the HEAD */ - git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"); - cl_git_pass(git_reference_create_oid(&ref, repo, "HEAD", &oid, 1)); - cl_assert(git_repository_head_detached(repo) == 1); - git_reference_free(ref); - - /* take the reop back to it's original state */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); - cl_assert(git_repository_head_detached(repo) == 0); - - git_reference_free(ref); - git_repository_free(repo); -} - -void test_repo_getters__head_orphan(void) -{ - git_repository *repo; - git_reference *ref; - - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_assert(git_repository_head_orphan(repo) == 0); - - /* orphan HEAD */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/orphan", 1)); - cl_assert(git_repository_head_orphan(repo) == 1); - git_reference_free(ref); - - /* take the reop back to it's original state */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); - cl_assert(git_repository_head_orphan(repo) == 0); - - git_reference_free(ref); - git_repository_free(repo); -} - void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) { git_odb *odb; diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c new file mode 100644 index 00000000000..eb1332aff35 --- /dev/null +++ b/tests-clar/repo/head.c @@ -0,0 +1,52 @@ +#include "clar_libgit2.h" +#include "refs.h" + +git_repository *repo; + +void test_repo_head__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_repo_head__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_repo_head__head_detached(void) +{ + git_reference *ref; + git_oid oid; + + cl_assert(git_repository_head_detached(repo) == 0); + + /* detach the HEAD */ + git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"); + cl_git_pass(git_reference_create_oid(&ref, repo, "HEAD", &oid, 1)); + cl_assert(git_repository_head_detached(repo) == 1); + git_reference_free(ref); + + /* take the reop back to it's original state */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); + cl_assert(git_repository_head_detached(repo) == 0); + + git_reference_free(ref); +} + +void test_repo_head__head_orphan(void) +{ + git_reference *ref; + + cl_assert(git_repository_head_orphan(repo) == 0); + + /* orphan HEAD */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/orphan", 1)); + cl_assert(git_repository_head_orphan(repo) == 1); + git_reference_free(ref); + + /* take the reop back to it's original state */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); + cl_assert(git_repository_head_orphan(repo) == 0); + + git_reference_free(ref); +} From cc548c7b0ff0d8b33a44d90316abe0027cb6c7d9 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 12:55:37 +0200 Subject: [PATCH 61/72] repository: fix documentation typo --- include/git2/repository.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/repository.h b/include/git2/repository.h index 32ec58dae45..a536c1398a7 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -456,7 +456,7 @@ GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo); GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index); /** - * Retrive git's prepared message + * Retrieve git's prepared message * * Operations such as git revert/cherry-pick/merge with the -n option * stop just short of creating a commit with the changes and save From 3f4c3072ea36877c07380d096d6277e9777f4587 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 22:03:31 +0200 Subject: [PATCH 62/72] repository: introduce git_repository_detach_head() --- include/git2/repository.h | 19 +++++++++++++++++++ src/repository.c | 24 ++++++++++++++++++++++++ tests-clar/repo/head.c | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index a536c1398a7..e68e0548f1e 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -506,6 +506,25 @@ GIT_EXTERN(int) git_repository_hashfile( git_otype type, const char *as_path); +/** + * Detach the HEAD. + * + * If the HEAD is already detached and points to a Commit, 0 is returned. + * + * If the HEAD is already detached and points to a Tag, the HEAD is + * updated into making it point to the peeled Commit, and 0 is returned. + * + * If the HEAD is already detached and points to a non commitish, the HEAD is + * unaletered, and -1 is returned. + * + * Otherwise, the HEAD will be detached and point to the peeled Commit. + * + * @param repo Repository pointer + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_detach_head( + git_repository* repo); + /** @} */ GIT_END_DECL #endif diff --git a/src/repository.c b/src/repository.c index 20a623a8500..def96816ffe 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1442,3 +1442,27 @@ int git_repository_hashfile( return error; } +int git_repository_detach_head( + git_repository* repo) +{ + git_reference *old_head = NULL, + *new_head = NULL; + git_object *object = NULL; + int error = -1; + + assert(repo); + + if (git_repository_head(&old_head, repo) < 0) + return -1; + + if (git_object_lookup(&object, repo, git_reference_oid(old_head), GIT_OBJ_COMMIT) < 0) + goto cleanup; + + error = git_reference_create_oid(&new_head, repo, GIT_HEAD_FILE, git_reference_oid(old_head), 1); + +cleanup: + git_object_free(object); + git_reference_free(old_head); + git_reference_free(new_head); + return error; +} diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c index eb1332aff35..74d2a1c88d8 100644 --- a/tests-clar/repo/head.c +++ b/tests-clar/repo/head.c @@ -50,3 +50,38 @@ void test_repo_head__head_orphan(void) git_reference_free(ref); } + +static void assert_head_is_correctly_detached(void) +{ + git_reference *head; + git_object *commit; + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + + cl_git_pass(git_object_lookup(&commit, repo, git_reference_oid(head), GIT_OBJ_COMMIT)); + + git_object_free(commit); + git_reference_free(head); +} + +void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_detach_head(repo)); + + assert_head_is_correctly_detached(); +} + +void test_repo_head__detach_head_Fails_if_HEAD_and_point_to_a_non_commitish(void) +{ + git_reference *head; + + cl_git_pass(git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, "refs/tags/point_to_blob", 1)); + + cl_git_fail(git_repository_detach_head(repo)); + + git_reference_free(head); +} From 4ebe38bd589b7b99427b2822ca7a486c8bb3bf02 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 22:07:09 +0200 Subject: [PATCH 63/72] repository: introduce git_repository_set_head_detached() --- include/git2/repository.h | 20 ++++++++++++++++++++ src/repository.c | 26 ++++++++++++++++++++++++++ tests-clar/repo/head.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index e68e0548f1e..59a7d2c9848 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -506,6 +506,26 @@ GIT_EXTERN(int) git_repository_hashfile( git_otype type, const char *as_path); +/** + * Make the repository HEAD directly point to the Commit. + * + * If the provided committish cannot be found in the repository, the HEAD + * is unaltered and GIT_ENOTFOUND is returned. + * + * If the provided commitish cannot be peeled into a commit, the HEAD + * is unaltered and -1 is returned. + * + * Otherwise, the HEAD will eventually be detached and will directly point to + * the peeled Commit. + * + * @param repo Repository pointer + * @param commitish Object id of the Commit the HEAD should point to + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_set_head_detached( + git_repository* repo, + const git_oid* commitish); + /** * Detach the HEAD. * diff --git a/src/repository.c b/src/repository.c index def96816ffe..a3e781478df 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1442,6 +1442,32 @@ int git_repository_hashfile( return error; } +int git_repository_set_head_detached( + git_repository* repo, + const git_oid* commitish) +{ + int error; + git_object *object, + *peeled = NULL; + git_reference *new_head = NULL; + + assert(repo && commitish); + + if ((error = git_object_lookup(&object, repo, commitish, GIT_OBJ_ANY)) < 0) + return error; + + if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0) + goto cleanup; + + error = git_reference_create_oid(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), 1); + +cleanup: + git_object_free(object); + git_object_free(peeled); + git_reference_free(new_head); + return error; +} + int git_repository_detach_head( git_repository* repo) { diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c index 74d2a1c88d8..372cdd61de0 100644 --- a/tests-clar/repo/head.c +++ b/tests-clar/repo/head.c @@ -66,6 +66,40 @@ static void assert_head_is_correctly_detached(void) git_reference_free(head); } +void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head_detached(repo, &oid)); +} + +void test_repo_head__set_head_detached_Fails_when_the_object_isnt_a_commitish(void) +{ + git_object *blob; + + cl_git_pass(git_revparse_single(&blob, repo, "point_to_blob")); + + cl_git_fail(git_repository_set_head_detached(repo, git_object_id(blob))); + + git_object_free(blob); +} + +void test_repo_head__set_head_detached_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + git_object *tag; + + cl_git_pass(git_revparse_single(&tag, repo, "tags/test")); + cl_assert_equal_i(GIT_OBJ_TAG, git_object_type(tag)); + + cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag))); + + assert_head_is_correctly_detached(); + + git_object_free(tag); +} + void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) { cl_assert_equal_i(false, git_repository_head_detached(repo)); From 44af67a8b6679ac33c3407d45fee042178d97e76 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 22:07:45 +0200 Subject: [PATCH 64/72] repository: introduce git_repository_set_head() --- include/git2/repository.h | 22 ++++++++++++++++++++ src/repository.c | 32 ++++++++++++++++++++++++++++ tests-clar/repo/head.c | 44 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index 59a7d2c9848..025a0a95dec 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -506,6 +506,28 @@ GIT_EXTERN(int) git_repository_hashfile( git_otype type, const char *as_path); +/** + * Make the repository HEAD point to the specified reference. + * + * If the provided reference points to a Tree or a Blob, the HEAD is + * unaltered and -1 is returned. + * + * If the provided reference points to a branch, the HEAD will point + * to that branch, staying attached, or become attached if it isn't yet. + * If the branch doesn't exist yet, no error will be return. The HEAD + * will then be attached to an unborn branch. + * + * Otherwise, the HEAD will be detached and will directly point to + * the Commit. + * + * @param repo Repository pointer + * @param refname Canonical name of the reference the HEAD should point at + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_set_head( + git_repository* repo, + const char* refname); + /** * Make the repository HEAD directly point to the Commit. * diff --git a/src/repository.c b/src/repository.c index a3e781478df..734cab43dbc 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1442,6 +1442,38 @@ int git_repository_hashfile( return error; } +static bool looks_like_a_branch(const char *refname) +{ + return git__prefixcmp(refname, GIT_REFS_HEADS_DIR) == 0; +} + +int git_repository_set_head( + git_repository* repo, + const char* refname) +{ + git_reference *ref, + *new_head = NULL; + int error; + + assert(repo && refname); + + error = git_reference_lookup(&ref, repo, refname); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + if (git_reference_is_branch(ref)) + error = git_reference_create_symbolic(&new_head, repo, GIT_HEAD_FILE, git_reference_name(ref), 1); + else + error = git_repository_set_head_detached(repo, git_reference_oid(ref)); + } else if (looks_like_a_branch(refname)) + error = git_reference_create_symbolic(&new_head, repo, GIT_HEAD_FILE, refname, 1); + + git_reference_free(ref); + git_reference_free(new_head); + return error; +} + int git_repository_set_head_detached( git_repository* repo, const git_oid* commitish) diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c index 372cdd61de0..64dec69dd8f 100644 --- a/tests-clar/repo/head.c +++ b/tests-clar/repo/head.c @@ -51,6 +51,41 @@ void test_repo_head__head_orphan(void) git_reference_free(ref); } +void test_repo_head__set_head_Attaches_HEAD_to_un_unborn_branch_when_the_branch_doesnt_exist(void) +{ + git_reference *head; + + cl_git_pass(git_repository_set_head(repo, "refs/heads/doesnt/exist/yet")); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo)); +} + +void test_repo_head__set_head_Returns_ENOTFOUND_when_the_reference_doesnt_exist(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head(repo, "refs/tags/doesnt/exist/yet")); +} + +void test_repo_head__set_head_Fails_when_the_reference_points_to_a_non_commitish(void) +{ + cl_git_fail(git_repository_set_head(repo, "refs/tags/point_to_blob")); +} + +void test_repo_head__set_head_Attaches_HEAD_when_the_reference_points_to_a_branch(void) +{ + git_reference *head; + + cl_git_pass(git_repository_set_head(repo, "refs/heads/br2")); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + cl_assert_equal_s("refs/heads/br2", git_reference_name(head)); + + git_reference_free(head); +} + static void assert_head_is_correctly_detached(void) { git_reference *head; @@ -66,6 +101,15 @@ static void assert_head_is_correctly_detached(void) git_reference_free(head); } +void test_repo_head__set_head_Detaches_HEAD_when_the_reference_doesnt_point_to_a_branch(void) +{ + cl_git_pass(git_repository_set_head(repo, "refs/tags/test")); + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + assert_head_is_correctly_detached(); +} + void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void) { git_oid oid; From 5e4cb4f4da0baef99683be95cb5eeb5288d8ba84 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 17 Sep 2012 10:38:57 +0200 Subject: [PATCH 65/72] checkout : reduce memory usage when not filtering --- src/checkout.c | 48 +++++++++++++++++++++++++++++++++++------------- src/filter.c | 19 ------------------- src/filter.h | 11 ----------- 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index c39bccbaac6..30799b6087a 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -59,28 +59,50 @@ static int blob_content_to_file( unsigned int entry_filemode, git_checkout_opts *opts) { - int retcode; - git_buf content = GIT_BUF_INIT; - int file_mode = opts->file_mode; + int error, nb_filters = 0, file_mode = opts->file_mode; + bool dont_free_filtered = false; + git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT; + git_vector filters = GIT_VECTOR_INIT; + + if (opts->disable_filters || + (nb_filters = git_filters_load( + &filters, + git_object_owner((git_object *)blob), + path, + GIT_FILTER_TO_WORKTREE)) == 0) { + + /* Create a fake git_buf from the blob raw data... */ + filtered.ptr = blob->odb_object->raw.data; + filtered.size = blob->odb_object->raw.len; + + /* ... and make sure it doesn't get unexpectedly freed */ + dont_free_filtered = true; + } - /* Allow disabling of filters */ - if (opts->disable_filters) - retcode = git_blob__getbuf(&content, blob); - else - retcode = git_filter_blob_content(&content, blob, path); + if (nb_filters < 0) + return nb_filters; - if (retcode < 0) - goto cleanup; + if (nb_filters > 0) { + if (git_blob__getbuf(&unfiltered, blob) < 0) + goto cleanup; + + if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0) + goto cleanup; + } /* Allow overriding of file mode */ if (!file_mode) file_mode = entry_filemode; - retcode = buffer_to_file(&content, path, opts->dir_mode, opts->file_open_flags, file_mode); + error = buffer_to_file(&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); cleanup: - git_buf_free(&content); - return retcode; + git_filters_free(&filters); + git_buf_free(&unfiltered); + if (!dont_free_filtered) + git_buf_free(&filtered); + + return error; } static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink) diff --git a/src/filter.c b/src/filter.c index 5b6bb286a98..28a05235b1d 100644 --- a/src/filter.c +++ b/src/filter.c @@ -164,22 +164,3 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) return 0; } - -int git_filter_blob_content(git_buf *out, git_blob *blob, const char *hintpath) -{ - git_buf unfiltered = GIT_BUF_INIT; - git_vector filters = GIT_VECTOR_INIT; - int retcode; - - retcode = git_blob__getbuf(&unfiltered, blob); - - git_buf_clear(out); - - if (git_filters_load(&filters, git_object_owner((git_object *)blob), hintpath, GIT_FILTER_TO_WORKTREE) >= 0) - retcode = git_filters_apply(out, &unfiltered, &filters); - - git_filters_free(&filters); - git_buf_free(&unfiltered); - - return retcode; -} diff --git a/src/filter.h b/src/filter.h index d58e173f97e..b9beb49427f 100644 --- a/src/filter.h +++ b/src/filter.h @@ -119,15 +119,4 @@ extern void git_text_gather_stats(git_text_stats *stats, const git_buf *text); */ extern int git_text_is_binary(git_text_stats *stats); - -/** - * Get the content of a blob after all filters have been run. - * - * @param out buffer to receive the contents - * @param hintpath path to the blob's output file, relative to the workdir root. - * Used to determine what git filters should be applied to the content. - * @return 0 on success, an error code otherwise - */ -extern int git_filter_blob_content(git_buf *out, git_blob *blob, const char *hintpath); - #endif From 397837197d1ce04b8bd4aaa57a7f5f67648dc57f Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 17 Sep 2012 20:27:28 +0200 Subject: [PATCH 66/72] checkout: Mimic git_diff_options storage of paths --- include/git2/checkout.h | 2 +- src/checkout.c | 6 ++---- tests-clar/checkout/index.c | 6 ++---- tests-clar/checkout/tree.c | 12 ++++-------- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index b15b56a33be..42d47003d58 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -39,7 +39,7 @@ typedef struct git_checkout_opts { /* when not NULL, arrays of fnmatch pattern specifying * which paths should be taken into account */ - git_strarray *paths; + git_strarray paths; } git_checkout_opts; /** diff --git a/src/checkout.c b/src/checkout.c index 30799b6087a..b20bd57e86b 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -282,10 +282,8 @@ int git_checkout_index( diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - if (opts && opts->paths) { - diff_opts.pathspec.strings = opts->paths->strings; - diff_opts.pathspec.count = opts->paths->count; - } + if (opts && opts->paths.count > 0) + diff_opts.pathspec = opts->paths; if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0) goto cleanup; diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index fad10be516e..d1c59e38c78 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -102,12 +102,10 @@ void test_checkout_index__can_remove_untracked_files(void) void test_checkout_index__honor_the_specified_pathspecs(void) { - git_strarray paths; char *entries[] = { "*.txt" }; - paths.strings = entries; - paths.count = 1; - g_opts.paths = &paths; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 5f99043f9d2..6d573bfd7b1 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -32,12 +32,10 @@ void test_checkout_tree__cannot_checkout_a_non_treeish(void) void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) { - git_strarray paths; char *entries[] = { "ab/de/" }; - paths.strings = entries; - paths.count = 1; - g_opts.paths = &paths; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); @@ -51,12 +49,10 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) { - git_strarray paths; char *entries[] = { "de/" }; - paths.strings = entries; - paths.count = 1; - g_opts.paths = &paths; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab")); From 28abf3dbd27c232acd7dd17c6a642c793a3c80c9 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 20 Sep 2012 11:41:49 +0200 Subject: [PATCH 67/72] checkout: prefer mode_t type usage over int --- src/checkout.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index b20bd57e86b..730e8a499e5 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -35,7 +35,7 @@ struct checkout_diff_data static int buffer_to_file( git_buf *buffer, const char *path, - int dir_mode, + mode_t dir_mode, int file_open_flags, mode_t file_mode) { @@ -56,10 +56,11 @@ static int buffer_to_file( static int blob_content_to_file( git_blob *blob, const char *path, - unsigned int entry_filemode, + mode_t entry_filemode, git_checkout_opts *opts) { - int error, nb_filters = 0, file_mode = opts->file_mode; + int error, nb_filters = 0; + mode_t file_mode = opts->file_mode; bool dont_free_filtered = false; git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT; git_vector filters = GIT_VECTOR_INIT; @@ -127,7 +128,7 @@ static int checkout_blob( git_repository *repo, git_oid *blob_oid, const char *path, - unsigned int filemode, + mode_t filemode, bool can_symlink, git_checkout_opts *opts) { From 9ac8b113b18e04d4d6f0573e3a6c5e06c447dbf3 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 20 Sep 2012 14:06:49 +0200 Subject: [PATCH 68/72] Fix MSVC amd64 compilation warnings --- src/checkout.c | 2 +- src/diff_output.c | 4 ++-- src/transports/http.c | 2 +- src/win32/utf-conv.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index 730e8a499e5..89f73549fe6 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -25,7 +25,7 @@ struct checkout_diff_data { git_buf *path; - int workdir_len; + size_t workdir_len; git_checkout_opts *checkout_opts; git_indexer_stats *stats; git_repository *owner; diff --git a/src/diff_output.c b/src/diff_output.c index 37cceff9223..58a1a35678d 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -1354,9 +1354,9 @@ int git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iter) return error; if (iter->hunk_curr) - return iter->hunk_curr->line_count; + return (int)iter->hunk_curr->line_count; if (iter->hunk_head) - return iter->hunk_head->line_count; + return (int)iter->hunk_head->line_count; return 0; } diff --git a/src/transports/http.c b/src/transports/http.c index 456b85e3fff..d5015f5af4a 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -166,7 +166,7 @@ static int send_request(transport_http *t, const char *service, void *data, ssiz } if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, - data, content_length, content_length, 0) == FALSE) { + data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) { giterr_set(GITERR_OS, "Failed to send request"); goto on_error; } diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index 88a84141eff..396af7cadc2 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -72,7 +72,7 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) { - MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, length); + MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length); } void git__utf16_to_8(char *out, const wchar_t *input) From b1127a30c771cb37673cb82506b3fd647b5f03ae Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Thu, 20 Sep 2012 22:32:19 +0200 Subject: [PATCH 69/72] git_repository_hashfile: Only close file handle if we have a valid one Otherwise this throws an exception on MFC based systems. Signed-off-by: Sven Strickroth --- src/repository.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/repository.c b/src/repository.c index 734cab43dbc..1a46db0a514 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1384,7 +1384,7 @@ int git_repository_hashfile( { int error; git_vector filters = GIT_VECTOR_INIT; - git_file fd; + git_file fd = -1; git_off_t len; git_buf full_path = GIT_BUF_INIT; @@ -1435,7 +1435,8 @@ int git_repository_hashfile( error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, &filters); cleanup: - p_close(fd); + if (fd >= 0) + p_close(fd); git_filters_free(&filters); git_buf_free(&full_path); From 9e592583fc5fcd7eec5d40d30e34870e6a029fef Mon Sep 17 00:00:00 2001 From: nulltoken Date: Wed, 19 Sep 2012 12:23:47 +0200 Subject: [PATCH 70/72] checkout: add notification callback for skipped files --- include/git2/checkout.h | 15 ++++++++ src/checkout.c | 26 ++++++++++--- tests-clar/checkout/index.c | 73 +++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 42d47003d58..ef3badbe994 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -36,6 +36,21 @@ typedef struct git_checkout_opts { int file_mode; /* default is 0644 */ int file_open_flags; /* default is O_CREAT | O_TRUNC | O_WRONLY */ + /* Optional callback to notify the consumer of files that + * haven't be checked out because a modified version of them + * exist in the working directory. + * + * When provided, this callback will be invoked when the flag + * GIT_CHECKOUT_OVERWRITE_MODIFIED isn't part of the checkout strategy. + */ + int (* skipped_notify_cb)( + const char *skipped_file, + const git_oid *blob_oid, + int file_mode, + void *payload); + + void *notify_payload; + /* when not NULL, arrays of fnmatch pattern specifying * which paths should be taken into account */ diff --git a/src/checkout.c b/src/checkout.c index 89f73549fe6..ea5e79abdda 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -155,6 +155,7 @@ static int checkout_diff_fn( { struct checkout_diff_data *data; int error = -1; + git_checkout_opts *opts; data = (struct checkout_diff_data *)cb_data; @@ -164,9 +165,11 @@ static int checkout_diff_fn( if (git_buf_joinpath(data->path, git_buf_cstr(data->path), delta->new_file.path) < 0) return -1; + opts = data->checkout_opts; + switch (delta->status) { case GIT_DELTA_UNTRACKED: - if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED)) + if (!(opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED)) return 0; if (!git__suffixcmp(delta->new_file.path, "/")) @@ -176,8 +179,20 @@ static int checkout_diff_fn( break; case GIT_DELTA_MODIFIED: - if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) + if (!(opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) { + + if ((opts->skipped_notify_cb != NULL) + && (opts->skipped_notify_cb( + delta->new_file.path, + &delta->old_file.oid, + delta->old_file.mode, + opts->notify_payload))) { + giterr_clear(); + return GIT_EUSER; + } + return 0; + } if (checkout_blob( data->owner, @@ -185,13 +200,13 @@ static int checkout_diff_fn( git_buf_cstr(data->path), delta->old_file.mode, data->can_symlink, - data->checkout_opts) < 0) + opts) < 0) goto cleanup; break; case GIT_DELTA_DELETED: - if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING)) + if (!(opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING)) return 0; if (checkout_blob( @@ -200,7 +215,7 @@ static int checkout_diff_fn( git_buf_cstr(data->path), delta->old_file.mode, data->can_symlink, - data->checkout_opts) < 0) + opts) < 0) goto cleanup; break; @@ -378,4 +393,3 @@ int git_checkout_head( return error; } - diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index d1c59e38c78..f017a0fe22b 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -287,3 +287,76 @@ void test_checkout_index__options_open_flags(void) test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); } + +struct notify_data { + const char *file; + const char *sha; +}; + +static int notify_cb( + const char *skipped_file, + const git_oid *blob_oid, + int file_mode, + void *payload) +{ + struct notify_data *expectations = (struct notify_data *)payload; + + GIT_UNUSED(file_mode); + + cl_assert_equal_s(expectations->file, skipped_file); + cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha)); + + return 0; +} + +void test_checkout_index__can_notify_of_skipped_files(void) +{ + struct notify_data data; + + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + + /* + * $ git ls-tree HEAD + * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README + * 100644 blob 3697d64be941a53d4ae8f6a271e4e3fa56b022cc branch_file.txt + * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt + */ + data.file = "new.txt"; + data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"; + + g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + g_opts.skipped_notify_cb = notify_cb; + g_opts.notify_payload = &data; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); +} + +static int dont_notify_cb( + const char *skipped_file, + const git_oid *blob_oid, + int file_mode, + void *payload) +{ + GIT_UNUSED(skipped_file); + GIT_UNUSED(blob_oid); + GIT_UNUSED(file_mode); + GIT_UNUSED(payload); + + cl_assert(false); + + return 0; +} + +void test_checkout_index__wont_notify_of_expected_line_ending_changes(void) +{ + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + set_core_autocrlf_to(true); + + cl_git_mkfile("./testrepo/new.txt", "my new file\r\n"); + + g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + g_opts.skipped_notify_cb = dont_notify_cb; + g_opts.notify_payload = NULL; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); +} From f55af775ab9b6b20e66607d502ff8bcdb0b72b7d Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Sat, 22 Sep 2012 01:16:10 +0200 Subject: [PATCH 71/72] Make clear that git_odb_hashfile does not use filters Signed-off-by: Sven Strickroth --- include/git2/odb.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/git2/odb.h b/include/git2/odb.h index 1919f61a00a..c6e73571b27 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -279,8 +279,10 @@ GIT_EXTERN(int) git_odb_hash(git_oid *id, const void *data, size_t len, git_otyp /** * Read a file from disk and fill a git_oid with the object id * that the file would have if it were written to the Object - * Database as an object of the given type. Similar functionality - * to git.git's `git hash-object` without the `-w` flag. + * Database as an object of the given type (w/o applying filters). + * Similar functionality to git.git's `git hash-object` without + * the `-w` flag, however, with the --no-filters flag. + * If you need filters, see git_repository_hashfile. * * @param out oid structure the result is written into. * @param path file to read and determine object id for From d75074f4c02e8d8928d20261a891d94d26d41ea7 Mon Sep 17 00:00:00 2001 From: Michael Schubert Date: Sat, 22 Sep 2012 12:29:16 +0200 Subject: [PATCH 72/72] Fix -Wmaybe-uninitialized warning --- src/checkout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/checkout.c b/src/checkout.c index ea5e79abdda..7cf9fe03366 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -59,7 +59,7 @@ static int blob_content_to_file( mode_t entry_filemode, git_checkout_opts *opts) { - int error, nb_filters = 0; + int error = -1, nb_filters = 0; mode_t file_mode = opts->file_mode; bool dont_free_filtered = false; git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT;