From 1ee2ef87ec4c2c63b7c89849eb9daad9e46e6fe7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 21 May 2013 11:05:21 -0500 Subject: [PATCH] status access by index, providing more details to callers --- include/git2/status.h | 63 ++++++++++++ include/git2/types.h | 3 + src/status.c | 232 ++++++++++++++++++++++++++++++------------ src/status.h | 23 +++++ 4 files changed, 255 insertions(+), 66 deletions(-) create mode 100644 src/status.h diff --git a/include/git2/status.h b/include/git2/status.h index 38b6fa5bd..ce1d44a06 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -174,9 +174,72 @@ typedef struct { git_strarray pathspec; } git_status_options; +/** + * A status entry, providing the differences between the file as it exists + * in HEAD and the index, and providing the differences between the index + * and the working directory. + * + * The `status` value provides the status flags for this file. + * + * The `head_to_index` value provides detailed information about the + * differences between the file in HEAD and the file in the index. + * + * The `index_to_workdir` value provides detailed information about the + * differences between the file in the index and the file in the + * working directory. + */ +typedef struct { + git_status_t status; + git_diff_delta *head_to_index; + git_diff_delta *index_to_workdir; +} git_status_entry; + #define GIT_STATUS_OPTIONS_VERSION 1 #define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} +/** + * Gather file status information and populate the `git_status_list`. + * + * @param out Pointer to store the status results in + * @param repo Repository object + * @param opts Status options structure + * @return 0 on success or error code + */ +GIT_EXTERN(int) git_status_list_new( + git_status_list **out, + git_repository *repo, + const git_status_options *opts); + +/** + * Gets the count of status entries in this list. + * + * @param statuslist Existing status list object + * @return the number of status entries + */ +GIT_EXTERN(size_t) git_status_list_entrycount( + git_status_list *statuslist); + +/** + * Get a pointer to one of the entries in the status list. + * + * The entry is not modifiable and should not be freed. + * + * @param statuslist Existing status list object + * @param idx Position of the entry + * @return Pointer to the entry; NULL if out of bounds + */ +GIT_EXTERN(const git_status_entry *) git_status_byindex( + git_status_list *statuslist, + size_t idx); + +/** + * Free an existing status list + * + * @param statuslist Existing status list object + */ +GIT_EXTERN(void) git_status_list_free( + git_status_list *statuslist); + /** * Gather file status information and run callbacks as requested. * diff --git a/include/git2/types.h b/include/git2/types.h index 1bfa73be6..dc344075c 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -174,6 +174,9 @@ typedef struct git_reference_iterator git_reference_iterator; /** Merge heads, the input to merge */ typedef struct git_merge_head git_merge_head; +/** Representation of a status collection */ +typedef struct git_status_list git_status_list; + /** Basic type of any Git reference. */ typedef enum { diff --git a/src/status.c b/src/status.c index 712e0d515..33e67efac 100644 --- a/src/status.c +++ b/src/status.c @@ -11,6 +11,7 @@ #include "hash.h" #include "vector.h" #include "tree.h" +#include "status.h" #include "git2/status.h" #include "repository.h" #include "ignore.h" @@ -77,71 +78,105 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) return st; } -typedef struct { - git_status_cb cb; - void *payload; - const git_status_options *opts; -} status_user_callback; - -static int status_invoke_cb( - git_diff_delta *h2i, git_diff_delta *i2w, void *payload) +static bool status_is_included( + git_status_list *statuslist, + git_diff_delta *head2idx, + git_diff_delta *idx2wd) { - status_user_callback *usercb = payload; - const char *path = NULL; - unsigned int status = 0; - - if (i2w) { - path = i2w->old_file.path; - status |= workdir_delta2status(i2w->status); - } - if (h2i) { - path = h2i->old_file.path; - status |= index_delta2status(h2i->status); - } - /* if excluding submodules and this is a submodule everywhere */ - if (usercb->opts && - (usercb->opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) - { - bool in_tree = (h2i && h2i->status != GIT_DELTA_ADDED); - bool in_index = (h2i && h2i->status != GIT_DELTA_DELETED); - bool in_wd = (i2w && i2w->status != GIT_DELTA_DELETED); + if ((statuslist->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) { + bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); + bool in_index = (head2idx && head2idx->status != GIT_DELTA_DELETED); + bool in_wd = (idx2wd && idx2wd->status != GIT_DELTA_DELETED); - if ((!in_tree || h2i->old_file.mode == GIT_FILEMODE_COMMIT) && - (!in_index || h2i->new_file.mode == GIT_FILEMODE_COMMIT) && - (!in_wd || i2w->new_file.mode == GIT_FILEMODE_COMMIT)) + if ((!in_tree || head2idx->old_file.mode == GIT_FILEMODE_COMMIT) && + (!in_index || head2idx->new_file.mode == GIT_FILEMODE_COMMIT) && + (!in_wd || idx2wd->new_file.mode == GIT_FILEMODE_COMMIT)) return 0; } - return usercb->cb(path, status, usercb->payload); + return 1; } -int git_status_foreach_ext( - git_repository *repo, - const git_status_options *opts, - git_status_cb cb, +static git_status_t status_compute( + git_diff_delta *head2idx, + git_diff_delta *idx2wd) +{ + git_status_t status = 0; + + if (head2idx) + status |= index_delta2status(head2idx->status); + + if (idx2wd) + status |= workdir_delta2status(idx2wd->status); + + return status; +} + +static int status_collect( + git_diff_delta *head2idx, + git_diff_delta *idx2wd, void *payload) { - int err = 0; + git_status_list *statuslist = payload; + git_status_entry *status_entry; + + if (!status_is_included(statuslist, head2idx, idx2wd)) + return 0; + + status_entry = git__malloc(sizeof(git_status_entry)); + GITERR_CHECK_ALLOC(status_entry); + + status_entry->status = status_compute(head2idx, idx2wd); + status_entry->head_to_index = head2idx; + status_entry->index_to_workdir = idx2wd; + + git_vector_insert(&statuslist->paired, status_entry); + + return 0; +} + +git_status_list *git_status_list_alloc(void) +{ + git_status_list *statuslist = NULL; + + if ((statuslist = git__calloc(1, sizeof(git_status_list))) == NULL || + git_vector_init(&statuslist->paired, 0, NULL) < 0) + return NULL; + + return statuslist; +} + +int git_status_list_new( + git_status_list **out, + git_repository *repo, + const git_status_options *opts) +{ + git_status_list *statuslist = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *head2idx = NULL, *idx2wd = NULL; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - status_user_callback usercb; + int error = 0; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); + *out = NULL; + GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); - if (show != GIT_STATUS_SHOW_INDEX_ONLY && - (err = git_repository__ensure_not_bare(repo, "status")) < 0) - return err; + if ((error = git_repository__ensure_not_bare(repo, "status")) < 0) + return error; /* if there is no HEAD, that's okay - we'll make an empty iterator */ - if (((err = git_repository_head_tree(&head, repo)) < 0) && - !(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD)) - return err; + if (((error = git_repository_head_tree(&head, repo)) < 0) && + !(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD)) + return error; + + statuslist = git_status_list_alloc(); + GITERR_CHECK_ALLOC(statuslist); + + memcpy(&statuslist->opts, opts, sizeof(git_status_options)); memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); @@ -163,41 +198,106 @@ int git_status_foreach_ext( diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { - err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt); - if (err < 0) - goto cleanup; + error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt); + + if (error < 0) + goto on_error; } if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt); - if (err < 0) - goto cleanup; - } + error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt); - usercb.cb = cb; - usercb.payload = payload; - usercb.opts = opts; + if (error < 0) + goto on_error; + } if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { - if ((err = git_diff__paired_foreach( - head2idx, NULL, status_invoke_cb, &usercb)) < 0) - goto cleanup; + if ((error = git_diff__paired_foreach(statuslist->head2idx, NULL, status_collect, statuslist)) < 0) + goto on_error; - git_diff_list_free(head2idx); - head2idx = NULL; + git_diff_list_free(statuslist->head2idx); + statuslist->head2idx = NULL; } - err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb); + if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, status_collect, statuslist)) < 0) + goto on_error; -cleanup: + *out = statuslist; + goto done; + +on_error: + git_status_list_free(statuslist); + +done: git_tree_free(head); - git_diff_list_free(head2idx); - git_diff_list_free(idx2wd); - if (err == GIT_EUSER) - giterr_clear(); + return error; +} - return err; +size_t git_status_list_entrycount(git_status_list *statuslist) +{ + assert(statuslist); + + return statuslist->paired.length; +} + +const git_status_entry *git_status_byindex( + git_status_list *statuslist, + size_t i) +{ + assert(statuslist); + + return git_vector_get(&statuslist->paired, i); +} + +void git_status_list_free(git_status_list *statuslist) +{ + git_status_entry *status_entry; + size_t i; + + if (statuslist == NULL) + return; + + git_diff_list_free(statuslist->head2idx); + git_diff_list_free(statuslist->idx2wd); + + git_vector_foreach(&statuslist->paired, i, status_entry) + git__free(status_entry); + + git_vector_free(&statuslist->paired); + + git__free(statuslist); +} + +int git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + git_status_cb cb, + void *payload) +{ + git_status_list *statuslist; + const git_status_entry *status_entry; + size_t i; + int error = 0; + + if ((error = git_status_list_new(&statuslist, repo, opts)) < 0) + return error; + + git_vector_foreach(&statuslist->paired, i, status_entry) { + const char *path = status_entry->head_to_index ? + status_entry->head_to_index->old_file.path : + status_entry->index_to_workdir->old_file.path; + + if (cb(path, status_entry->status, payload) != 0) { + error = GIT_EUSER; + giterr_clear(); + break; + } + } + + git_status_list_free(statuslist); + + return error; } int git_status_foreach( diff --git a/src/status.h b/src/status.h new file mode 100644 index 000000000..b58e0ebd6 --- /dev/null +++ b/src/status.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_status_h__ +#define INCLUDE_status_h__ + +#include "diff.h" +#include "git2/status.h" +#include "git2/diff.h" + +struct git_status_list { + git_status_options opts; + + git_diff_list *head2idx; + git_diff_list *idx2wd; + + git_vector paired; +}; + +#endif