From 56453d346864e312ec138626a3fc920c39890f0d Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 2 Sep 2011 13:44:42 +0200 Subject: [PATCH] status: enhance determination of status for a single file - fix retrieval of a file status when working against a newly initialized repository - reduce memory pressure - prevents a directory from being tested --- include/git2/status.h | 4 +- src/status.c | 203 +++++++++++++++++++++++++----------------- tests/t12-repo.c | 2 - tests/t18-status.c | 63 +++++++++++++ tests/test_helpers.h | 1 + 5 files changed, 188 insertions(+), 85 deletions(-) diff --git a/include/git2/status.h b/include/git2/status.h index fead43b02..622d9535d 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -70,7 +70,9 @@ GIT_EXTERN(int) git_status_foreach(git_repository *repo, int (*callback)(const c * @param status_flags the status value * @param repo a repository object * @param path the file to retrieve status for, rooted at the repo's workdir - * @return GIT_SUCCESS or an error code + * @return GIT_EINVALIDPATH when `path` points at a folder, GIT_ENOTFOUND when + * the file doesn't exist in any of HEAD, the index or the worktree, + * GIT_SUCCESS otherwise */ GIT_EXTERN(int) git_status_file(unsigned int *status_flags, git_repository *repo, const char *path); diff --git a/src/status.c b/src/status.c index d9613c129..72792a635 100644 --- a/src/status.c +++ b/src/status.c @@ -30,10 +30,9 @@ #include "vector.h" #include "tree.h" #include "git2/status.h" +#include "repository.h" struct status_entry { - char path[GIT_PATH_MAX]; - git_index_time mtime; git_oid head_oid; @@ -41,6 +40,8 @@ struct status_entry { git_oid wt_oid; unsigned int status_flags:6; + + char path[GIT_FLEX_ARRAY]; /* more */ }; static int status_cmp(const void *a, const void *b) @@ -66,11 +67,17 @@ static int find_status_entry(git_vector *entries, const char *path) static struct status_entry *new_status_entry(git_vector *entries, const char *path) { - struct status_entry *e = git__malloc(sizeof(struct status_entry)); - memset(e, 0x0, sizeof(struct status_entry)); + struct status_entry *e = git__malloc(sizeof(*e) + strlen(path) + 1); + if (e == NULL) + return NULL; + + memset(e, 0x0, sizeof(*e)); + if (entries != NULL) git_vector_insert(entries, e); + strcpy(e->path, path); + return e; } @@ -102,33 +109,39 @@ static void recurse_tree_entries(git_tree *tree, git_vector *entries, char *path } } -static void recurse_tree_entry(git_tree *tree, struct status_entry *e, const char *path) +static int recurse_tree_entry(git_tree *tree, struct status_entry *e, const char *path) { char *dir_sep; - char buffer[GIT_PATH_MAX]; const git_tree_entry *tree_entry; git_tree *subtree; + int error = GIT_SUCCESS; - strcpy(buffer, path); + dir_sep = strchr(path, '/'); + if (!dir_sep) { + tree_entry = git_tree_entry_byname(tree, path); + if (tree_entry == NULL) + return GIT_SUCCESS; /* The leaf doesn't exist in the tree*/ - dir_sep = strchr(buffer, '/'); - if (dir_sep) { - *dir_sep = '\0'; - - tree_entry = git_tree_entry_byname(tree, buffer); - if (tree_entry != NULL) { - if (git_tree_lookup(&subtree, tree->object.repo, &tree_entry->oid) == GIT_SUCCESS) { - recurse_tree_entry(subtree, e, dir_sep+1); - git_tree_close(subtree); - return; - } - } + git_oid_cpy(&e->head_oid, &tree_entry->oid); + return GIT_SUCCESS; } + /* Retrieve subtree name */ + *dir_sep = '\0'; + tree_entry = git_tree_entry_byname(tree, path); - if (tree_entry != NULL) { - git_oid_cpy(&e->head_oid, &tree_entry->oid); - } + if (tree_entry == NULL) + return GIT_SUCCESS; /* The subtree doesn't exist in the tree*/ + + *dir_sep = '/'; + + /* Retreive subtree */ + if ((error = git_tree_lookup(&subtree, tree->object.repo, &tree_entry->oid)) < GIT_SUCCESS) + return git__throw(GIT_EOBJCORRUPTED, "Can't find tree object '%s'", &tree_entry->filename); + + error = recurse_tree_entry(subtree, e, dir_sep+1); + git_tree_close(subtree); + return error; } struct status_st { @@ -149,7 +162,7 @@ static int dirent_cb(void *state, char *full_path) struct stat filest; git_oid oid; - if ((git_futils_isdir(full_path) == GIT_SUCCESS) && (!strcmp(".git", file_path))) + if ((git_futils_isdir(full_path) == GIT_SUCCESS) && (!strcmp(DOT_GIT, file_path))) return 0; if (git_futils_isdir(full_path) == GIT_SUCCESS) @@ -175,38 +188,6 @@ static int dirent_cb(void *state, char *full_path) return 0; } -static int single_dirent_cb(void *state, char *full_path) -{ - struct status_st *st = (struct status_st *)state; - struct status_entry *e = st->entry.e; - - char *file_path = full_path + st->workdir_path_len; - struct stat filest; - git_oid oid; - - if ((git_futils_isdir(full_path) == GIT_SUCCESS) && (!strcmp(".git", file_path))) - return 0; - - if (git_futils_isdir(full_path) == GIT_SUCCESS) - return git_futils_direach(full_path, GIT_PATH_MAX, single_dirent_cb, state); - - if (!strcmp(file_path, e->path)) { - if (p_stat(full_path, &filest) < 0) - return git__throw(GIT_EOSERR, "Failed to read file %s", full_path); - - if (e->mtime.seconds == (git_time_t)filest.st_mtime) { - git_oid_cpy(&e->wt_oid, &e->index_oid); - return 1; - } - - git_odb_hashfile(&oid, full_path, GIT_OBJ_BLOB); - git_oid_cpy(&e->wt_oid, &oid); - return 1; - } - - return 0; -} - static int set_status_flags(struct status_entry *e) { git_oid zero; @@ -305,24 +286,81 @@ int git_status_foreach(git_repository *repo, int (*callback)(const char *, unsig return GIT_SUCCESS; } +static int retrieve_head_tree(git_tree **tree_out, git_repository *repo) +{ + git_reference *resolved_head_ref; + git_commit *head_commit = NULL; + git_tree *tree; + int error = GIT_SUCCESS; + + *tree_out = NULL; + + error = git_repository_head(&resolved_head_ref, repo); + if (error != GIT_SUCCESS && error != GIT_ENOTFOUND) + return git__rethrow(error, "HEAD can't be resolved"); + + /* + * We assume that a situation where HEAD exists but can not be resolved is valid. + * A new repository fits this description for instance. + */ + + if (error == GIT_ENOTFOUND) + return GIT_SUCCESS; + + if ((error = git_commit_lookup(&head_commit, repo, git_reference_oid(resolved_head_ref))) < GIT_SUCCESS) + return git__rethrow(error, "The tip of HEAD can't be retrieved"); + + if ((error = git_commit_tree(&tree, head_commit)) < GIT_SUCCESS) { + error = git__rethrow(error, "The tree of HEAD can't be retrieved"); + goto exit; + } + + *tree_out = tree; + +exit: + git_commit_close(head_commit); + return error; +} + int git_status_file(unsigned int *status_flags, git_repository *repo, const char *path) { struct status_entry *e; - git_index *index; + git_index *index = NULL; git_index_entry *index_entry; char temp_path[GIT_PATH_MAX]; - int idx, error; - git_tree *tree; - git_reference *head_ref, *resolved_head_ref; - git_commit *head_commit; - struct status_st dirent_st; + int idx, error = GIT_SUCCESS; + git_tree *tree = NULL; + struct stat filest; - assert(status_flags); + assert(status_flags && repo && path); + + git_path_join(temp_path, repo->path_workdir, path); + if (git_futils_isdir(temp_path) == GIT_SUCCESS) + return git__throw(GIT_EINVALIDPATH, "Failed to determine status of file '%s'. Provided path leads to a folder, not a file", path); e = new_status_entry(NULL, path); + if (e == NULL) + return GIT_ENOMEM; + + /* Find file in Workdir */ + if (git_futils_exists(temp_path) == GIT_SUCCESS) { + if (p_stat(temp_path, &filest) < GIT_SUCCESS) { + error = git__throw(GIT_EOSERR, "Failed to determine status of file '%s'. Can't read file", temp_path); + goto exit; + } + + if (e->mtime.seconds == (git_time_t)filest.st_mtime) + git_oid_cpy(&e->wt_oid, &e->index_oid); + else + git_odb_hashfile(&e->wt_oid, temp_path, GIT_OBJ_BLOB); + } + + /* Find file in Index */ + if ((error = git_repository_index(&index, repo)) < GIT_SUCCESS) { + error = git__rethrow(error, "Failed to determine status of file '%s'. Index can't be opened", path); + goto exit; + } - // Find file in Index - git_repository_index(&index, repo); idx = git_index_find(index, path); if (idx >= 0) { index_entry = git_index_get(index, idx); @@ -331,31 +369,32 @@ int git_status_file(unsigned int *status_flags, git_repository *repo, const char } git_index_free(index); - // Find file in HEAD - git_reference_lookup(&head_ref, repo, GIT_HEAD_FILE); - git_reference_resolve(&resolved_head_ref, head_ref); + if ((error = retrieve_head_tree(&tree, repo)) < GIT_SUCCESS) { + error = git__rethrow(error, "Failed to determine status of file '%s'", path); + goto exit; + } - git_commit_lookup(&head_commit, repo, git_reference_oid(resolved_head_ref)); + /* If the repository is not empty, try and locate the file in HEAD */ + if (tree != NULL) { + strcpy(temp_path, path); - git_commit_tree(&tree, head_commit); - recurse_tree_entry(tree, e, path); - git_tree_close(tree); - git_commit_close(head_commit); - - // Find file in Workdir - dirent_st.workdir_path_len = strlen(repo->path_workdir); - dirent_st.entry.e = e; - strcpy(temp_path, repo->path_workdir); - git_futils_direach(temp_path, GIT_PATH_MAX, single_dirent_cb, &dirent_st); + error = recurse_tree_entry(tree, e, temp_path); + if (error < GIT_SUCCESS) { + error = git__rethrow(error, "Failed to determine status of file '%s'. An error occured while processing the tree", path); + goto exit; + } + } + /* Determine status */ if ((error = set_status_flags(e)) < GIT_SUCCESS) { - free(e); - return git__throw(error, "Nonexistent file"); + error = git__throw(error, "Nonexistent file"); + goto exit; } *status_flags = e->status_flags; +exit: + git_tree_close(tree); free(e); - - return GIT_SUCCESS; + return error; } diff --git a/tests/t12-repo.c b/tests/t12-repo.c index cc8942f40..1d9132cfd 100644 --- a/tests/t12-repo.c +++ b/tests/t12-repo.c @@ -212,8 +212,6 @@ BEGIN_TEST(open0, "Open a bare repository that has just been initialized by git" must_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, 1)); END_TEST -#define EMPTY_REPOSITORY_FOLDER TEST_RESOURCES "/empty_standard_repo/.gitted/" - BEGIN_TEST(open1, "Open a standard repository that has just been initialized by git") git_repository *repo; diff --git a/tests/t18-status.c b/tests/t18-status.c index c30c541df..774dab6b1 100644 --- a/tests/t18-status.c +++ b/tests/t18-status.c @@ -174,6 +174,66 @@ BEGIN_TEST(singlestatus1, "test retrieving status for nonexistent file") git_futils_rmdir_r(TEMP_REPO_FOLDER, 1); END_TEST +BEGIN_TEST(singlestatus2, "test retrieving status for a non existent file in an empty repository") + git_repository *repo; + unsigned int status_flags; + int error; + + must_pass(copydir_recurs(EMPTY_REPOSITORY_FOLDER, TEST_STD_REPO_FOLDER)); + must_pass(remove_placeholders(TEST_STD_REPO_FOLDER, "dummy-marker.txt")); + must_pass(git_repository_open(&repo, TEST_STD_REPO_FOLDER)); + + error = git_status_file(&status_flags, repo, "nonexistent"); + must_be_true(error == GIT_ENOTFOUND); + + git_repository_free(repo); + + git_futils_rmdir_r(TEMP_REPO_FOLDER, 1); +END_TEST + +BEGIN_TEST(singlestatus3, "test retrieving status for a new file in an empty repository") + git_repository *repo; + unsigned int status_flags; + char file_path[GIT_PATH_MAX]; + char filename[] = "new_file"; + int fd; + + must_pass(copydir_recurs(EMPTY_REPOSITORY_FOLDER, TEST_STD_REPO_FOLDER)); + must_pass(remove_placeholders(TEST_STD_REPO_FOLDER, "dummy-marker.txt")); + + git_path_join(file_path, TEMP_REPO_FOLDER, filename); + fd = p_creat(file_path, 0644); + must_pass(fd); + must_pass(p_write(fd, "new_file\n", 9)); + must_pass(p_close(fd)); + + must_pass(git_repository_open(&repo, TEST_STD_REPO_FOLDER)); + + must_pass(git_status_file(&status_flags, repo, filename)); + must_be_true(status_flags == GIT_STATUS_WT_NEW); + + git_repository_free(repo); + + git_futils_rmdir_r(TEMP_REPO_FOLDER, 1); +END_TEST + +BEGIN_TEST(singlestatus4, "can't determine the status for a folder") + git_repository *repo; + unsigned int status_flags; + int error; + + must_pass(copydir_recurs(STATUS_WORKDIR_FOLDER, TEMP_REPO_FOLDER)); + must_pass(git_futils_mv_atomic(STATUS_REPOSITORY_TEMP_FOLDER, TEST_STD_REPO_FOLDER)); + must_pass(git_repository_open(&repo, TEST_STD_REPO_FOLDER)); + + error = git_status_file(&status_flags, repo, "subdir"); + must_be_true(error == GIT_EINVALIDPATH); + + git_repository_free(repo); + + git_futils_rmdir_r(TEMP_REPO_FOLDER, 1); +END_TEST + BEGIN_SUITE(status) ADD_TEST(file0); @@ -181,4 +241,7 @@ BEGIN_SUITE(status) ADD_TEST(singlestatus0); ADD_TEST(singlestatus1); + ADD_TEST(singlestatus2); + ADD_TEST(singlestatus3); + ADD_TEST(singlestatus4); END_SUITE \ No newline at end of file diff --git a/tests/test_helpers.h b/tests/test_helpers.h index 75027dd6f..53361b7b1 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -37,6 +37,7 @@ #define TEST_INDEX_PATH (REPOSITORY_FOLDER "index") #define TEST_INDEX2_PATH (TEST_RESOURCES "/gitgit.index") #define TEST_INDEXBIG_PATH (TEST_RESOURCES "/big.index") +#define EMPTY_REPOSITORY_FOLDER TEST_RESOURCES "/empty_standard_repo/.gitted/" #define TEMP_FOLDER "" #define TEMP_REPO_FOLDER TEMP_FOLDER TEST_REPOSITORY_NAME "/"