From 97bbf61e901ac9e0080394a9321dfc632b77fe99 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 3 Jan 2014 12:14:22 -0800 Subject: [PATCH 01/17] Tree accessor tests with hard path names --- tests/object/tree/write.c | 66 +++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/tests/object/tree/write.c b/tests/object/tree/write.c index 3bea0ed4d..599859727 100644 --- a/tests/object/tree/write.c +++ b/tests/object/tree/write.c @@ -9,7 +9,7 @@ static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488"; static git_repository *g_repo; -// Fixture setup and teardown +/* Fixture setup and teardown */ void test_object_tree_write__initialize(void) { g_repo = cl_git_sandbox_init("testrepo"); @@ -22,7 +22,7 @@ void test_object_tree_write__cleanup(void) void test_object_tree_write__from_memory(void) { - // write a tree from a memory + /* write a tree from a memory */ git_treebuilder *builder; git_tree *tree; git_oid id, bid, rid, id2; @@ -31,7 +31,9 @@ void test_object_tree_write__from_memory(void) git_oid_fromstr(&id2, second_tree); git_oid_fromstr(&bid, blob_oid); - //create a second tree from first tree using `git_treebuilder_insert` on REPOSITORY_FOLDER. + /* create a second tree from first tree using `git_treebuilder_insert` + * on REPOSITORY_FOLDER. + */ cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); cl_git_pass(git_treebuilder_create(&builder, tree)); @@ -61,7 +63,7 @@ void test_object_tree_write__from_memory(void) void test_object_tree_write__subtree(void) { - // write a hierarchical tree from a memory + /* write a hierarchical tree from a memory */ git_treebuilder *builder; git_tree *tree; git_oid id, bid, subtree_id, id2, id3; @@ -72,25 +74,25 @@ void test_object_tree_write__subtree(void) git_oid_fromstr(&id3, third_tree); git_oid_fromstr(&bid, blob_oid); - //create subtree + /* create subtree */ cl_git_pass(git_treebuilder_create(&builder, NULL)); cl_git_pass(git_treebuilder_insert( - NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); //-V536 + NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); /* -V536 */ cl_git_pass(git_treebuilder_write(&subtree_id, g_repo, builder)); git_treebuilder_free(builder); - // create parent tree + /* create parent tree */ cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); cl_git_pass(git_treebuilder_create(&builder, tree)); cl_git_pass(git_treebuilder_insert( - NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); //-V536 + NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); /* -V536 */ cl_git_pass(git_treebuilder_write(&id_hiearar, g_repo, builder)); git_treebuilder_free(builder); git_tree_free(tree); cl_assert(git_oid_cmp(&id_hiearar, &id3) == 0); - // check data is correct + /* check data is correct */ cl_git_pass(git_tree_lookup(&tree, g_repo, &id_hiearar)); cl_assert(2 == git_tree_entrycount(tree)); git_tree_free(tree); @@ -314,3 +316,49 @@ void test_object_tree_write__filtering(void) git_tree_free(tree); } + +void test_object_tree_write__cruel_paths(void) +{ + static const char *the_paths[] = { + "C:\\", + " : * ? \" \n < > |", + "a\\b", + "\\\\b\a", + REP1024("1234"), /* 4096 char string */ + REP1024("12345678"), /* 8192 char string */ + NULL + }; + git_treebuilder *builder; + git_tree *tree; + git_oid id, bid; + const char **scan; + int count = 0; + + git_oid_fromstr(&bid, blob_oid); + + /* create tree */ + cl_git_pass(git_treebuilder_create(&builder, NULL)); + for (scan = the_paths; *scan; ++scan) { + cl_git_pass(git_treebuilder_insert( + NULL, builder, *scan, &bid, GIT_FILEMODE_BLOB)); + count++; + } + cl_git_pass(git_treebuilder_write(&id, g_repo, builder)); + git_treebuilder_free(builder); + + /* check data is correct */ + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_assert_equal_i(count, git_tree_entrycount(tree)); + for (scan = the_paths; *scan; ++scan) { + const git_tree_entry *te = git_tree_entry_byname(tree, *scan); + cl_assert(te != NULL); + cl_assert_equal_s(*scan, git_tree_entry_name(te)); + } + for (scan = the_paths; *scan; ++scan) { + git_tree_entry *te; + cl_git_pass(git_tree_entry_bypath(&te, tree, *scan)); + cl_assert_equal_s(*scan, git_tree_entry_name(te)); + git_tree_entry_free(te); + } + git_tree_free(tree); +} From 79ccb921781dc61c4ed06e5c8c19c2ea1ba0a33a Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 3 Jan 2014 14:26:02 -0800 Subject: [PATCH 02/17] Further tree building tests with hard paths --- tests/object/tree/write.c | 46 ++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/tests/object/tree/write.c b/tests/object/tree/write.c index 599859727..45356e807 100644 --- a/tests/object/tree/write.c +++ b/tests/object/tree/write.c @@ -324,15 +324,20 @@ void test_object_tree_write__cruel_paths(void) " : * ? \" \n < > |", "a\\b", "\\\\b\a", + ":\\", + "COM1", + "foo.aux", REP1024("1234"), /* 4096 char string */ REP1024("12345678"), /* 8192 char string */ + "\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD", /* Ūnĭcōde̽ */ NULL }; git_treebuilder *builder; git_tree *tree; - git_oid id, bid; + git_oid id, bid, subid; const char **scan; - int count = 0; + int count = 0, i, j; + git_tree_entry *te; git_oid_fromstr(&bid, blob_oid); @@ -348,17 +353,46 @@ void test_object_tree_write__cruel_paths(void) /* check data is correct */ cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + cl_assert_equal_i(count, git_tree_entrycount(tree)); + for (scan = the_paths; *scan; ++scan) { - const git_tree_entry *te = git_tree_entry_byname(tree, *scan); - cl_assert(te != NULL); - cl_assert_equal_s(*scan, git_tree_entry_name(te)); + const git_tree_entry *cte = git_tree_entry_byname(tree, *scan); + cl_assert(cte != NULL); + cl_assert_equal_s(*scan, git_tree_entry_name(cte)); } for (scan = the_paths; *scan; ++scan) { - git_tree_entry *te; cl_git_pass(git_tree_entry_bypath(&te, tree, *scan)); cl_assert_equal_s(*scan, git_tree_entry_name(te)); git_tree_entry_free(te); } + + git_tree_free(tree); + + /* let's try longer paths */ + cl_git_pass(git_treebuilder_create(&builder, NULL)); + for (scan = the_paths; *scan; ++scan) { + cl_git_pass(git_treebuilder_insert( + NULL, builder, *scan, &id, GIT_FILEMODE_TREE)); + } + cl_git_pass(git_treebuilder_write(&subid, g_repo, builder)); + git_treebuilder_free(builder); + + /* check data is correct */ + cl_git_pass(git_tree_lookup(&tree, g_repo, &subid)); + + cl_assert_equal_i(count, git_tree_entrycount(tree)); + + for (i = 0; i < count; ++i) { + for (j = 0; j < count; ++j) { + git_buf b = GIT_BUF_INIT; + cl_git_pass(git_buf_joinpath(&b, the_paths[i], the_paths[j])); + cl_git_pass(git_tree_entry_bypath(&te, tree, b.ptr)); + cl_assert_equal_s(the_paths[j], git_tree_entry_name(te)); + git_tree_entry_free(te); + git_buf_free(&b); + } + } + git_tree_free(tree); } From b60149ecedab39fffc8607896f653ef7ac81b1e2 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 31 Jan 2014 18:43:50 -0800 Subject: [PATCH 03/17] Tests merge when changes exist in workdir/index --- tests/merge/workdir/dirty.c | 182 ++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 tests/merge/workdir/dirty.c diff --git a/tests/merge/workdir/dirty.c b/tests/merge/workdir/dirty.c new file mode 100644 index 000000000..67026336b --- /dev/null +++ b/tests/merge/workdir/dirty.c @@ -0,0 +1,182 @@ +#include "clar_libgit2.h" +#include "git2/merge.h" +#include "buffer.h" +#include "merge.h" +#include "../merge_helpers.h" + +#define TEST_REPO_PATH "merge-resolve" +#define MERGE_BRANCH_OID "7cb63eed597130ba4abb87b3e544b85021905520" + +static git_repository *repo; +static git_index *repo_index; + +static char *unaffected[][4] = { + { "added-in-master.txt", NULL }, + { "changed-in-master.txt", NULL }, + { "unchanged.txt", NULL }, + { "added-in-master.txt", "changed-in-master.txt", NULL }, + { "added-in-master.txt", "unchanged.txt", NULL }, + { "changed-in-master.txt", "unchanged.txt", NULL }, + { "added-in-master.txt", "changed-in-master.txt", "unchanged.txt", NULL }, + { "new_file.txt", NULL }, + { "new_file.txt", "unchanged.txt", NULL }, + { NULL }, +}; + +static char *affected[][5] = { + { "automergeable.txt", NULL }, + { "changed-in-branch.txt", NULL }, + { "conflicting.txt", NULL }, + { "removed-in-branch.txt", NULL }, + { "automergeable.txt", "changed-in-branch.txt", NULL }, + { "automergeable.txt", "conflicting.txt", NULL }, + { "automergeable.txt", "removed-in-branch.txt", NULL }, + { "changed-in-branch.txt", "conflicting.txt", NULL }, + { "changed-in-branch.txt", "removed-in-branch.txt", NULL }, + { "conflicting.txt", "removed-in-branch.txt", NULL }, + { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", NULL }, + { "automergeable.txt", "changed-in-branch.txt", "removed-in-branch.txt", NULL }, + { "automergeable.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, + { "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, + { "automergeable.txt", "changed-in-branch.txt", "conflicting.txt", "removed-in-branch.txt", NULL }, + { NULL }, +}; + +void test_merge_workdir_dirty__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); +} + +void test_merge_workdir_dirty__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +static void set_core_autocrlf_to(git_repository *repo, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", value)); + + git_config_free(cfg); +} + +static int merge_branch(git_merge_result **result, int merge_file_favor, int checkout_strategy) +{ + git_oid their_oids[1]; + git_merge_head *their_heads[1]; + git_merge_opts opts = GIT_MERGE_OPTS_INIT; + int error; + + cl_git_pass(git_oid_fromstr(&their_oids[0], MERGE_BRANCH_OID)); + cl_git_pass(git_merge_head_from_id(&their_heads[0], repo, &their_oids[0])); + + opts.merge_tree_opts.file_favor = merge_file_favor; + opts.checkout_opts.checkout_strategy = checkout_strategy; + error = git_merge(result, repo, (const git_merge_head **)their_heads, 1, &opts); + + git_merge_head_free(their_heads[0]); + + return error; +} + +static void write_files(char *files[]) +{ + char *filename; + git_buf path = GIT_BUF_INIT, content = GIT_BUF_INIT; + size_t i; + + for (i = 0, filename = files[i]; filename; filename = files[++i]) { + git_buf_clear(&path); + git_buf_clear(&content); + + git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename); + git_buf_printf(&content, "This is a dirty file in the working directory!\n\n" + "It will not be staged! Its filename is %s.\n", filename); + + cl_git_mkfile(path.ptr, content.ptr); + } + + git_buf_free(&path); + git_buf_free(&content); +} + +static void stage_random_files(char *files[]) +{ + char *filename; + size_t i; + + write_files(files); + + for (i = 0, filename = files[i]; filename; filename = files[++i]) + cl_git_pass(git_index_add_bypath(repo_index, filename)); +} + +static int merge_dirty_files(char *dirty_files[]) +{ + git_reference *head; + git_object *head_object; + git_merge_result *result = NULL; + int error; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD)); + + write_files(dirty_files); + + error = merge_branch(&result, 0, 0); + + git_merge_result_free(result); + git_object_free(head_object); + git_reference_free(head); + + return error; +} + +static int merge_staged_files(char *staged_files[]) +{ + git_merge_result *result = NULL; + int error; + + stage_random_files(staged_files); + + error = merge_branch(&result, 0, 0); + + git_merge_result_free(result); + + return error; +} + +void test_merge_workdir_dirty__unaffected_dirty_files_allowed(void) +{ + char **files; + size_t i; + + for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i]) + cl_git_pass(merge_dirty_files(files)); +} + +void test_merge_workdir_dirty__affected_dirty_files_disallowed(void) +{ + char **files; + size_t i; + + for (i = 0, files = affected[i]; files[0]; files = affected[++i]) + cl_git_fail(merge_dirty_files(files)); +} + +void test_merge_workdir_dirty__staged_files_in_index_disallowed(void) +{ + char **files; + size_t i; + + for (i = 0, files = unaffected[i]; files[0]; files = unaffected[++i]) + cl_git_fail(merge_staged_files(files)); + + for (i = 0, files = affected[i]; files[0]; files = affected[++i]) + cl_git_fail(merge_staged_files(files)); +} From 16eb8b7c0667ba819e85b7bb4b40c2f71f73e59a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 1 Feb 2014 12:02:55 -0800 Subject: [PATCH 04/17] Tests merging staged files identical to result --- tests/merge/workdir/dirty.c | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/merge/workdir/dirty.c b/tests/merge/workdir/dirty.c index 67026336b..23840c1c3 100644 --- a/tests/merge/workdir/dirty.c +++ b/tests/merge/workdir/dirty.c @@ -7,6 +7,20 @@ #define TEST_REPO_PATH "merge-resolve" #define MERGE_BRANCH_OID "7cb63eed597130ba4abb87b3e544b85021905520" +#define AUTOMERGEABLE_MERGED_FILE \ + "this file is changed in master\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is automergeable\n" \ + "this file is changed in branch\n" + +#define CHANGED_IN_BRANCH_FILE \ + "changed in branch\n" + static git_repository *repo; static git_index *repo_index; @@ -42,6 +56,13 @@ static char *affected[][5] = { { NULL }, }; +static char *result_contents[4][6] = { + { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, NULL, NULL }, + { "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL }, + { "automergeable.txt", AUTOMERGEABLE_MERGED_FILE, "changed-in-branch.txt", CHANGED_IN_BRANCH_FILE, NULL, NULL }, + { NULL } +}; + void test_merge_workdir_dirty__initialize(void) { repo = cl_git_sandbox_init(TEST_REPO_PATH); @@ -115,6 +136,37 @@ static void stage_random_files(char *files[]) cl_git_pass(git_index_add_bypath(repo_index, filename)); } +static void stage_content(char *content[]) +{ + git_reference *head; + git_object *head_object; + git_merge_result *result = NULL; + git_buf path = GIT_BUF_INIT; + char *filename, *text; + size_t i; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD)); + + for (i = 0, filename = content[i], text = content[++i]; + filename && text; + filename = content[++i], text = content[++i]) { + + git_buf_clear(&path); + + cl_git_pass(git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename)); + + cl_git_mkfile(path.ptr, text); + cl_git_pass(git_index_add_bypath(repo_index, filename)); + } + + git_merge_result_free(result); + git_object_free(head_object); + git_reference_free(head); + git_buf_free(&path); +} + static int merge_dirty_files(char *dirty_files[]) { git_reference *head; @@ -180,3 +232,21 @@ void test_merge_workdir_dirty__staged_files_in_index_disallowed(void) for (i = 0, files = affected[i]; files[0]; files = affected[++i]) cl_git_fail(merge_staged_files(files)); } + +void test_merge_workdir_dirty__identical_staged_files_allowed(void) +{ + git_merge_result *result; + char **content; + size_t i; + + set_core_autocrlf_to(repo, false); + + for (i = 0, content = result_contents[i]; content[0]; content = result_contents[++i]) { + stage_content(content); + + git_index_write(repo_index); + cl_git_pass(merge_branch(&result, 0, 0)); + + git_merge_result_free(result); + } +} From bb13d39162b0416585b34f18e9072fc4364d2655 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 1 Feb 2014 12:51:44 -0800 Subject: [PATCH 05/17] Test that emulates a strange filter implementation --- tests/merge/workdir/dirty.c | 70 +++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/tests/merge/workdir/dirty.c b/tests/merge/workdir/dirty.c index 23840c1c3..625d3d01a 100644 --- a/tests/merge/workdir/dirty.c +++ b/tests/merge/workdir/dirty.c @@ -3,6 +3,7 @@ #include "buffer.h" #include "merge.h" #include "../merge_helpers.h" +#include "posix.h" #define TEST_REPO_PATH "merge-resolve" #define MERGE_BRANCH_OID "7cb63eed597130ba4abb87b3e544b85021905520" @@ -125,6 +126,41 @@ static void write_files(char *files[]) git_buf_free(&content); } +static void hack_index(char *files[]) +{ + char *filename; + struct stat statbuf; + git_buf path = GIT_BUF_INIT; + git_index_entry *entry; + size_t i; + + /* Update the index to suggest that checkout placed these files on + * disk, keeping the object id but updating the cache, which will + * emulate a Git implementation's different filter. + */ + for (i = 0, filename = files[i]; filename; filename = files[++i]) { + git_buf_clear(&path); + + cl_assert(entry = (git_index_entry *) + git_index_get_bypath(repo_index, filename, 0)); + + cl_git_pass(git_buf_printf(&path, "%s/%s", TEST_REPO_PATH, filename)); + cl_git_pass(p_stat(path.ptr, &statbuf)); + + entry->ctime.seconds = (git_time_t)statbuf.st_ctime; + entry->ctime.nanoseconds = 0; + entry->mtime.seconds = (git_time_t)statbuf.st_mtime; + entry->mtime.nanoseconds = 0; + entry->dev = statbuf.st_dev; + entry->ino = statbuf.st_ino; + entry->uid = statbuf.st_uid; + entry->gid = statbuf.st_gid; + entry->file_size = statbuf.st_size; + } + + git_buf_free(&path); +} + static void stage_random_files(char *files[]) { char *filename; @@ -189,6 +225,31 @@ static int merge_dirty_files(char *dirty_files[]) return error; } +static int merge_differently_filtered_files(char *files[]) +{ + git_reference *head; + git_object *head_object; + git_merge_result *result = NULL; + int error; + + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJ_COMMIT)); + cl_git_pass(git_reset(repo, head_object, GIT_RESET_HARD)); + + write_files(files); + hack_index(files); + + cl_git_pass(git_index_write(repo_index)); + + error = merge_branch(&result, 0, 0); + + git_merge_result_free(result); + git_object_free(head_object); + git_reference_free(head); + + return error; +} + static int merge_staged_files(char *staged_files[]) { git_merge_result *result = NULL; @@ -250,3 +311,12 @@ void test_merge_workdir_dirty__identical_staged_files_allowed(void) git_merge_result_free(result); } } + +void test_merge_workdir_dirty__honors_cache(void) +{ + char **files; + size_t i; + + for (i = 0, files = affected[i]; files[0]; files = affected[++i]) + cl_git_pass(merge_differently_filtered_files(files)); +} From c0b10c25e02c6922276567600988fb2b85aeede1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 1 Feb 2014 12:05:00 -0800 Subject: [PATCH 06/17] Merge wd validation tests against index not HEAD Validating the workdir should not compare HEAD to working directory - this is both inefficient (as it ignores the cache) and incorrect. If we had legitimately allowed changes in the index (identical to the merge result) then comparing HEAD to workdir would reject these changes as different. Further, this will identify files that were filtered strangely as modified, while testing with the cache would prevent this. Also, it's stupid slow. --- src/merge.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/merge.c b/src/merge.c index 20cfc0e23..2ae02e54d 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2330,7 +2330,7 @@ done: static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_index *index_new, git_vector *merged_paths) { - git_tree *head_tree = NULL; + git_index *index_repo = NULL; git_diff *wd_diff_list = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; int error = 0; @@ -2341,9 +2341,6 @@ static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_inde opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - if ((error = git_repository_head_tree(&head_tree, repo)) < 0) - goto done; - /* Workdir changes may exist iff they do not conflict with changes that * will be applied by the merge (including conflicts). Ensure that there * are no changes in the workdir to these paths. @@ -2351,13 +2348,13 @@ static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_inde opts.pathspec.count = merged_paths->length; opts.pathspec.strings = (char **)merged_paths->contents; - if ((error = git_diff_tree_to_workdir(&wd_diff_list, repo, head_tree, &opts)) < 0) + if ((error = git_diff_index_to_workdir(&wd_diff_list, repo, index_repo, &opts)) < 0) goto done; *conflicts = wd_diff_list->deltas.length; done: - git_tree_free(head_tree); + git_index_free(index_repo); git_diff_free(wd_diff_list); return error; From dbfd83bc6567763e6c363c462e9dd0628eaf3fe6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 3 Feb 2014 19:56:13 -0800 Subject: [PATCH 07/17] Remove unused pointer assignment --- src/merge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/merge.c b/src/merge.c index 2ae02e54d..ac973efd0 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2397,7 +2397,7 @@ int git_merge__indexes(git_repository *repo, git_index *index_new) /* Remove removed items from the index */ git_vector_foreach(&paths, i, path) { - if ((e = git_index_get_bypath(index_new, path, 0)) == NULL) { + if (git_index_get_bypath(index_new, path, 0) == NULL) { if ((error = git_index_remove(index_repo, path, 0)) < 0 && error != GIT_ENOTFOUND) goto done; From f61272e04727716ad09bb57e4ffa9ce4be09dc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Feb 2014 12:51:36 +0100 Subject: [PATCH 08/17] revwalk: accept committish objects Let the user push committish objects and peel them to figure out which commit to push to our queue. This is for convenience and for allowing uses of git_revwalk_push_glob(w, "tags") with annotated tags. --- include/git2/revwalk.h | 8 ++++---- src/revwalk.c | 21 +++++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index c59b79938..4eac8d43c 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -87,7 +87,7 @@ GIT_EXTERN(void) git_revwalk_reset(git_revwalk *walker); /** * Mark a commit to start traversal from. * - * The given OID must belong to a commit on the walked + * The given OID must belong to a committish on the walked * repository. * * The given commit will be used as one of the roots @@ -127,7 +127,7 @@ GIT_EXTERN(int) git_revwalk_push_head(git_revwalk *walk); /** * Mark a commit (and its ancestors) uninteresting for the output. * - * The given OID must belong to a commit on the walked + * The given OID must belong to a committish on the walked * repository. * * The resolved commit and all its parents will be hidden from the @@ -166,7 +166,7 @@ GIT_EXTERN(int) git_revwalk_hide_head(git_revwalk *walk); /** * Push the OID pointed to by a reference * - * The reference must point to a commit. + * The reference must point to a committish. * * @param walk the walker being used for the traversal * @param refname the reference to push @@ -177,7 +177,7 @@ GIT_EXTERN(int) git_revwalk_push_ref(git_revwalk *walk, const char *refname); /** * Hide the OID pointed to by a reference * - * The reference must point to a commit. + * The reference must point to a committish. * * @param walk the walker being used for the traversal * @param refname the reference to hide diff --git a/src/revwalk.c b/src/revwalk.c index c0a053211..3cc3140b8 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -112,23 +112,28 @@ static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commi static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) { + git_oid commit_id; int error; - git_object *obj; - git_otype type; + git_object *obj, *oobj; git_commit_list_node *commit; - if ((error = git_object_lookup(&obj, walk->repo, oid, GIT_OBJ_ANY)) < 0) + if ((error = git_object_lookup(&oobj, walk->repo, oid, GIT_OBJ_ANY)) < 0) return error; - type = git_object_type(obj); - git_object_free(obj); + error = git_object_peel(&obj, oobj, GIT_OBJ_COMMIT); + git_object_free(oobj); - if (type != GIT_OBJ_COMMIT) { - giterr_set(GITERR_INVALID, "Object is no commit object"); + if (error == GIT_ENOTFOUND) { + giterr_set(GITERR_INVALID, "Object is not a committish"); return -1; } + if (error < 0) + return error; - commit = git_revwalk__commit_lookup(walk, oid); + git_oid_cpy(&commit_id, git_object_id(obj)); + git_object_free(obj); + + commit = git_revwalk__commit_lookup(walk, &commit_id); if (commit == NULL) return -1; /* error already reported by failed lookup */ From b4ef67d5ebc55943073d7baacd91f46c20d9ccf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Feb 2014 15:11:00 +0100 Subject: [PATCH 09/17] revwalk: add a failing test for pushing "tags" This shows that pusing a whole namespace can be problematic. --- tests/revwalk/basic.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c index 6d55aed54..4621ab8ff 100644 --- a/tests/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -252,3 +252,13 @@ void test_revwalk_basic__push_range(void) cl_git_pass(git_revwalk_push_range(_walk, "9fd738e~2..9fd738e")); cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 1)); } + +void test_revwalk_basic__push_mixed(void) +{ + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_pass(git_revwalk_push_glob(_walk, "tags")); + cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 1)); +} From d465e4e980333b3e7437801fc375b595fb3adf1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Feb 2014 15:19:13 +0100 Subject: [PATCH 10/17] revwalk: ignore wrong object type in glob pushes Pushing a whole namespace can cause us to attempt to push non-committish objects. Catch this situation and special-case it for ignoring this. --- include/git2/revwalk.h | 6 ++++++ src/revwalk.c | 28 ++++++++++++++++------------ tests/revwalk/basic.c | 11 ++++++++++- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index 4eac8d43c..12829b14c 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -110,6 +110,9 @@ GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *id); * A leading 'refs/' is implied if not present as well as a trailing * '/ *' if the glob lacks '?', '*' or '['. * + * Any references matching this glob which do not point to a + * committish will be ignored. + * * @param walk the walker being used for the traversal * @param glob the glob pattern references should match * @return 0 or an error code @@ -149,6 +152,9 @@ GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *commit_id); * A leading 'refs/' is implied if not present as well as a trailing * '/ *' if the glob lacks '?', '*' or '['. * + * Any references matching this glob which do not point to a + * committish will be ignored. + * * @param walk the walker being used for the traversal * @param glob the glob pattern references should match * @return 0 or an error code diff --git a/src/revwalk.c b/src/revwalk.c index 3cc3140b8..63d78fe64 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -110,7 +110,7 @@ static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commi return error; } -static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) +static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, int from_glob) { git_oid commit_id; int error; @@ -124,6 +124,10 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) git_object_free(oobj); if (error == GIT_ENOTFOUND) { + /* If this comes from e.g. push_glob("tags"), ignore this */ + if (from_glob) + return 0; + giterr_set(GITERR_INVALID, "Object is not a committish"); return -1; } @@ -151,24 +155,24 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) int git_revwalk_push(git_revwalk *walk, const git_oid *oid) { assert(walk && oid); - return push_commit(walk, oid, 0); + return push_commit(walk, oid, 0, false); } int git_revwalk_hide(git_revwalk *walk, const git_oid *oid) { assert(walk && oid); - return push_commit(walk, oid, 1); + return push_commit(walk, oid, 1, false); } -static int push_ref(git_revwalk *walk, const char *refname, int hide) +static int push_ref(git_revwalk *walk, const char *refname, int hide, int from_glob) { git_oid oid; if (git_reference_name_to_id(&oid, walk->repo, refname) < 0) return -1; - return push_commit(walk, &oid, hide); + return push_commit(walk, &oid, hide, from_glob); } struct push_cb_data { @@ -179,7 +183,7 @@ struct push_cb_data { static int push_glob_cb(const char *refname, void *data_) { struct push_cb_data *data = (struct push_cb_data *)data_; - return push_ref(data->walk, refname, data->hide); + return push_ref(data->walk, refname, data->hide, true); } static int push_glob(git_revwalk *walk, const char *glob, int hide) @@ -229,19 +233,19 @@ int git_revwalk_hide_glob(git_revwalk *walk, const char *glob) int git_revwalk_push_head(git_revwalk *walk) { assert(walk); - return push_ref(walk, GIT_HEAD_FILE, 0); + return push_ref(walk, GIT_HEAD_FILE, 0, false); } int git_revwalk_hide_head(git_revwalk *walk) { assert(walk); - return push_ref(walk, GIT_HEAD_FILE, 1); + return push_ref(walk, GIT_HEAD_FILE, 1, false); } int git_revwalk_push_ref(git_revwalk *walk, const char *refname) { assert(walk && refname); - return push_ref(walk, refname, 0); + return push_ref(walk, refname, 0, false); } int git_revwalk_push_range(git_revwalk *walk, const char *range) @@ -258,10 +262,10 @@ int git_revwalk_push_range(git_revwalk *walk, const char *range) return GIT_EINVALIDSPEC; } - if ((error = push_commit(walk, git_object_id(revspec.from), 1))) + if ((error = push_commit(walk, git_object_id(revspec.from), 1, false))) goto out; - error = push_commit(walk, git_object_id(revspec.to), 0); + error = push_commit(walk, git_object_id(revspec.to), 0, false); out: git_object_free(revspec.from); @@ -272,7 +276,7 @@ out: int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) { assert(walk && refname); - return push_ref(walk, refname, 1); + return push_ref(walk, refname, 1, false); } static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit) diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c index 4621ab8ff..4d6522c1c 100644 --- a/tests/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -255,10 +255,19 @@ void test_revwalk_basic__push_range(void) void test_revwalk_basic__push_mixed(void) { + git_oid oid; + int i = 0; + revwalk_basic_setup_walk(NULL); git_revwalk_reset(_walk); git_revwalk_sorting(_walk, 0); cl_git_pass(git_revwalk_push_glob(_walk, "tags")); - cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 1)); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git rev-list --count --glob=tags #=> 9 */ + cl_assert_equal_i(9, i); } From af81720236f05f72cfe763208143b5dacb2f99a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Feb 2014 15:29:16 +0100 Subject: [PATCH 11/17] revwalk: remove usage of foreach --- src/revwalk.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/revwalk.c b/src/revwalk.c index 63d78fe64..628057fcd 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -180,17 +180,12 @@ struct push_cb_data { int hide; }; -static int push_glob_cb(const char *refname, void *data_) -{ - struct push_cb_data *data = (struct push_cb_data *)data_; - return push_ref(data->walk, refname, data->hide, true); -} - static int push_glob(git_revwalk *walk, const char *glob, int hide) { int error = 0; git_buf buf = GIT_BUF_INIT; - struct push_cb_data data; + git_reference *ref; + git_reference_iterator *iter; size_t wildcard; assert(walk && glob); @@ -208,12 +203,20 @@ static int push_glob(git_revwalk *walk, const char *glob, int hide) if (!glob[wildcard]) git_buf_put(&buf, "/*", 2); - data.walk = walk; - data.hide = hide; + if ((error = git_reference_iterator_glob_new(&iter, walk->repo, buf.ptr)) < 0) + goto out; - error = git_reference_foreach_glob( - walk->repo, git_buf_cstr(&buf), push_glob_cb, &data); + while ((error = git_reference_next(&ref, iter)) == 0) { + error = push_ref(walk, git_reference_name(ref), hide, true); + git_reference_free(ref); + if (error < 0) + break; + } + git_reference_iterator_free(iter); + if (error == GIT_ITEROVER) + error = 0; +out: git_buf_free(&buf); return error; } From d18209eef9a7fbbccb495b6e5450d14a76623847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 1 Feb 2014 16:27:42 +0100 Subject: [PATCH 12/17] revwalk: add a test for pushing all references This used to be broken, let's make sure we don't break this use-case. --- tests/revwalk/basic.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c index 4d6522c1c..9fe8a350b 100644 --- a/tests/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -271,3 +271,22 @@ void test_revwalk_basic__push_mixed(void) /* git rev-list --count --glob=tags #=> 9 */ cl_assert_equal_i(9, i); } + +void test_revwalk_basic__push_all(void) +{ + git_oid oid; + int i = 0; + + revwalk_basic_setup_walk(NULL); + + git_revwalk_reset(_walk); + git_revwalk_sorting(_walk, 0); + cl_git_pass(git_revwalk_push_glob(_walk, "*")); + + while (git_revwalk_next(&oid, _walk) == 0) { + i++; + } + + /* git rev-list --count --all #=> 15 */ + cl_assert_equal_i(15, i); +} From c74077d13c582ff46668f3ace4c54f20287cfed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 2 Feb 2014 12:08:18 +0100 Subject: [PATCH 13/17] revparse: do look at all refs when matching text Now that we no longer fail to push non-commits on a glob, let's search on all refs when we rev-parse syntax asks us to match text. --- src/revparse.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/revparse.c b/src/revparse.c index 5cce3be63..60872e187 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -490,8 +490,7 @@ static int handle_grep_syntax(git_object **out, git_repository *repo, const git_ git_revwalk_sorting(walk, GIT_SORT_TIME); if (spec_oid == NULL) { - // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails - if ((error = git_revwalk_push_glob(walk, GIT_REFS_HEADS_DIR "*")) < 0) + if ((error = git_revwalk_push_glob(walk, "refs/*")) < 0) goto cleanup; } else if ((error = git_revwalk_push(walk, spec_oid)) < 0) goto cleanup; From d60064132aa0ffe2bb7a02716b054b0456947e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 30 Jan 2014 17:24:46 +0100 Subject: [PATCH 14/17] docs: produce literal asterisks --- include/git2/revwalk.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index c59b79938..850a1a56d 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -108,7 +108,7 @@ GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *id); * pattern will be pushed to the revision walker. * * A leading 'refs/' is implied if not present as well as a trailing - * '/ *' if the glob lacks '?', '*' or '['. + * '/\*' if the glob lacks '?', '\*' or '['. * * @param walk the walker being used for the traversal * @param glob the glob pattern references should match @@ -147,7 +147,7 @@ GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *commit_id); * revision walk. * * A leading 'refs/' is implied if not present as well as a trailing - * '/ *' if the glob lacks '?', '*' or '['. + * '/\*' if the glob lacks '?', '\*' or '['. * * @param walk the walker being used for the traversal * @param glob the glob pattern references should match From a6563619e98437a99ff49eef590f62dbb1584358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 5 Feb 2014 13:01:54 +0100 Subject: [PATCH 15/17] commit: faster parsing The current code issues a lot of strncmp() calls in order to check for the end of the header, simply in order to copy it and start going through it again. These are a lot of calls for something we can check as we go along. Knowing the amount of parents beforehand to reduce allocations in extreme cases does not make up for them. Instead start parsing immediately and check for the double-newline after each header field, leaving the raw_header allocation for the end, which lets us go through the header once and reduces the amount of strncmp() calls significantly. In unscientific testing, this has reduced a shortlog-like usage (walking though the whole history of a branch and extracting data from the commits) of git.git from ~830ms to ~700ms and makes the time we spend in strncmp() negligible. --- src/commit.c | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/src/commit.c b/src/commit.c index da7c4992e..730fa6403 100644 --- a/src/commit.c +++ b/src/commit.c @@ -164,33 +164,15 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) const char *buffer_start = git_odb_object_data(odb_obj), *buffer; const char *buffer_end = buffer_start + git_odb_object_size(odb_obj); git_oid parent_id; - uint32_t parent_count = 0; size_t header_len; - /* find end-of-header (counting parents as we go) */ - for (buffer = buffer_start; buffer < buffer_end; ++buffer) { - if (!strncmp("\n\n", buffer, 2)) { - ++buffer; - break; - } - if (!strncmp("\nparent ", buffer, strlen("\nparent "))) - ++parent_count; - } + buffer = buffer_start; - header_len = buffer - buffer_start; - commit->raw_header = git__strndup(buffer_start, header_len); - GITERR_CHECK_ALLOC(commit->raw_header); - - /* point "buffer" to header data */ - buffer = commit->raw_header; - buffer_end = commit->raw_header + header_len; - - if (parent_count < 1) - parent_count = 1; - - git_array_init_to_size(commit->parent_ids, parent_count); + /* Allocate for one, which will allow not to realloc 90% of the time */ + git_array_init_to_size(commit->parent_ids, 1); GITERR_CHECK_ARRAY(commit->parent_ids); + /* The tree is always the first field */ if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) goto bad_buffer; @@ -221,6 +203,9 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) /* Parse add'l header entries */ while (buffer < buffer_end) { const char *eoln = buffer; + if (buffer[-1] == '\n' && buffer[0] == '\n') + break; + while (eoln < buffer_end && *eoln != '\n') ++eoln; @@ -236,13 +221,12 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) buffer = eoln; } - /* point "buffer" to data after header */ - buffer = git_odb_object_data(odb_obj); - buffer_end = buffer + git_odb_object_size(odb_obj); + header_len = buffer - buffer_start; + commit->raw_header = git__strndup(buffer_start, header_len); + GITERR_CHECK_ALLOC(commit->raw_header); - buffer += header_len; - if (*buffer == '\n') - ++buffer; + /* point "buffer" to data after header, +1 for the final LF */ + buffer = buffer_start + header_len + 1; /* extract commit message */ if (buffer <= buffer_end) { From 1e6f0ac4360bae25ef0a9618c9b091192b8e8997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 5 Feb 2014 13:49:51 +0100 Subject: [PATCH 16/17] utils: don't reimplement strnlen The standard library provides a very nice strnlen function, which knows to use SSE, let's not reimplement it ourselves. --- src/util.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.h b/src/util.h index f9de909e9..c18221f49 100644 --- a/src/util.h +++ b/src/util.h @@ -7,6 +7,7 @@ #ifndef INCLUDE_util_h__ #define INCLUDE_util_h__ +#include "posix.h" #include "common.h" #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) @@ -50,8 +51,7 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) size_t length = 0; char *ptr; - while (length < n && str[length]) - ++length; + length = p_strnlen(str, n); ptr = (char*)git__malloc(length + 1); From 24f3024f36ca44ca8bb32e1a80a048ac4301eaf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 5 Feb 2014 14:31:01 +0100 Subject: [PATCH 17/17] Split p_strlen into its own header We need this from util.h and posix.h, but the latter includes common.h which includes util.h, which means p_strlen is not defined by the time we get to git__strndup(). Split the definition on p_strlen() off into its own header so we can use it in util.h. --- src/posix.h | 13 +------------ src/strnlen.h | 23 +++++++++++++++++++++++ src/util.h | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 src/strnlen.h diff --git a/src/posix.h b/src/posix.h index 0d9be49a9..6d3a84eba 100644 --- a/src/posix.h +++ b/src/posix.h @@ -89,18 +89,7 @@ extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result); # include "unix/posix.h" #endif -#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) -# define NO_STRNLEN -#endif - -#ifdef NO_STRNLEN -GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { - const char *end = memchr(s, 0, maxlen); - return end ? (size_t)(end - s) : maxlen; -} -#else -# define p_strnlen strnlen -#endif +#include "strnlen.h" #ifdef NO_READDIR_R # include diff --git a/src/strnlen.h b/src/strnlen.h new file mode 100644 index 000000000..007da2e55 --- /dev/null +++ b/src/strnlen.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_strlen_h__ +#define INCLUDE_strlen_h__ + +#if defined(__MINGW32__) || defined(__sun) || defined(__APPLE__) +# define NO_STRNLEN +#endif + +#ifdef NO_STRNLEN +GIT_INLINE(size_t) p_strnlen(const char *s, size_t maxlen) { + const char *end = memchr(s, 0, maxlen); + return end ? (size_t)(end - s) : maxlen; +} +#else +# define p_strnlen strnlen +#endif + +#endif diff --git a/src/util.h b/src/util.h index c18221f49..e378786d9 100644 --- a/src/util.h +++ b/src/util.h @@ -7,8 +7,8 @@ #ifndef INCLUDE_util_h__ #define INCLUDE_util_h__ -#include "posix.h" #include "common.h" +#include "strnlen.h" #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #define bitsizeof(x) (CHAR_BIT * sizeof(x))