mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-04 15:07:41 +00:00
Merge pull request #570 from arrbee/uniform-iterators
Uniform iterators for trees, index, and workdir
This commit is contained in:
commit
36d72a5125
70
src/attr.c
70
src/attr.c
@ -218,6 +218,48 @@ int git_attr_cache__is_cached(git_repository *repo, const char *path)
|
||||
return (git_hashtable_lookup(repo->attrcache.files, cache_key) == NULL);
|
||||
}
|
||||
|
||||
int git_attr_cache__lookup_or_create_file(
|
||||
git_repository *repo,
|
||||
const char *key,
|
||||
const char *filename,
|
||||
int (*loader)(git_repository *, const char *, git_attr_file *),
|
||||
git_attr_file **file_ptr)
|
||||
{
|
||||
int error;
|
||||
git_attr_cache *cache = &repo->attrcache;
|
||||
git_attr_file *file = NULL;
|
||||
|
||||
file = git_hashtable_lookup(cache->files, key);
|
||||
if (file) {
|
||||
*file_ptr = file;
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (loader && git_path_exists(filename) != GIT_SUCCESS) {
|
||||
*file_ptr = NULL;
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
if ((error = git_attr_file__new(&file)) < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
if (loader)
|
||||
error = loader(repo, filename, file);
|
||||
else
|
||||
error = git_attr_file__set_path(repo, key, file);
|
||||
|
||||
if (error == GIT_SUCCESS)
|
||||
error = git_hashtable_insert(cache->files, file->path, file);
|
||||
|
||||
if (error < GIT_SUCCESS) {
|
||||
git_attr_file__free(file);
|
||||
file = NULL;
|
||||
}
|
||||
|
||||
*file_ptr = file;
|
||||
return error;
|
||||
}
|
||||
|
||||
/* add git_attr_file to vector of files, loading if needed */
|
||||
int git_attr_cache__push_file(
|
||||
git_repository *repo,
|
||||
@ -226,16 +268,14 @@ int git_attr_cache__push_file(
|
||||
const char *filename,
|
||||
int (*loader)(git_repository *, const char *, git_attr_file *))
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
git_attr_cache *cache = &repo->attrcache;
|
||||
int error;
|
||||
git_buf path = GIT_BUF_INIT;
|
||||
git_attr_file *file = NULL;
|
||||
int add_to_cache = 0;
|
||||
const char *cache_key;
|
||||
|
||||
if (base != NULL) {
|
||||
if ((error = git_buf_joinpath(&path, base, filename)) < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
return error;
|
||||
filename = path.ptr;
|
||||
}
|
||||
|
||||
@ -244,28 +284,12 @@ int git_attr_cache__push_file(
|
||||
if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0)
|
||||
cache_key += strlen(git_repository_workdir(repo));
|
||||
|
||||
file = git_hashtable_lookup(cache->files, cache_key);
|
||||
if (file == NULL && git_path_exists(filename) == GIT_SUCCESS) {
|
||||
if ((error = git_attr_file__new(&file)) == GIT_SUCCESS) {
|
||||
if ((error = loader(repo, filename, file)) < GIT_SUCCESS) {
|
||||
git_attr_file__free(file);
|
||||
file = NULL;
|
||||
}
|
||||
}
|
||||
add_to_cache = (error == GIT_SUCCESS);
|
||||
}
|
||||
error = git_attr_cache__lookup_or_create_file(
|
||||
repo, cache_key, filename, loader, &file);
|
||||
|
||||
if (error == GIT_SUCCESS && file != NULL) {
|
||||
/* add file to vector, if we found it */
|
||||
if (error == GIT_SUCCESS && file != NULL)
|
||||
error = git_vector_insert(stack, file);
|
||||
|
||||
/* add file to cache, if it is new */
|
||||
/* do this after above step b/c it is not critical */
|
||||
if (error == GIT_SUCCESS && add_to_cache && file->path != NULL)
|
||||
error = git_hashtable_insert(cache->files, file->path, file);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
git_buf_free(&path);
|
||||
return error;
|
||||
}
|
||||
|
@ -20,6 +20,13 @@ extern int git_attr_cache__init(git_repository *repo);
|
||||
extern int git_attr_cache__insert_macro(
|
||||
git_repository *repo, git_attr_rule *macro);
|
||||
|
||||
extern int git_attr_cache__lookup_or_create_file(
|
||||
git_repository *repo,
|
||||
const char *key,
|
||||
const char *filename,
|
||||
int (*loader)(git_repository *, const char *, git_attr_file *),
|
||||
git_attr_file **file_ptr);
|
||||
|
||||
extern int git_attr_cache__push_file(
|
||||
git_repository *repo,
|
||||
git_vector *stack,
|
||||
|
@ -213,6 +213,12 @@ void git_buf_truncate(git_buf *buf, ssize_t len)
|
||||
}
|
||||
}
|
||||
|
||||
void git_buf_rtruncate_at_char(git_buf *buf, char separator)
|
||||
{
|
||||
int idx = git_buf_rfind_next(buf, separator);
|
||||
git_buf_truncate(buf, idx < 0 ? 0 : idx);
|
||||
}
|
||||
|
||||
void git_buf_swap(git_buf *buf_a, git_buf *buf_b)
|
||||
{
|
||||
git_buf t = *buf_a;
|
||||
@ -327,7 +333,7 @@ int git_buf_join(
|
||||
const char *str_b)
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
size_t strlen_a = strlen(str_a);
|
||||
size_t strlen_a = str_a ? strlen(str_a) : 0;
|
||||
size_t strlen_b = strlen(str_b);
|
||||
int need_sep = 0;
|
||||
ssize_t offset_a = -1;
|
||||
|
@ -84,6 +84,7 @@ int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3
|
||||
void git_buf_clear(git_buf *buf);
|
||||
void git_buf_consume(git_buf *buf, const char *end);
|
||||
void git_buf_truncate(git_buf *buf, ssize_t len);
|
||||
void git_buf_rtruncate_at_char(git_buf *path, char separator);
|
||||
|
||||
int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...);
|
||||
int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b);
|
||||
|
@ -79,6 +79,24 @@ git_off_t git_futils_filesize(git_file fd)
|
||||
return sb.st_size;
|
||||
}
|
||||
|
||||
#define GIT_MODE_PERMS_MASK 0777
|
||||
#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644)
|
||||
#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK)
|
||||
|
||||
mode_t git_futils_canonical_mode(mode_t raw_mode)
|
||||
{
|
||||
if (S_ISREG(raw_mode))
|
||||
return S_IFREG | GIT_CANONICAL_PERMS(raw_mode);
|
||||
else if (S_ISLNK(raw_mode))
|
||||
return S_IFLNK;
|
||||
else if (S_ISDIR(raw_mode))
|
||||
return S_IFDIR;
|
||||
else if (S_ISGITLINK(raw_mode))
|
||||
return S_IFGITLINK;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int git_futils_readbuffer_updated(git_fbuffer *obj, const char *path, time_t *mtime, int *updated)
|
||||
{
|
||||
git_file fd;
|
||||
|
@ -85,12 +85,17 @@ extern int git_futils_mktmp(git_buf *path_out, const char *filename);
|
||||
*/
|
||||
extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode);
|
||||
|
||||
|
||||
/**
|
||||
* Get the filesize in bytes of a file
|
||||
*/
|
||||
extern git_off_t git_futils_filesize(git_file fd);
|
||||
|
||||
/**
|
||||
* Convert a mode_t from the OS to a legal git mode_t value.
|
||||
*/
|
||||
extern mode_t git_futils_canonical_mode(mode_t raw_mode);
|
||||
|
||||
|
||||
/**
|
||||
* Read-only map all or part of a file into memory.
|
||||
* When possible this function should favor a virtual memory
|
||||
|
118
src/ignore.c
118
src/ignore.c
@ -69,38 +69,44 @@ static int load_ignore_file(
|
||||
static int push_one_ignore(void *ref, git_buf *path)
|
||||
{
|
||||
git_ignores *ign = (git_ignores *)ref;
|
||||
return push_ignore(ign->repo, &ign->stack, path->ptr, GIT_IGNORE_FILE);
|
||||
return push_ignore(ign->repo, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
|
||||
}
|
||||
|
||||
int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ignores)
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
git_buf dir = GIT_BUF_INIT;
|
||||
git_config *cfg;
|
||||
const char *workdir = git_repository_workdir(repo);
|
||||
|
||||
assert(ignores);
|
||||
|
||||
ignores->repo = repo;
|
||||
git_buf_init(&ignores->dir, 0);
|
||||
ignores->ign_internal = NULL;
|
||||
git_vector_init(&ignores->ign_path, 8, NULL);
|
||||
git_vector_init(&ignores->ign_global, 2, NULL);
|
||||
|
||||
if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
if ((error = git_path_find_dir(&dir, path, workdir)) < GIT_SUCCESS)
|
||||
if ((error = git_path_find_dir(&ignores->dir, path, workdir)) < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
ignores->repo = repo;
|
||||
ignores->dir = NULL;
|
||||
git_vector_init(&ignores->stack, 2, NULL);
|
||||
|
||||
/* insert internals */
|
||||
if ((error = push_ignore(repo, &ignores->stack, NULL, GIT_IGNORE_INTERNAL)) < GIT_SUCCESS)
|
||||
/* set up internals */
|
||||
error = git_attr_cache__lookup_or_create_file(
|
||||
repo, GIT_IGNORE_INTERNAL, NULL, NULL, &ignores->ign_internal);
|
||||
if (error < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
/* load .gitignore up the path */
|
||||
if ((error = git_path_walk_up(&dir, workdir, push_one_ignore, ignores)) < GIT_SUCCESS)
|
||||
error = git_path_walk_up(&ignores->dir, workdir, push_one_ignore, ignores);
|
||||
if (error < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
/* load .git/info/exclude */
|
||||
if ((error = push_ignore(repo, &ignores->stack, repo->path_repository, GIT_IGNORE_FILE_INREPO)) < GIT_SUCCESS)
|
||||
error = push_ignore(repo, &ignores->ign_global,
|
||||
repo->path_repository, GIT_IGNORE_FILE_INREPO);
|
||||
if (error < GIT_SUCCESS)
|
||||
goto cleanup;
|
||||
|
||||
/* load core.excludesfile */
|
||||
@ -108,7 +114,7 @@ int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ig
|
||||
const char *core_ignore;
|
||||
error = git_config_get_string(cfg, GIT_IGNORE_CONFIG, &core_ignore);
|
||||
if (error == GIT_SUCCESS && core_ignore != NULL)
|
||||
error = push_ignore(repo, &ignores->stack, NULL, core_ignore);
|
||||
error = push_ignore(repo, &ignores->ign_global, NULL, core_ignore);
|
||||
else {
|
||||
error = GIT_SUCCESS;
|
||||
git_clearerror(); /* don't care if attributesfile is not set */
|
||||
@ -117,46 +123,92 @@ int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ig
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (error < GIT_SUCCESS)
|
||||
if (error < GIT_SUCCESS) {
|
||||
git_ignore__free(ignores);
|
||||
git__rethrow(error, "Could not get ignore files for '%s'", path);
|
||||
else
|
||||
ignores->dir = git_buf_detach(&dir);
|
||||
|
||||
git_buf_free(&dir);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_ignore__push_dir(git_ignores *ign, const char *dir)
|
||||
{
|
||||
int error = git_buf_joinpath(&ign->dir, ign->dir.ptr, dir);
|
||||
|
||||
if (error == GIT_SUCCESS)
|
||||
error = push_ignore(
|
||||
ign->repo, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_ignore__pop_dir(git_ignores *ign)
|
||||
{
|
||||
if (ign->ign_path.length > 0) {
|
||||
git_attr_file *file = git_vector_last(&ign->ign_path);
|
||||
if (git__suffixcmp(ign->dir.ptr, file->path) == 0)
|
||||
git_vector_pop(&ign->ign_path);
|
||||
git_buf_rtruncate_at_char(&ign->dir, '/');
|
||||
}
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
void git_ignore__free(git_ignores *ignores)
|
||||
{
|
||||
git__free(ignores->dir);
|
||||
ignores->dir = NULL;
|
||||
git_vector_free(&ignores->stack);
|
||||
/* don't need to free ignores->ign_internal since it is in cache */
|
||||
git_vector_free(&ignores->ign_path);
|
||||
git_vector_free(&ignores->ign_global);
|
||||
git_buf_free(&ignores->dir);
|
||||
}
|
||||
|
||||
static int ignore_lookup_in_rules(
|
||||
git_vector *rules, git_attr_path *path, int *ignored)
|
||||
{
|
||||
unsigned int j;
|
||||
git_attr_fnmatch *match;
|
||||
|
||||
git_vector_rforeach(rules, j, match) {
|
||||
if (git_attr_fnmatch__match(match, path) == GIT_SUCCESS) {
|
||||
*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0);
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
return GIT_ENOTFOUND;
|
||||
}
|
||||
|
||||
int git_ignore__lookup(git_ignores *ignores, const char *pathname, int *ignored)
|
||||
{
|
||||
int error;
|
||||
unsigned int i, j;
|
||||
unsigned int i;
|
||||
git_attr_file *file;
|
||||
git_attr_path path;
|
||||
git_attr_fnmatch *match;
|
||||
|
||||
if ((error = git_attr_path__init(
|
||||
&path, pathname, git_repository_workdir(ignores->repo))) < GIT_SUCCESS)
|
||||
return git__rethrow(error, "Could not get attribute for '%s'", pathname);
|
||||
|
||||
/* first process builtins */
|
||||
error = ignore_lookup_in_rules(
|
||||
&ignores->ign_internal->rules, &path, ignored);
|
||||
if (error == GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
/* next process files in the path */
|
||||
git_vector_foreach(&ignores->ign_path, i, file) {
|
||||
error = ignore_lookup_in_rules(&file->rules, &path, ignored);
|
||||
if (error == GIT_SUCCESS)
|
||||
return error;
|
||||
}
|
||||
|
||||
/* last process global ignores */
|
||||
git_vector_foreach(&ignores->ign_global, i, file) {
|
||||
error = ignore_lookup_in_rules(&file->rules, &path, ignored);
|
||||
if (error == GIT_SUCCESS)
|
||||
return error;
|
||||
}
|
||||
|
||||
*ignored = 0;
|
||||
|
||||
git_vector_foreach(&ignores->stack, i, file) {
|
||||
git_vector_rforeach(&file->rules, j, match) {
|
||||
if (git_attr_fnmatch__match(match, &path) == GIT_SUCCESS) {
|
||||
*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0);
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
}
|
||||
found:
|
||||
|
||||
return error;
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
24
src/ignore.h
24
src/ignore.h
@ -10,14 +10,28 @@
|
||||
#include "repository.h"
|
||||
#include "vector.h"
|
||||
|
||||
/* The git_ignores structure maintains three sets of ignores:
|
||||
* - internal ignores
|
||||
* - per directory ignores
|
||||
* - global ignores (at lower priority than the others)
|
||||
* As you traverse from one directory to another, you can push and pop
|
||||
* directories onto git_ignores list efficiently.
|
||||
*/
|
||||
typedef struct {
|
||||
git_repository *repo;
|
||||
char *dir;
|
||||
git_vector stack;
|
||||
git_buf dir;
|
||||
git_attr_file *ign_internal;
|
||||
git_vector ign_path;
|
||||
git_vector ign_global;
|
||||
} git_ignores;
|
||||
|
||||
extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *stack);
|
||||
extern void git_ignore__free(git_ignores *stack);
|
||||
extern int git_ignore__lookup(git_ignores *stack, const char *path, int *ignored);
|
||||
extern int git_ignore__for_path(
|
||||
git_repository *repo, const char *path, git_ignores *ign);
|
||||
|
||||
extern int git_ignore__push_dir(git_ignores *ign, const char *dir);
|
||||
extern int git_ignore__pop_dir(git_ignores *ign);
|
||||
|
||||
extern void git_ignore__free(git_ignores *ign);
|
||||
extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored);
|
||||
|
||||
#endif
|
||||
|
492
src/iterator.c
Normal file
492
src/iterator.c
Normal file
@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2012 the libgit2 contributors
|
||||
*
|
||||
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
||||
* a Linking Exception. For full terms see the included COPYING file.
|
||||
*/
|
||||
|
||||
#include "iterator.h"
|
||||
#include "tree.h"
|
||||
#include "ignore.h"
|
||||
#include "buffer.h"
|
||||
|
||||
typedef struct tree_iterator_frame tree_iterator_frame;
|
||||
struct tree_iterator_frame {
|
||||
tree_iterator_frame *next;
|
||||
git_tree *tree;
|
||||
unsigned int index;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
git_iterator base;
|
||||
git_repository *repo;
|
||||
tree_iterator_frame *stack;
|
||||
git_index_entry entry;
|
||||
git_buf path;
|
||||
} tree_iterator;
|
||||
|
||||
static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti)
|
||||
{
|
||||
return (ti->stack == NULL) ? NULL :
|
||||
git_tree_entry_byindex(ti->stack->tree, ti->stack->index);
|
||||
}
|
||||
|
||||
static int tree_iterator__current(
|
||||
git_iterator *self, const git_index_entry **entry)
|
||||
{
|
||||
int error;
|
||||
tree_iterator *ti = (tree_iterator *)self;
|
||||
const git_tree_entry *te = tree_iterator__tree_entry(ti);
|
||||
|
||||
*entry = NULL;
|
||||
|
||||
if (te == NULL)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
ti->entry.mode = te->attr;
|
||||
git_oid_cpy(&ti->entry.oid, &te->oid);
|
||||
error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename);
|
||||
if (error < GIT_SUCCESS)
|
||||
return error;
|
||||
ti->entry.path = ti->path.ptr;
|
||||
|
||||
*entry = &ti->entry;
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int tree_iterator__at_end(git_iterator *self)
|
||||
{
|
||||
return (tree_iterator__tree_entry((tree_iterator *)self) == NULL);
|
||||
}
|
||||
|
||||
static tree_iterator_frame *tree_iterator__alloc_frame(git_tree *tree)
|
||||
{
|
||||
tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame));
|
||||
tf->tree = tree;
|
||||
return tf;
|
||||
}
|
||||
|
||||
static int tree_iterator__expand_tree(tree_iterator *ti)
|
||||
{
|
||||
int error;
|
||||
git_tree *subtree;
|
||||
const git_tree_entry *te = tree_iterator__tree_entry(ti);
|
||||
tree_iterator_frame *tf;
|
||||
|
||||
while (te != NULL && entry_is_tree(te)) {
|
||||
error = git_tree_lookup(&subtree, ti->repo, &te->oid);
|
||||
if (error != GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
if ((tf = tree_iterator__alloc_frame(subtree)) == NULL)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
tf->next = ti->stack;
|
||||
ti->stack = tf;
|
||||
|
||||
error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename);
|
||||
if (error < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
te = tree_iterator__tree_entry(ti);
|
||||
}
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static void tree_iterator__pop_frame(tree_iterator *ti)
|
||||
{
|
||||
tree_iterator_frame *tf = ti->stack;
|
||||
ti->stack = tf->next;
|
||||
if (ti->stack != NULL) /* don't free the initial tree */
|
||||
git_tree_free(tf->tree);
|
||||
git__free(tf);
|
||||
}
|
||||
|
||||
static int tree_iterator__advance(
|
||||
git_iterator *self, const git_index_entry **entry)
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
tree_iterator *ti = (tree_iterator *)self;
|
||||
const git_tree_entry *te;
|
||||
|
||||
if (entry != NULL)
|
||||
*entry = NULL;
|
||||
|
||||
while (ti->stack != NULL) {
|
||||
/* remove old entry filename */
|
||||
git_buf_rtruncate_at_char(&ti->path, '/');
|
||||
|
||||
te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index);
|
||||
if (te != NULL)
|
||||
break;
|
||||
|
||||
tree_iterator__pop_frame(ti);
|
||||
git_buf_rtruncate_at_char(&ti->path, '/');
|
||||
}
|
||||
|
||||
if (te && entry_is_tree(te))
|
||||
error = tree_iterator__expand_tree(ti);
|
||||
|
||||
if (error == GIT_SUCCESS && entry != NULL)
|
||||
error = tree_iterator__current(self, entry);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void tree_iterator__free(git_iterator *self)
|
||||
{
|
||||
tree_iterator *ti = (tree_iterator *)self;
|
||||
while (ti->stack != NULL)
|
||||
tree_iterator__pop_frame(ti);
|
||||
git_buf_free(&ti->path);
|
||||
}
|
||||
|
||||
int git_iterator_for_tree(
|
||||
git_repository *repo, git_tree *tree, git_iterator **iter)
|
||||
{
|
||||
int error;
|
||||
tree_iterator *ti = git__calloc(1, sizeof(tree_iterator));
|
||||
if (!ti)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
ti->base.type = GIT_ITERATOR_TREE;
|
||||
ti->base.current = tree_iterator__current;
|
||||
ti->base.at_end = tree_iterator__at_end;
|
||||
ti->base.advance = tree_iterator__advance;
|
||||
ti->base.free = tree_iterator__free;
|
||||
ti->repo = repo;
|
||||
ti->stack = tree_iterator__alloc_frame(tree);
|
||||
|
||||
if ((error = tree_iterator__expand_tree(ti)) < GIT_SUCCESS)
|
||||
git_iterator_free((git_iterator *)ti);
|
||||
else
|
||||
*iter = (git_iterator *)ti;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
git_iterator base;
|
||||
git_index *index;
|
||||
unsigned int current;
|
||||
} index_iterator;
|
||||
|
||||
static int index_iterator__current(
|
||||
git_iterator *self, const git_index_entry **entry)
|
||||
{
|
||||
index_iterator *ii = (index_iterator *)self;
|
||||
*entry = git_index_get(ii->index, ii->current);
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int index_iterator__at_end(git_iterator *self)
|
||||
{
|
||||
index_iterator *ii = (index_iterator *)self;
|
||||
return (ii->current >= git_index_entrycount(ii->index));
|
||||
}
|
||||
|
||||
static int index_iterator__advance(
|
||||
git_iterator *self, const git_index_entry **entry)
|
||||
{
|
||||
index_iterator *ii = (index_iterator *)self;
|
||||
if (ii->current < git_index_entrycount(ii->index))
|
||||
ii->current++;
|
||||
if (entry)
|
||||
*entry = git_index_get(ii->index, ii->current);
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static void index_iterator__free(git_iterator *self)
|
||||
{
|
||||
index_iterator *ii = (index_iterator *)self;
|
||||
git_index_free(ii->index);
|
||||
ii->index = NULL;
|
||||
}
|
||||
|
||||
int git_iterator_for_index(git_repository *repo, git_iterator **iter)
|
||||
{
|
||||
int error;
|
||||
index_iterator *ii = git__calloc(1, sizeof(index_iterator));
|
||||
if (!ii)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
ii->base.type = GIT_ITERATOR_INDEX;
|
||||
ii->base.current = index_iterator__current;
|
||||
ii->base.at_end = index_iterator__at_end;
|
||||
ii->base.advance = index_iterator__advance;
|
||||
ii->base.free = index_iterator__free;
|
||||
ii->current = 0;
|
||||
|
||||
if ((error = git_repository_index(&ii->index, repo)) < GIT_SUCCESS)
|
||||
git__free(ii);
|
||||
else
|
||||
*iter = (git_iterator *)ii;
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
typedef struct workdir_iterator_frame workdir_iterator_frame;
|
||||
struct workdir_iterator_frame {
|
||||
workdir_iterator_frame *next;
|
||||
git_vector entries;
|
||||
unsigned int index;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
git_iterator base;
|
||||
git_repository *repo;
|
||||
size_t root_len;
|
||||
workdir_iterator_frame *stack;
|
||||
git_ignores ignores;
|
||||
git_index_entry entry;
|
||||
git_buf path;
|
||||
int is_ignored;
|
||||
} workdir_iterator;
|
||||
|
||||
static workdir_iterator_frame *workdir_iterator__alloc_frame(void)
|
||||
{
|
||||
workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));
|
||||
if (wf == NULL)
|
||||
return wf;
|
||||
if (git_vector_init(&wf->entries, 0, git__strcmp_cb) != GIT_SUCCESS) {
|
||||
git__free(wf);
|
||||
return NULL;
|
||||
}
|
||||
return wf;
|
||||
}
|
||||
|
||||
static void workdir_iterator__free_frame(workdir_iterator_frame *wf)
|
||||
{
|
||||
unsigned int i;
|
||||
char *path;
|
||||
|
||||
git_vector_foreach(&wf->entries, i, path)
|
||||
git__free(path);
|
||||
git_vector_free(&wf->entries);
|
||||
git__free(wf);
|
||||
}
|
||||
|
||||
static int workdir_iterator__update_entry(workdir_iterator *wi);
|
||||
|
||||
static int workdir_iterator__expand_dir(workdir_iterator *wi)
|
||||
{
|
||||
int error;
|
||||
workdir_iterator_frame *wf = workdir_iterator__alloc_frame();
|
||||
if (wf == NULL)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
/* allocate dir entries with extra byte (the "1" param) so we
|
||||
* can suffix directory names with a "/".
|
||||
*/
|
||||
error = git_path_dirload(wi->path.ptr, wi->root_len, 1, &wf->entries);
|
||||
if (error < GIT_SUCCESS || wf->entries.length == 0) {
|
||||
workdir_iterator__free_frame(wf);
|
||||
return GIT_ENOTFOUND;
|
||||
}
|
||||
|
||||
git_vector_sort(&wf->entries);
|
||||
wf->next = wi->stack;
|
||||
wi->stack = wf;
|
||||
|
||||
/* only push new ignores if this is not top level directory */
|
||||
if (wi->stack->next != NULL) {
|
||||
int slash_pos = git_buf_rfind_next(&wi->path, '/');
|
||||
(void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]);
|
||||
}
|
||||
|
||||
return workdir_iterator__update_entry(wi);
|
||||
}
|
||||
|
||||
static int workdir_iterator__current(
|
||||
git_iterator *self, const git_index_entry **entry)
|
||||
{
|
||||
workdir_iterator *wi = (workdir_iterator *)self;
|
||||
*entry = (wi->entry.path == NULL) ? NULL : &wi->entry;
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
static int workdir_iterator__at_end(git_iterator *self)
|
||||
{
|
||||
return (((workdir_iterator *)self)->entry.path == NULL);
|
||||
}
|
||||
|
||||
static int workdir_iterator__advance(
|
||||
git_iterator *self, const git_index_entry **entry)
|
||||
{
|
||||
int error;
|
||||
workdir_iterator *wi = (workdir_iterator *)self;
|
||||
workdir_iterator_frame *wf;
|
||||
const char *next;
|
||||
|
||||
if (entry != NULL)
|
||||
*entry = NULL;
|
||||
|
||||
if (wi->entry.path == NULL)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
while ((wf = wi->stack) != NULL) {
|
||||
next = git_vector_get(&wf->entries, ++wf->index);
|
||||
if (next != NULL) {
|
||||
if (strcmp(next, DOT_GIT) == 0)
|
||||
continue;
|
||||
/* else found a good entry */
|
||||
break;
|
||||
}
|
||||
|
||||
/* pop workdir directory stack */
|
||||
wi->stack = wf->next;
|
||||
workdir_iterator__free_frame(wf);
|
||||
git_ignore__pop_dir(&wi->ignores);
|
||||
|
||||
if (wi->stack == NULL) {
|
||||
memset(&wi->entry, 0, sizeof(wi->entry));
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
error = workdir_iterator__update_entry(wi);
|
||||
|
||||
if (error == GIT_SUCCESS && entry != NULL)
|
||||
error = workdir_iterator__current(self, entry);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void workdir_iterator__free(git_iterator *self)
|
||||
{
|
||||
workdir_iterator *wi = (workdir_iterator *)self;
|
||||
|
||||
while (wi->stack != NULL) {
|
||||
workdir_iterator_frame *wf = wi->stack;
|
||||
wi->stack = wf->next;
|
||||
workdir_iterator__free_frame(wf);
|
||||
}
|
||||
|
||||
git_ignore__free(&wi->ignores);
|
||||
git_buf_free(&wi->path);
|
||||
}
|
||||
|
||||
static int workdir_iterator__update_entry(workdir_iterator *wi)
|
||||
{
|
||||
int error;
|
||||
struct stat st;
|
||||
char *relpath = git_vector_get(&wi->stack->entries, wi->stack->index);
|
||||
|
||||
error = git_buf_joinpath(
|
||||
&wi->path, git_repository_workdir(wi->repo), relpath);
|
||||
if (error < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
memset(&wi->entry, 0, sizeof(wi->entry));
|
||||
wi->entry.path = relpath;
|
||||
|
||||
/* skip over .git directory */
|
||||
if (strcmp(relpath, DOT_GIT) == 0)
|
||||
return workdir_iterator__advance((git_iterator *)wi, NULL);
|
||||
|
||||
/* if there is an error processing the entry, treat as ignored */
|
||||
wi->is_ignored = 1;
|
||||
|
||||
if (p_lstat(wi->path.ptr, &st) < 0)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
/* TODO: remove shared code for struct stat conversion with index.c */
|
||||
wi->entry.ctime.seconds = (git_time_t)st.st_ctime;
|
||||
wi->entry.mtime.seconds = (git_time_t)st.st_mtime;
|
||||
wi->entry.dev = st.st_rdev;
|
||||
wi->entry.ino = st.st_ino;
|
||||
wi->entry.mode = git_futils_canonical_mode(st.st_mode);
|
||||
wi->entry.uid = st.st_uid;
|
||||
wi->entry.gid = st.st_gid;
|
||||
wi->entry.file_size = st.st_size;
|
||||
|
||||
/* if this is a file type we don't handle, treat as ignored */
|
||||
if (st.st_mode == 0)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
/* okay, we are far enough along to look up real ignore rule */
|
||||
error = git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored);
|
||||
if (error != GIT_SUCCESS)
|
||||
return GIT_SUCCESS;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) {
|
||||
/* create submodule entry */
|
||||
wi->entry.mode = S_IFGITLINK;
|
||||
} else {
|
||||
/* create directory entry that can be advanced into as needed */
|
||||
size_t pathlen = strlen(wi->entry.path);
|
||||
wi->entry.path[pathlen] = '/';
|
||||
wi->entry.path[pathlen + 1] = '\0';
|
||||
wi->entry.mode = S_IFDIR;
|
||||
}
|
||||
}
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
int git_iterator_for_workdir(git_repository *repo, git_iterator **iter)
|
||||
{
|
||||
int error;
|
||||
workdir_iterator *wi = git__calloc(1, sizeof(workdir_iterator));
|
||||
if (!wi)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
wi->base.type = GIT_ITERATOR_WORKDIR;
|
||||
wi->base.current = workdir_iterator__current;
|
||||
wi->base.at_end = workdir_iterator__at_end;
|
||||
wi->base.advance = workdir_iterator__advance;
|
||||
wi->base.free = workdir_iterator__free;
|
||||
wi->repo = repo;
|
||||
|
||||
error = git_buf_sets(&wi->path, git_repository_workdir(repo));
|
||||
if (error == GIT_SUCCESS)
|
||||
error = git_ignore__for_path(repo, "", &wi->ignores);
|
||||
if (error != GIT_SUCCESS) {
|
||||
git__free(wi);
|
||||
return error;
|
||||
}
|
||||
|
||||
wi->root_len = wi->path.size;
|
||||
|
||||
if ((error = workdir_iterator__expand_dir(wi)) < GIT_SUCCESS)
|
||||
git_iterator_free((git_iterator *)wi);
|
||||
else
|
||||
*iter = (git_iterator *)wi;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
int git_iterator_current_tree_entry(
|
||||
git_iterator *iter, const git_tree_entry **tree_entry)
|
||||
{
|
||||
*tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL :
|
||||
tree_iterator__tree_entry((tree_iterator *)iter);
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
int git_iterator_current_is_ignored(git_iterator *iter)
|
||||
{
|
||||
return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :
|
||||
((workdir_iterator *)iter)->is_ignored;
|
||||
}
|
||||
|
||||
int git_iterator_advance_into_directory(
|
||||
git_iterator *iter, const git_index_entry **entry)
|
||||
{
|
||||
workdir_iterator *wi = (workdir_iterator *)iter;
|
||||
|
||||
if (iter->type == GIT_ITERATOR_WORKDIR &&
|
||||
wi->entry.path && S_ISDIR(wi->entry.mode))
|
||||
{
|
||||
if (workdir_iterator__expand_dir(wi) < GIT_SUCCESS)
|
||||
/* if error loading or if empty, skip the directory. */
|
||||
return workdir_iterator__advance(iter, entry);
|
||||
}
|
||||
|
||||
return entry ? git_iterator_current(iter, entry) : GIT_SUCCESS;
|
||||
}
|
99
src/iterator.h
Normal file
99
src/iterator.h
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2009-2012 the libgit2 contributors
|
||||
*
|
||||
* 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_iterator_h__
|
||||
#define INCLUDE_iterator_h__
|
||||
|
||||
#include "common.h"
|
||||
#include "git2/index.h"
|
||||
|
||||
typedef struct git_iterator git_iterator;
|
||||
|
||||
typedef enum {
|
||||
GIT_ITERATOR_TREE = 1,
|
||||
GIT_ITERATOR_INDEX = 2,
|
||||
GIT_ITERATOR_WORKDIR = 3
|
||||
} git_iterator_type_t;
|
||||
|
||||
struct git_iterator {
|
||||
git_iterator_type_t type;
|
||||
int (*current)(git_iterator *, const git_index_entry **);
|
||||
int (*at_end)(git_iterator *);
|
||||
int (*advance)(git_iterator *, const git_index_entry **);
|
||||
void (*free)(git_iterator *);
|
||||
};
|
||||
|
||||
int git_iterator_for_tree(
|
||||
git_repository *repo, git_tree *tree, git_iterator **iter);
|
||||
|
||||
int git_iterator_for_index(
|
||||
git_repository *repo, git_iterator **iter);
|
||||
|
||||
int git_iterator_for_workdir(
|
||||
git_repository *repo, git_iterator **iter);
|
||||
|
||||
/* Entry is not guaranteed to be fully populated. For a tree iterator,
|
||||
* we will only populate the mode, oid and path, for example. For a workdir
|
||||
* iterator, we will not populate the oid.
|
||||
*
|
||||
* You do not need to free the entry. It is still "owned" by the iterator.
|
||||
* Once you call `git_iterator_advance`, then content of the old entry is
|
||||
* no longer guaranteed to be valid.
|
||||
*/
|
||||
GIT_INLINE(int) git_iterator_current(
|
||||
git_iterator *iter, const git_index_entry **entry)
|
||||
{
|
||||
return iter->current(iter, entry);
|
||||
}
|
||||
|
||||
GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)
|
||||
{
|
||||
return iter->at_end(iter);
|
||||
}
|
||||
|
||||
GIT_INLINE(int) git_iterator_advance(
|
||||
git_iterator *iter, const git_index_entry **entry)
|
||||
{
|
||||
return iter->advance(iter, entry);
|
||||
}
|
||||
|
||||
GIT_INLINE(void) git_iterator_free(git_iterator *iter)
|
||||
{
|
||||
iter->free(iter);
|
||||
git__free(iter);
|
||||
}
|
||||
|
||||
GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
|
||||
{
|
||||
return iter->type;
|
||||
}
|
||||
|
||||
extern int git_iterator_current_tree_entry(
|
||||
git_iterator *iter, const git_tree_entry **tree_entry);
|
||||
|
||||
extern int git_iterator_current_is_ignored(git_iterator *iter);
|
||||
|
||||
/**
|
||||
* Iterate into a workdir directory.
|
||||
*
|
||||
* Workdir iterators do not automatically descend into directories (so that
|
||||
* when comparing two iterator entries you can detect a newly created
|
||||
* directory in the workdir). As a result, you may get S_ISDIR items from
|
||||
* a workdir iterator. If you wish to iterate over the contents of the
|
||||
* directories you encounter, then call this function when you encounter
|
||||
* a directory.
|
||||
*
|
||||
* If there are no files in the directory, this will end up acting like a
|
||||
* regular advance and will skip past the directory, so you should be
|
||||
* prepared for that case.
|
||||
*
|
||||
* On non-workdir iterators or if not pointing at a directory, this is a
|
||||
* no-op and will not advance the iterator.
|
||||
*/
|
||||
extern int git_iterator_advance_into_directory(
|
||||
git_iterator *iter, const git_index_entry **entry);
|
||||
|
||||
#endif
|
81
src/path.c
81
src/path.c
@ -398,37 +398,38 @@ int git_path_isfile(const char *path)
|
||||
static int _check_dir_contents(
|
||||
git_buf *dir,
|
||||
const char *sub,
|
||||
int append_on_success,
|
||||
int (*predicate)(const char *))
|
||||
{
|
||||
int error = GIT_SUCCESS;
|
||||
size_t dir_size = dir->size;
|
||||
size_t sub_size = strlen(sub);
|
||||
|
||||
/* leave base valid even if we could not make space for subdir */
|
||||
/* separate allocation and join, so we can always leave git_buf valid */
|
||||
if ((error = git_buf_try_grow(dir, dir_size + sub_size + 2)) < GIT_SUCCESS)
|
||||
return error;
|
||||
|
||||
/* save excursion */
|
||||
git_buf_joinpath(dir, dir->ptr, sub);
|
||||
|
||||
error = (*predicate)(dir->ptr);
|
||||
|
||||
/* restore excursion */
|
||||
if (!append_on_success || error != GIT_SUCCESS)
|
||||
/* restore path */
|
||||
git_buf_truncate(dir, dir_size);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_path_contains_dir(git_buf *base, const char *subdir, int append_if_exists)
|
||||
int git_path_contains(git_buf *dir, const char *item)
|
||||
{
|
||||
return _check_dir_contents(base, subdir, append_if_exists, &git_path_isdir);
|
||||
return _check_dir_contents(dir, item, &git_path_exists);
|
||||
}
|
||||
|
||||
int git_path_contains_file(git_buf *base, const char *file, int append_if_exists)
|
||||
int git_path_contains_dir(git_buf *base, const char *subdir)
|
||||
{
|
||||
return _check_dir_contents(base, file, append_if_exists, &git_path_isfile);
|
||||
return _check_dir_contents(base, subdir, &git_path_isdir);
|
||||
}
|
||||
|
||||
int git_path_contains_file(git_buf *base, const char *file)
|
||||
{
|
||||
return _check_dir_contents(base, file, &git_path_isfile);
|
||||
}
|
||||
|
||||
int git_path_find_dir(git_buf *dir, const char *path, const char *base)
|
||||
@ -522,3 +523,63 @@ int git_path_direach(
|
||||
closedir(dir);
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
int git_path_dirload(
|
||||
const char *path,
|
||||
size_t prefix_len,
|
||||
size_t alloc_extra,
|
||||
git_vector *contents)
|
||||
{
|
||||
int error, need_slash;
|
||||
DIR *dir;
|
||||
struct dirent de_buf, *de;
|
||||
size_t path_len;
|
||||
|
||||
assert(path != NULL && contents != NULL);
|
||||
path_len = strlen(path);
|
||||
assert(path_len > 0 && path_len >= prefix_len);
|
||||
|
||||
if ((dir = opendir(path)) == NULL)
|
||||
return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure."
|
||||
" An error occured while opening the directory", path);
|
||||
|
||||
path += prefix_len;
|
||||
path_len -= prefix_len;
|
||||
need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0;
|
||||
|
||||
while ((error = readdir_r(dir, &de_buf, &de)) == 0 && de != NULL) {
|
||||
char *entry_path;
|
||||
size_t entry_len;
|
||||
|
||||
if (is_dot_or_dotdot(de->d_name))
|
||||
continue;
|
||||
|
||||
entry_len = strlen(de->d_name);
|
||||
|
||||
entry_path = git__malloc(
|
||||
path_len + need_slash + entry_len + 1 + alloc_extra);
|
||||
if (entry_path == NULL)
|
||||
return GIT_ENOMEM;
|
||||
|
||||
if (path_len)
|
||||
memcpy(entry_path, path, path_len);
|
||||
if (need_slash)
|
||||
entry_path[path_len] = '/';
|
||||
memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len);
|
||||
entry_path[path_len + need_slash + entry_len] = '\0';
|
||||
|
||||
if ((error = git_vector_insert(contents, entry_path)) < GIT_SUCCESS) {
|
||||
git__free(entry_path);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
if (error != GIT_SUCCESS)
|
||||
return git__throw(
|
||||
GIT_EOSERR, "Failed to process directory entry in `%s`", path);
|
||||
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
38
src/path.h
38
src/path.h
@ -9,6 +9,7 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "buffer.h"
|
||||
#include "vector.h"
|
||||
|
||||
/**
|
||||
* Path manipulation utils
|
||||
@ -128,25 +129,32 @@ extern int git_path_isdir(const char *path);
|
||||
*/
|
||||
extern int git_path_isfile(const char *path);
|
||||
|
||||
/**
|
||||
* Check if the parent directory contains the item.
|
||||
*
|
||||
* @param dir Directory to check.
|
||||
* @param item Item that might be in the directory.
|
||||
* @return GIT_SUCCESS if item exists in directory, <0 otherwise.
|
||||
*/
|
||||
extern int git_path_contains(git_buf *dir, const char *item);
|
||||
|
||||
/**
|
||||
* Check if the given path contains the given subdirectory.
|
||||
*
|
||||
* @param parent Directory path that might contain subdir
|
||||
* @param subdir Subdirectory name to look for in parent
|
||||
* @param append_if_exists If true, then subdir will be appended to the parent path if it does exist
|
||||
* @return GIT_SUCCESS if subdirectory exists, < 0 otherwise.
|
||||
*/
|
||||
extern int git_path_contains_dir(git_buf *parent, const char *subdir, int append_if_exists);
|
||||
extern int git_path_contains_dir(git_buf *parent, const char *subdir);
|
||||
|
||||
/**
|
||||
* Check if the given path contains the given file.
|
||||
*
|
||||
* @param dir Directory path that might contain file
|
||||
* @param file File name to look for in parent
|
||||
* @param append_if_exists If true, then file will be appended to the path if it does exist
|
||||
* @return GIT_SUCCESS if file exists, < 0 otherwise.
|
||||
*/
|
||||
extern int git_path_contains_file(git_buf *dir, const char *file, int append_if_exists);
|
||||
extern int git_path_contains_file(git_buf *dir, const char *file);
|
||||
|
||||
/**
|
||||
* Clean up path, prepending base if it is not already rooted.
|
||||
@ -216,4 +224,26 @@ extern int git_path_walk_up(
|
||||
int (*fn)(void *state, git_buf *),
|
||||
void *state);
|
||||
|
||||
/**
|
||||
* Load all directory entries (except '.' and '..') into a vector.
|
||||
*
|
||||
* For cases where `git_path_direach()` is not appropriate, this
|
||||
* allows you to load the filenames in a directory into a vector
|
||||
* of strings. That vector can then be sorted, iterated, or whatever.
|
||||
* Remember to free alloc of the allocated strings when you are done.
|
||||
*
|
||||
* @param path The directory to read from.
|
||||
* @param prefix_len When inserting entries, the trailing part of path
|
||||
* will be prefixed after this length. I.e. given path "/a/b" and
|
||||
* prefix_len 3, the entries will look like "b/e1", "b/e2", etc.
|
||||
* @param alloc_extra Extra bytes to add to each string allocation in
|
||||
* case you want to append anything funny.
|
||||
* @param contents Vector to fill with directory entry names.
|
||||
*/
|
||||
extern int git_path_dirload(
|
||||
const char *path,
|
||||
size_t prefix_len,
|
||||
size_t alloc_extra,
|
||||
git_vector *contents);
|
||||
|
||||
#endif
|
||||
|
@ -81,14 +81,14 @@ void git_repository_free(git_repository *repo)
|
||||
static int quickcheck_repository_dir(git_buf *repository_path)
|
||||
{
|
||||
/* Check OBJECTS_DIR first, since it will generate the longest path name */
|
||||
if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR, 0) < 0)
|
||||
if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) < 0)
|
||||
return GIT_ERROR;
|
||||
|
||||
/* Ensure HEAD file exists */
|
||||
if (git_path_contains_file(repository_path, GIT_HEAD_FILE, 0) < 0)
|
||||
if (git_path_contains_file(repository_path, GIT_HEAD_FILE) < 0)
|
||||
return GIT_ERROR;
|
||||
|
||||
if (git_path_contains_dir(repository_path, GIT_REFS_DIR, 0) < 0)
|
||||
if (git_path_contains_dir(repository_path, GIT_REFS_DIR) < 0)
|
||||
return GIT_ERROR;
|
||||
|
||||
return GIT_SUCCESS;
|
||||
@ -166,8 +166,8 @@ int git_repository_open(git_repository **repo_out, const char *path)
|
||||
* of the working dir, by testing if it contains a `.git`
|
||||
* folder inside of it.
|
||||
*/
|
||||
git_path_contains_dir(&path_buf, GIT_DIR, 1); /* append on success */
|
||||
/* ignore error, since it just means `path/.git` doesn't exist */
|
||||
if (git_path_contains_dir(&path_buf, GIT_DIR) == GIT_SUCCESS)
|
||||
git_buf_joinpath(&path_buf, path_buf.ptr, GIT_DIR);
|
||||
|
||||
if (quickcheck_repository_dir(&path_buf) < GIT_SUCCESS) {
|
||||
error = git__throw(GIT_ENOTAREPO,
|
||||
|
@ -25,7 +25,6 @@ static int resize_vector(git_vector *v)
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
void git_vector_free(git_vector *v)
|
||||
{
|
||||
assert(v);
|
||||
@ -188,6 +187,12 @@ int git_vector_remove(git_vector *v, unsigned int idx)
|
||||
return GIT_SUCCESS;
|
||||
}
|
||||
|
||||
void git_vector_pop(git_vector *v)
|
||||
{
|
||||
if (v->length > 0)
|
||||
v->length--;
|
||||
}
|
||||
|
||||
void git_vector_uniq(git_vector *v)
|
||||
{
|
||||
git_vector_cmp cmp;
|
||||
|
@ -38,6 +38,11 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position)
|
||||
return (position < v->length) ? v->contents[position] : NULL;
|
||||
}
|
||||
|
||||
GIT_INLINE(void *) git_vector_last(git_vector *v)
|
||||
{
|
||||
return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL;
|
||||
}
|
||||
|
||||
#define git_vector_foreach(v, iter, elem) \
|
||||
for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ )
|
||||
|
||||
@ -48,6 +53,7 @@ int git_vector_insert(git_vector *v, void *element);
|
||||
int git_vector_insert_sorted(git_vector *v, void *element,
|
||||
int (*on_dup)(void **old, void *new));
|
||||
int git_vector_remove(git_vector *v, unsigned int idx);
|
||||
void git_vector_pop(git_vector *v);
|
||||
void git_vector_uniq(git_vector *v);
|
||||
|
||||
#endif
|
||||
|
22
tests-clar/diff/diff_helpers.c
Normal file
22
tests-clar/diff/diff_helpers.c
Normal file
@ -0,0 +1,22 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "diff_helpers.h"
|
||||
|
||||
git_tree *resolve_commit_oid_to_tree(
|
||||
git_repository *repo,
|
||||
const char *partial_oid)
|
||||
{
|
||||
size_t len = strlen(partial_oid);
|
||||
git_oid oid;
|
||||
git_object *obj;
|
||||
git_tree *tree;
|
||||
|
||||
if (git_oid_fromstrn(&oid, partial_oid, len) == 0)
|
||||
git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY);
|
||||
cl_assert(obj);
|
||||
if (git_object_type(obj) == GIT_OBJ_TREE)
|
||||
return (git_tree *)obj;
|
||||
cl_assert(git_object_type(obj) == GIT_OBJ_COMMIT);
|
||||
cl_git_pass(git_commit_tree(&tree, (git_commit *)obj));
|
||||
git_object_free(obj);
|
||||
return tree;
|
||||
}
|
4
tests-clar/diff/diff_helpers.h
Normal file
4
tests-clar/diff/diff_helpers.h
Normal file
@ -0,0 +1,4 @@
|
||||
#include "fileops.h"
|
||||
|
||||
extern git_tree *resolve_commit_oid_to_tree(
|
||||
git_repository *repo, const char *partial_oid);
|
364
tests-clar/diff/iterator.c
Normal file
364
tests-clar/diff/iterator.c
Normal file
@ -0,0 +1,364 @@
|
||||
#include "clar_libgit2.h"
|
||||
#include "diff_helpers.h"
|
||||
#include "iterator.h"
|
||||
|
||||
static git_repository *g_repo = NULL;
|
||||
static const char *g_sandbox = NULL;
|
||||
|
||||
static void setup_sandbox(const char *sandbox)
|
||||
{
|
||||
cl_fixture_sandbox(sandbox);
|
||||
g_sandbox = sandbox;
|
||||
|
||||
p_chdir(sandbox);
|
||||
cl_git_pass(p_rename(".gitted", ".git"));
|
||||
if (p_access("gitattributes", F_OK) == 0)
|
||||
cl_git_pass(p_rename("gitattributes", ".gitattributes"));
|
||||
if (p_access("gitignore", F_OK) == 0)
|
||||
cl_git_pass(p_rename("gitignore", ".gitignore"));
|
||||
p_chdir("..");
|
||||
|
||||
cl_git_pass(git_repository_open(&g_repo, sandbox));
|
||||
}
|
||||
|
||||
static void cleanup_sandbox(void)
|
||||
{
|
||||
if (g_repo) {
|
||||
git_repository_free(g_repo);
|
||||
g_repo = NULL;
|
||||
}
|
||||
if (g_sandbox) {
|
||||
cl_fixture_cleanup(g_sandbox);
|
||||
g_sandbox = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void test_diff_iterator__initialize(void)
|
||||
{
|
||||
/* since we are doing tests with different sandboxes, defer setup
|
||||
* to the actual tests. cleanup will still be done in the global
|
||||
* cleanup function so that assertion failures don't result in a
|
||||
* missed cleanup.
|
||||
*/
|
||||
}
|
||||
|
||||
void test_diff_iterator__cleanup(void)
|
||||
{
|
||||
cleanup_sandbox();
|
||||
}
|
||||
|
||||
|
||||
/* -- TREE ITERATOR TESTS -- */
|
||||
|
||||
static void tree_iterator_test(
|
||||
const char *sandbox,
|
||||
const char *treeish,
|
||||
int expected_count,
|
||||
const char **expected_values)
|
||||
{
|
||||
git_tree *t;
|
||||
git_iterator *i;
|
||||
const git_index_entry *entry;
|
||||
int count = 0;
|
||||
|
||||
setup_sandbox(sandbox);
|
||||
|
||||
cl_assert(t = resolve_commit_oid_to_tree(g_repo, treeish));
|
||||
cl_git_pass(git_iterator_for_tree(g_repo, t, &i));
|
||||
cl_git_pass(git_iterator_current(i, &entry));
|
||||
|
||||
while (entry != NULL) {
|
||||
if (expected_values != NULL)
|
||||
cl_assert_strequal(expected_values[count], entry->path);
|
||||
|
||||
count++;
|
||||
|
||||
cl_git_pass(git_iterator_advance(i, &entry));
|
||||
}
|
||||
|
||||
git_iterator_free(i);
|
||||
|
||||
cl_assert(expected_count == count);
|
||||
|
||||
git_tree_free(t);
|
||||
}
|
||||
|
||||
/* results of: git ls-tree -r --name-only 605812a */
|
||||
const char *expected_tree_0[] = {
|
||||
".gitattributes",
|
||||
"attr0",
|
||||
"attr1",
|
||||
"attr2",
|
||||
"attr3",
|
||||
"binfile",
|
||||
"macro_test",
|
||||
"root_test1",
|
||||
"root_test2",
|
||||
"root_test3",
|
||||
"root_test4.txt",
|
||||
"subdir/.gitattributes",
|
||||
"subdir/abc",
|
||||
"subdir/subdir_test1",
|
||||
"subdir/subdir_test2.txt",
|
||||
"subdir2/subdir2_test1",
|
||||
NULL
|
||||
};
|
||||
|
||||
void test_diff_iterator__tree_0(void)
|
||||
{
|
||||
tree_iterator_test("attr", "605812a", 16, expected_tree_0);
|
||||
}
|
||||
|
||||
/* results of: git ls-tree -r --name-only 6bab5c79 */
|
||||
const char *expected_tree_1[] = {
|
||||
".gitattributes",
|
||||
"attr0",
|
||||
"attr1",
|
||||
"attr2",
|
||||
"attr3",
|
||||
"root_test1",
|
||||
"root_test2",
|
||||
"root_test3",
|
||||
"root_test4.txt",
|
||||
"subdir/.gitattributes",
|
||||
"subdir/subdir_test1",
|
||||
"subdir/subdir_test2.txt",
|
||||
"subdir2/subdir2_test1",
|
||||
NULL
|
||||
};
|
||||
|
||||
void test_diff_iterator__tree_1(void)
|
||||
{
|
||||
tree_iterator_test("attr", "6bab5c79cd5", 13, expected_tree_1);
|
||||
}
|
||||
|
||||
/* results of: git ls-tree -r --name-only 26a125ee1 */
|
||||
const char *expected_tree_2[] = {
|
||||
"current_file",
|
||||
"file_deleted",
|
||||
"modified_file",
|
||||
"staged_changes",
|
||||
"staged_changes_file_deleted",
|
||||
"staged_changes_modified_file",
|
||||
"staged_delete_file_deleted",
|
||||
"staged_delete_modified_file",
|
||||
"subdir.txt",
|
||||
"subdir/current_file",
|
||||
"subdir/deleted_file",
|
||||
"subdir/modified_file",
|
||||
NULL
|
||||
};
|
||||
|
||||
void test_diff_iterator__tree_2(void)
|
||||
{
|
||||
tree_iterator_test("status", "26a125ee1", 12, expected_tree_2);
|
||||
}
|
||||
|
||||
/* $ git ls-tree -r --name-only 0017bd4ab1e */
|
||||
const char *expected_tree_3[] = {
|
||||
"current_file",
|
||||
"file_deleted",
|
||||
"modified_file",
|
||||
"staged_changes",
|
||||
"staged_changes_file_deleted",
|
||||
"staged_changes_modified_file",
|
||||
"staged_delete_file_deleted",
|
||||
"staged_delete_modified_file"
|
||||
};
|
||||
|
||||
void test_diff_iterator__tree_3(void)
|
||||
{
|
||||
tree_iterator_test("status", "0017bd4ab1e", 8, expected_tree_3);
|
||||
}
|
||||
|
||||
|
||||
/* -- INDEX ITERATOR TESTS -- */
|
||||
|
||||
static void index_iterator_test(
|
||||
const char *sandbox,
|
||||
int expected_count,
|
||||
const char **expected_names,
|
||||
const char **expected_oids)
|
||||
{
|
||||
git_iterator *i;
|
||||
const git_index_entry *entry;
|
||||
int count = 0;
|
||||
|
||||
setup_sandbox(sandbox);
|
||||
|
||||
cl_git_pass(git_iterator_for_index(g_repo, &i));
|
||||
cl_git_pass(git_iterator_current(i, &entry));
|
||||
|
||||
while (entry != NULL) {
|
||||
if (expected_names != NULL)
|
||||
cl_assert_strequal(expected_names[count], entry->path);
|
||||
|
||||
if (expected_oids != NULL) {
|
||||
git_oid oid;
|
||||
cl_git_pass(git_oid_fromstr(&oid, expected_oids[count]));
|
||||
cl_assert(git_oid_cmp(&oid, &entry->oid) == 0);
|
||||
}
|
||||
|
||||
count++;
|
||||
cl_git_pass(git_iterator_advance(i, &entry));
|
||||
}
|
||||
|
||||
git_iterator_free(i);
|
||||
|
||||
cl_assert(count == expected_count);
|
||||
}
|
||||
|
||||
static const char *expected_index_0[] = {
|
||||
"attr0",
|
||||
"attr1",
|
||||
"attr2",
|
||||
"attr3",
|
||||
"binfile",
|
||||
"gitattributes",
|
||||
"macro_bad",
|
||||
"macro_test",
|
||||
"root_test1",
|
||||
"root_test2",
|
||||
"root_test3",
|
||||
"root_test4.txt",
|
||||
"subdir/.gitattributes",
|
||||
"subdir/abc",
|
||||
"subdir/subdir_test1",
|
||||
"subdir/subdir_test2.txt",
|
||||
"subdir2/subdir2_test1",
|
||||
};
|
||||
|
||||
static const char *expected_index_oids_0[] = {
|
||||
"556f8c827b8e4a02ad5cab77dca2bcb3e226b0b3",
|
||||
"3b74db7ab381105dc0d28f8295a77f6a82989292",
|
||||
"2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2",
|
||||
"c485abe35abd4aa6fd83b076a78bbea9e2e7e06c",
|
||||
"d800886d9c86731ae5c4a62b0b77c437015e00d2",
|
||||
"2b40c5aca159b04ea8d20ffe36cdf8b09369b14a",
|
||||
"5819a185d77b03325aaf87cafc771db36f6ddca7",
|
||||
"ff69f8639ce2e6010b3f33a74160aad98b48da2b",
|
||||
"45141a79a77842c59a63229403220a4e4be74e3d",
|
||||
"45141a79a77842c59a63229403220a4e4be74e3d",
|
||||
"45141a79a77842c59a63229403220a4e4be74e3d",
|
||||
"fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
|
||||
"99eae476896f4907224978b88e5ecaa6c5bb67a9",
|
||||
"3e42ffc54a663f9401cc25843d6c0e71a33e4249",
|
||||
"e563cf4758f0d646f1b14b76016aa17fa9e549a4",
|
||||
"fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
|
||||
"dccada462d3df8ac6de596fb8c896aba9344f941"
|
||||
};
|
||||
|
||||
void test_diff_iterator__index_0(void)
|
||||
{
|
||||
index_iterator_test("attr", 17, expected_index_0, expected_index_oids_0);
|
||||
}
|
||||
|
||||
static const char *expected_index_1[] = {
|
||||
"current_file",
|
||||
"file_deleted",
|
||||
"modified_file",
|
||||
"staged_changes",
|
||||
"staged_changes_file_deleted",
|
||||
"staged_changes_modified_file",
|
||||
"staged_new_file",
|
||||
"staged_new_file_deleted_file",
|
||||
"staged_new_file_modified_file",
|
||||
"subdir.txt",
|
||||
"subdir/current_file",
|
||||
"subdir/deleted_file",
|
||||
"subdir/modified_file",
|
||||
};
|
||||
|
||||
static const char* expected_index_oids_1[] = {
|
||||
"a0de7e0ac200c489c41c59dfa910154a70264e6e",
|
||||
"5452d32f1dd538eb0405e8a83cc185f79e25e80f",
|
||||
"452e4244b5d083ddf0460acf1ecc74db9dcfa11a",
|
||||
"55d316c9ba708999f1918e9677d01dfcae69c6b9",
|
||||
"a6be623522ce87a1d862128ac42672604f7b468b",
|
||||
"906ee7711f4f4928ddcb2a5f8fbc500deba0d2a8",
|
||||
"529a16e8e762d4acb7b9636ff540a00831f9155a",
|
||||
"90b8c29d8ba39434d1c63e1b093daaa26e5bd972",
|
||||
"ed062903b8f6f3dccb2fa81117ba6590944ef9bd",
|
||||
"e8ee89e15bbe9b20137715232387b3de5b28972e",
|
||||
"53ace0d1cc1145a5f4fe4f78a186a60263190733",
|
||||
"1888c805345ba265b0ee9449b8877b6064592058",
|
||||
"a6191982709b746d5650e93c2acf34ef74e11504"
|
||||
};
|
||||
|
||||
void test_diff_iterator__index_1(void)
|
||||
{
|
||||
index_iterator_test("status", 13, expected_index_1, expected_index_oids_1);
|
||||
}
|
||||
|
||||
|
||||
/* -- WORKDIR ITERATOR TESTS -- */
|
||||
|
||||
static void workdir_iterator_test(
|
||||
const char *sandbox,
|
||||
int expected_count,
|
||||
int expected_ignores,
|
||||
const char **expected_names,
|
||||
const char *an_ignored_name)
|
||||
{
|
||||
git_iterator *i;
|
||||
const git_index_entry *entry;
|
||||
int count = 0, count_all = 0;
|
||||
|
||||
setup_sandbox(sandbox);
|
||||
|
||||
cl_git_pass(git_iterator_for_workdir(g_repo, &i));
|
||||
cl_git_pass(git_iterator_current(i, &entry));
|
||||
|
||||
while (entry != NULL) {
|
||||
int ignored = git_iterator_current_is_ignored(i);
|
||||
|
||||
if (!ignored && S_ISDIR(entry->mode)) {
|
||||
cl_git_pass(git_iterator_advance_into_directory(i, &entry));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (expected_names != NULL)
|
||||
cl_assert_strequal(expected_names[count_all], entry->path);
|
||||
|
||||
if (an_ignored_name && strcmp(an_ignored_name,entry->path)==0)
|
||||
cl_assert(ignored);
|
||||
|
||||
if (!ignored)
|
||||
count++;
|
||||
count_all++;
|
||||
|
||||
cl_git_pass(git_iterator_advance(i, &entry));
|
||||
}
|
||||
|
||||
git_iterator_free(i);
|
||||
|
||||
cl_assert(count == expected_count);
|
||||
cl_assert(count_all == expected_count + expected_ignores);
|
||||
}
|
||||
|
||||
void test_diff_iterator__workdir_0(void)
|
||||
{
|
||||
workdir_iterator_test("attr", 15, 4, NULL, "ign");
|
||||
}
|
||||
|
||||
static const char *status_paths[] = {
|
||||
"current_file",
|
||||
"ignored_file",
|
||||
"modified_file",
|
||||
"new_file",
|
||||
"staged_changes",
|
||||
"staged_changes_modified_file",
|
||||
"staged_delete_modified_file",
|
||||
"staged_new_file",
|
||||
"staged_new_file_modified_file",
|
||||
"subdir/current_file",
|
||||
"subdir/modified_file",
|
||||
"subdir/new_file",
|
||||
"subdir.txt",
|
||||
NULL
|
||||
};
|
||||
|
||||
void test_diff_iterator__workdir_1(void)
|
||||
{
|
||||
workdir_iterator_test("status", 12, 1, status_paths, "ignored_file");
|
||||
}
|
Loading…
Reference in New Issue
Block a user