diff options
-rw-r--r-- | examples/add.c | 4 | ||||
-rw-r--r-- | examples/commit.c | 84 | ||||
-rw-r--r-- | examples/common.h | 2 | ||||
-rw-r--r-- | examples/lg2.c | 2 | ||||
-rw-r--r-- | examples/push.c | 56 | ||||
-rw-r--r-- | src/merge.c | 27 | ||||
-rw-r--r-- | tests/merge/trees/renames.c | 77 |
7 files changed, 244 insertions, 8 deletions
diff --git a/examples/add.c b/examples/add.c index 6e3c239fc..542360ea9 100644 --- a/examples/add.c +++ b/examples/add.c @@ -48,9 +48,11 @@ int lg2_add(git_repository *repo, int argc, char **argv) git_index_matched_path_cb matched_cb = NULL; git_index *index; git_strarray array = {0}; - struct index_options options; + struct index_options options = {0}; struct args_info args = ARGS_INFO_INIT; + options.mode = INDEX_ADD; + /* Parse the options & arguments. */ parse_opts(NULL, &options, &args); strarray_from_args(&array, &args); diff --git a/examples/commit.c b/examples/commit.c new file mode 100644 index 000000000..cd9782de1 --- /dev/null +++ b/examples/commit.c @@ -0,0 +1,84 @@ +/* + * libgit2 "commit" example - shows how to create a git commit + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#include "common.h" + +/** + * This example demonstrates the libgit2 commit APIs to roughly + * simulate `git commit` with the commit message argument. + * + * This does not have: + * + * - Robust error handling + * - Most of the `git commit` options + * + * This does have: + * + * - Example of performing a git commit with a comment + * + */ +int lg2_commit(git_repository *repo, int argc, char **argv) +{ + const char *opt = argv[1]; + const char *comment = argv[2]; + int error; + + git_oid commit_oid,tree_oid; + git_tree *tree; + git_index *index; + git_object *parent = NULL; + git_reference *ref = NULL; + git_signature *signature; + + /* Validate args */ + if (argc < 3 || strcmp(opt, "-m") != 0) { + printf ("USAGE: %s -m <comment>\n", argv[0]); + return -1; + } + + error = git_revparse_ext(&parent, &ref, repo, "HEAD"); + if (error == GIT_ENOTFOUND) { + printf("HEAD not found. Creating first commit\n"); + error = 0; + } else if (error != 0) { + const git_error *err = git_error_last(); + if (err) printf("ERROR %d: %s\n", err->klass, err->message); + else printf("ERROR %d: no detailed info\n", error); + } + + check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL); + check_lg2(git_index_write_tree(&tree_oid, index), "Could not write tree", NULL);; + check_lg2(git_index_write(index), "Could not write index", NULL);; + + check_lg2(git_tree_lookup(&tree, repo, &tree_oid), "Error looking up tree", NULL); + + check_lg2(git_signature_default(&signature, repo), "Error creating signature", NULL); + + check_lg2(git_commit_create_v( + &commit_oid, + repo, + "HEAD", + signature, + signature, + NULL, + comment, + tree, + parent ? 1 : 0, parent), "Error creating commit", NULL); + + git_index_free(index); + git_signature_free(signature); + git_tree_free(tree); + + return error; +} diff --git a/examples/common.h b/examples/common.h index c01561b48..0126568ab 100644 --- a/examples/common.h +++ b/examples/common.h @@ -59,6 +59,7 @@ extern int lg2_blame(git_repository *repo, int argc, char **argv); extern int lg2_cat_file(git_repository *repo, int argc, char **argv); extern int lg2_checkout(git_repository *repo, int argc, char **argv); extern int lg2_clone(git_repository *repo, int argc, char **argv); +extern int lg2_commit(git_repository *repo, int argc, char **argv); extern int lg2_config(git_repository *repo, int argc, char **argv); extern int lg2_describe(git_repository *repo, int argc, char **argv); extern int lg2_diff(git_repository *repo, int argc, char **argv); @@ -71,6 +72,7 @@ extern int lg2_log(git_repository *repo, int argc, char **argv); extern int lg2_ls_files(git_repository *repo, int argc, char **argv); extern int lg2_ls_remote(git_repository *repo, int argc, char **argv); extern int lg2_merge(git_repository *repo, int argc, char **argv); +extern int lg2_push(git_repository *repo, int argc, char **argv); extern int lg2_remote(git_repository *repo, int argc, char **argv); extern int lg2_rev_list(git_repository *repo, int argc, char **argv); extern int lg2_rev_parse(git_repository *repo, int argc, char **argv); diff --git a/examples/lg2.c b/examples/lg2.c index a3987c34d..7946bc215 100644 --- a/examples/lg2.c +++ b/examples/lg2.c @@ -15,6 +15,7 @@ struct { { "cat-file", lg2_cat_file, 1 }, { "checkout", lg2_checkout, 1 }, { "clone", lg2_clone, 0 }, + { "commit", lg2_commit, 1 }, { "config", lg2_config, 1 }, { "describe", lg2_describe, 1 }, { "diff", lg2_diff, 1 }, @@ -27,6 +28,7 @@ struct { { "ls-files", lg2_ls_files, 1 }, { "ls-remote", lg2_ls_remote, 1 }, { "merge", lg2_merge, 1 }, + { "push", lg2_push, 1 }, { "remote", lg2_remote, 1 }, { "rev-list", lg2_rev_list, 1 }, { "rev-parse", lg2_rev_parse, 1 }, diff --git a/examples/push.c b/examples/push.c new file mode 100644 index 000000000..bcf307607 --- /dev/null +++ b/examples/push.c @@ -0,0 +1,56 @@ +/* + * libgit2 "push" example - shows how to push to remote + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#include "common.h" + +/** + * This example demonstrates the libgit2 push API to roughly + * simulate `git push`. + * + * This does not have: + * + * - Robust error handling + * - Any of the `git push` options + * + * This does have: + * + * - Example of push to origin/master + * + */ + +/** Entry point for this command */ +int lg2_push(git_repository *repo, int argc, char **argv) { + git_push_options options; + git_remote* remote = NULL; + char *refspec = "refs/heads/master"; + const git_strarray refspecs = { + &refspec, + 1 + }; + + /* Validate args */ + if (argc > 1) { + printf ("USAGE: %s\n\nsorry, no arguments supported yet\n", argv[0]); + return -1; + } + + check_lg2(git_remote_lookup(&remote, repo, "origin" ), "Unable to lookup remote", NULL); + + check_lg2(git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION ), "Error initializing push", NULL); + + check_lg2(git_remote_push(remote, &refspecs, &options), "Error pushing", NULL); + + printf("pushed\n"); + return 0; +} diff --git a/src/merge.c b/src/merge.c index 05a776e45..afe69e564 100644 --- a/src/merge.c +++ b/src/merge.c @@ -68,6 +68,16 @@ struct merge_diff_df_data { git_merge_diff *prev_conflict; }; +/* + * This acts as a negative cache entry marker. In case we've tried to calculate + * similarity metrics for a given blob already but `git_hashsig` determined + * that it's too small in order to have a meaningful hash signature, we will + * insert the address of this marker instead of `NULL`. Like this, we can + * easily check whether we have checked a gien entry already and skip doing the + * calculation again and again. + */ +static int cache_invalid_marker; + /* Merge base computation */ int merge_bases_many(git_commit_list **out, git_revwalk **walk_out, git_repository *repo, size_t length, const git_oid input_array[]) @@ -1027,6 +1037,9 @@ static int index_entry_similarity_calc( git_object_size_t blobsize; int error; + if (*out || *out == &cache_invalid_marker) + return 0; + *out = NULL; if ((error = git_blob_lookup(&blob, repo, &entry->id)) < 0) @@ -1047,6 +1060,8 @@ static int index_entry_similarity_calc( error = opts->metric->buffer_signature(out, &diff_file, git_blob_rawcontent(blob), (size_t)blobsize, opts->metric->payload); + if (error == GIT_EBUFS) + *out = &cache_invalid_marker; git_blob_free(blob); @@ -1069,18 +1084,16 @@ static int index_entry_similarity_inexact( return 0; /* update signature cache if needed */ - if (!cache[a_idx] && (error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0) - return error; - if (!cache[b_idx] && (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0) + if ((error = index_entry_similarity_calc(&cache[a_idx], repo, a, opts)) < 0 || + (error = index_entry_similarity_calc(&cache[b_idx], repo, b, opts)) < 0) return error; /* some metrics may not wish to process this file (too big / too small) */ - if (!cache[a_idx] || !cache[b_idx]) + if (cache[a_idx] == &cache_invalid_marker || cache[b_idx] == &cache_invalid_marker) return 0; /* compare signatures */ - if (opts->metric->similarity( - &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0) + if (opts->metric->similarity(&score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0) return -1; /* clip score */ @@ -1550,7 +1563,7 @@ int git_merge_diff_list__find_renames( done: if (cache != NULL) { for (i = 0; i < cache_size; ++i) { - if (cache[i] != NULL) + if (cache[i] != NULL && cache[i] != &cache_invalid_marker) opts->metric->free_signature(cache[i], opts->metric->payload); } diff --git a/tests/merge/trees/renames.c b/tests/merge/trees/renames.c index e0b12af3d..c515aaf1b 100644 --- a/tests/merge/trees/renames.c +++ b/tests/merge/trees/renames.c @@ -274,3 +274,80 @@ void test_merge_trees_renames__submodules(void) cl_assert(merge_test_index(index, merge_index_entries, 7)); git_index_free(index); } + +void test_merge_trees_renames__cache_recomputation(void) +{ + git_oid blob, binary, ancestor_oid, theirs_oid, ours_oid; + git_merge_options opts = GIT_MERGE_OPTIONS_INIT; + git_buf path = GIT_BUF_INIT; + git_treebuilder *builder; + git_tree *ancestor_tree, *their_tree, *our_tree; + git_index *index; + size_t blob_size; + void *data; + size_t i; + + cl_git_pass(git_oid_fromstr(&blob, "a2d8d1824c68541cca94ffb90f79291eba495921")); + + /* + * Create a 50MB blob that consists of NUL bytes only. It is important + * that this blob is of a special format, most importantly it cannot + * contain more than four non-consecutive newlines or NUL bytes. This + * is because of git_hashsig's inner workings where all files with less + * than four "lines" are deemed to small. + */ + blob_size = 50 * 1024 * 1024; + cl_assert(data = git__calloc(blob_size, 1)); + cl_git_pass(git_blob_create_from_buffer(&binary, repo, data, blob_size)); + + /* + * Create the common ancestor, which has 1000 dummy blobs and the binary + * blob. The dummy blobs serve as potential rename targets for the + * dummy blob. + */ + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); + for (i = 0; i < 1000; i++) { + cl_git_pass(git_buf_printf(&path, "%"PRIuMAX".txt", i)); + cl_git_pass(git_treebuilder_insert(NULL, builder, path.ptr, &blob, GIT_FILEMODE_BLOB)); + git_buf_clear(&path); + } + cl_git_pass(git_treebuilder_insert(NULL, builder, "original.bin", &binary, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&ancestor_oid, builder)); + + /* We now the binary blob in our tree. */ + cl_git_pass(git_treebuilder_remove(builder, "original.bin")); + cl_git_pass(git_treebuilder_insert(NULL, builder, "renamed.bin", &binary, GIT_FILEMODE_BLOB)); + cl_git_pass(git_treebuilder_write(&ours_oid, builder)); + + git_treebuilder_free(builder); + + /* And move everything into a subdirectory in their tree. */ + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); + cl_git_pass(git_treebuilder_insert(NULL, builder, "subdir", &ancestor_oid, GIT_FILEMODE_TREE)); + cl_git_pass(git_treebuilder_write(&theirs_oid, builder)); + + /* + * Now merge ancestor, ours and theirs. As `git_hashsig` refuses to + * create a hash signature for the 50MB binary file, we historically + * didn't cache the hashsig computation for it. As a result, we now + * started looking up the 50MB blob and scanning it at least 1000 + * times, which takes a long time. + * + * The number of 1000 blobs is chosen in such a way that it's + * noticeable when the bug creeps in again, as it takes around 12 + * minutes on my machine to compute the following merge. + */ + opts.target_limit = 5000; + cl_git_pass(git_tree_lookup(&ancestor_tree, repo, &ancestor_oid)); + cl_git_pass(git_tree_lookup(&their_tree, repo, &theirs_oid)); + cl_git_pass(git_tree_lookup(&our_tree, repo, &ours_oid)); + cl_git_pass(git_merge_trees(&index, repo, ancestor_tree, our_tree, their_tree, &opts)); + + git_treebuilder_free(builder); + git_buf_dispose(&path); + git_index_free(index); + git_tree_free(ancestor_tree); + git_tree_free(their_tree); + git_tree_free(our_tree); + git__free(data); +} |