summaryrefslogtreecommitdiff
path: root/src/status.c
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2012-05-15 14:17:39 -0700
committerRussell Belfer <rb@github.com>2012-05-15 14:34:15 -0700
commit41a82592ef56a216f96558942d717af15589071d (patch)
tree8f9c2969000d388f1091b2cd134776bee59e29c6 /src/status.c
parent54695f4098c75801f477f5bc229a52653484e08a (diff)
downloadlibgit2-41a82592ef56a216f96558942d717af15589071d.tar.gz
Ranged iterators and rewritten git_status_file
The goal of this work is to rewrite git_status_file to use the same underlying code as git_status_foreach. This is done in 3 phases: 1. Extend iterators to allow ranged iteration with start and end prefixes for the range of file names to be covered. 2. Improve diff so that when there is a pathspec and there is a common non-wildcard prefix of the pathspec, it will use ranged iterators to minimize excess iteration. 3. Rewrite git_status_file to call git_status_foreach_ext with a pathspec that covers just the one file being checked. Since ranged iterators underlie the status & diff implementation, this is actually fairly efficient. The workdir iterator does end up loading the contents of all the directories down to the single file, which should ideally be avoided, but it is pretty good.
Diffstat (limited to 'src/status.c')
-rw-r--r--src/status.c252
1 files changed, 39 insertions, 213 deletions
diff --git a/src/status.c b/src/status.c
index 1c5609cd8..e9ad3cfe4 100644
--- a/src/status.c
+++ b/src/status.c
@@ -70,7 +70,7 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
int git_status_foreach_ext(
git_repository *repo,
- git_status_options *opts,
+ const git_status_options *opts,
int (*cb)(const char *, unsigned int, void *),
void *cbdata)
{
@@ -163,245 +163,71 @@ int git_status_foreach(
return git_status_foreach_ext(repo, &opts, callback, payload);
}
-
-/*
- * the old stuff
- */
-
-struct status_entry {
- git_index_time mtime;
-
- git_oid head_oid;
- git_oid index_oid;
- git_oid wt_oid;
-
- unsigned int status_flags;
-
- char path[GIT_FLEX_ARRAY]; /* more */
+struct status_file_info {
+ unsigned int count;
+ unsigned int status;
+ char *expected;
};
-static struct status_entry *status_entry_new(git_vector *entries, const char *path)
-{
- struct status_entry *e = git__calloc(sizeof(*e) + strlen(path) + 1, 1);
- if (e == NULL)
- return NULL;
-
- if (entries != NULL)
- git_vector_insert(entries, e);
-
- strcpy(e->path, path);
-
- return e;
-}
-
-GIT_INLINE(void) status_entry_update_from_tree_entry(struct status_entry *e, const git_tree_entry *tree_entry)
-{
- assert(e && tree_entry);
-
- git_oid_cpy(&e->head_oid, &tree_entry->oid);
-}
-
-GIT_INLINE(void) status_entry_update_from_index_entry(struct status_entry *e, const git_index_entry *index_entry)
-{
- assert(e && index_entry);
-
- git_oid_cpy(&e->index_oid, &index_entry->oid);
- e->mtime = index_entry->mtime;
-}
-
-static void status_entry_update_from_index(struct status_entry *e, git_index *index)
-{
- int idx;
- git_index_entry *index_entry;
-
- assert(e && index);
-
- idx = git_index_find(index, e->path);
- if (idx < 0)
- return;
-
- index_entry = git_index_get(index, idx);
-
- status_entry_update_from_index_entry(e, index_entry);
-}
-
-static int status_entry_update_from_workdir(struct status_entry *e, const char* full_path)
-{
- struct stat filest;
-
- if (p_stat(full_path, &filest) < 0) {
- giterr_set(GITERR_OS, "Cannot access file '%s'", full_path);
- return GIT_ENOTFOUND;
- }
-
- 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, full_path, GIT_OBJ_BLOB);
-
- return 0;
-}
-
-static int status_entry_update_flags(struct status_entry *e)
-{
- git_oid zero;
- int head_zero, index_zero, wt_zero;
-
- memset(&zero, 0x0, sizeof(git_oid));
-
- head_zero = git_oid_cmp(&zero, &e->head_oid);
- index_zero = git_oid_cmp(&zero, &e->index_oid);
- wt_zero = git_oid_cmp(&zero, &e->wt_oid);
-
- if (head_zero == 0 && index_zero == 0 && wt_zero == 0)
- return GIT_ENOTFOUND;
-
- if (head_zero == 0 && index_zero != 0)
- e->status_flags |= GIT_STATUS_INDEX_NEW;
- else if (index_zero == 0 && head_zero != 0)
- e->status_flags |= GIT_STATUS_INDEX_DELETED;
- else if (git_oid_cmp(&e->head_oid, &e->index_oid) != 0)
- e->status_flags |= GIT_STATUS_INDEX_MODIFIED;
-
- if (index_zero == 0 && wt_zero != 0)
- e->status_flags |= GIT_STATUS_WT_NEW;
- else if (wt_zero == 0 && index_zero != 0)
- e->status_flags |= GIT_STATUS_WT_DELETED;
- else if (git_oid_cmp(&e->index_oid, &e->wt_oid) != 0)
- e->status_flags |= GIT_STATUS_WT_MODIFIED;
-
- return 0;
-}
-
-static int status_entry_is_ignorable(struct status_entry *e)
+static int get_one_status(const char *path, unsigned int status, void *data)
{
- /* don't ignore files that exist in head or index already */
- return (e->status_flags == GIT_STATUS_WT_NEW);
-}
+ struct status_file_info *sfi = data;
-static int status_entry_update_ignore(struct status_entry *e, git_ignores *ignores, const char *path)
-{
- int ignored;
+ sfi->count++;
+ sfi->status = status;
- if (git_ignore__lookup(ignores, path, &ignored) < 0)
+ if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) {
+ giterr_set(GITERR_INVALID,
+ "Ambiguous path '%s' given to git_status_file", sfi->expected);
return -1;
-
- if (ignored)
- /* toggle off WT_NEW and on IGNORED */
- e->status_flags =
- (e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_IGNORED;
-
- return 0;
-}
-
-static int recurse_tree_entry(git_tree *tree, struct status_entry *e, const char *path)
-{
- char *dir_sep;
- const git_tree_entry *tree_entry;
- git_tree *subtree;
- int error;
-
- dir_sep = strchr(path, '/');
- if (!dir_sep) {
- if ((tree_entry = git_tree_entry_byname(tree, path)) != NULL)
- /* The leaf exists in the tree*/
- status_entry_update_from_tree_entry(e, tree_entry);
- return 0;
- }
-
- /* Retrieve subtree name */
- *dir_sep = '\0';
-
- if ((tree_entry = git_tree_entry_byname(tree, path)) == NULL)
- return 0; /* The subtree doesn't exist in the tree*/
-
- *dir_sep = '/';
-
- /* Retreive subtree */
- error = git_tree_lookup(&subtree, tree->object.repo, &tree_entry->oid);
- if (!error) {
- error = recurse_tree_entry(subtree, e, dir_sep+1);
- git_tree_free(subtree);
}
- return error;
+ return 0;
}
int git_status_file(
- unsigned int *status_flags, git_repository *repo, const char *path)
+ unsigned int *status_flags,
+ git_repository *repo,
+ const char *path)
{
- struct status_entry *e;
- git_index *index = NULL;
- git_buf temp_path = GIT_BUF_INIT;
- int error = 0;
- git_tree *tree = NULL;
- const char *workdir;
+ int error;
+ git_status_options opts;
+ struct status_file_info sfi;
assert(status_flags && repo && path);
- if ((workdir = git_repository_workdir(repo)) == NULL) {
- giterr_set(GITERR_INVALID, "Cannot get file status from bare repo");
+ memset(&sfi, 0, sizeof(sfi));
+ if ((sfi.expected = git__strdup(path)) == NULL)
return -1;
- }
-
- if (git_buf_joinpath(&temp_path, workdir, path) < 0)
- return -1;
-
- if (git_path_isdir(temp_path.ptr)) {
- giterr_set(GITERR_INVALID, "Cannot get file status for directory '%s'", temp_path.ptr);
- git_buf_free(&temp_path);
- return -1;
- }
-
- e = status_entry_new(NULL, path);
- GITERR_CHECK_ALLOC(e);
-
- /* Find file in Workdir */
- if (git_path_exists(temp_path.ptr) == true &&
- (error = status_entry_update_from_workdir(e, temp_path.ptr)) < 0)
- goto cleanup;
-
- /* Find file in Index */
- if ((error = git_repository_index__weakptr(&index, repo)) < 0)
- goto cleanup;
- status_entry_update_from_index(e, index);
- /* Try to find file in HEAD */
- if ((error = git_repository_head_tree(&tree, repo)) < 0)
- goto cleanup;
-
- if (tree != NULL) {
- if ((error = git_buf_sets(&temp_path, path)) < 0 ||
- (error = recurse_tree_entry(tree, e, temp_path.ptr)) < 0)
- goto cleanup;
- }
-
- /* Determine status */
- if ((error = status_entry_update_flags(e)) < 0)
- giterr_set(GITERR_OS, "Cannot find file '%s' to determine status", path);
-
- if (!error && status_entry_is_ignorable(e)) {
- git_ignores ignores;
+ memset(&opts, 0, sizeof(opts));
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
+ GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
+ opts.pathspec.count = 1;
+ opts.pathspec.strings = &sfi.expected;
- if ((error = git_ignore__for_path(repo, path, &ignores)) == 0)
- error = status_entry_update_ignore(e, &ignores, path);
+ error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi);
- git_ignore__free(&ignores);
+ if (!error && !sfi.count) {
+ giterr_set(GITERR_INVALID,
+ "Attempt to get status of nonexistent file '%s'", path);
+ error = GIT_ENOTFOUND;
}
- if (!error)
- *status_flags = e->status_flags;
+ *status_flags = sfi.status;
-cleanup:
- git_buf_free(&temp_path);
- git_tree_free(tree);
- git__free(e);
+ git__free(sfi.expected);
return error;
}
int git_status_should_ignore(
- int *ignored, git_repository *repo, const char *path)
+ int *ignored,
+ git_repository *repo,
+ const char *path)
{
int error;
git_ignores ignores;