Commit b6c93aef by Russell Belfer

Uniform iterators for trees, index, and workdir

This create a new git_iterator type of object that provides a
uniform interface for iterating over the index, an arbitrary
tree, or the working directory of a repository.

As part of this, git ignore support was extended to support
push and pop of directory-based ignore files as the working
directory is being traversed (so the array of ignores does
not have to be recreated at each directory during traveral).

There are a number of other small utility functions in buffer,
path, vector, and fileops that are included in this patch
that made the iterator implementation cleaner.
parent 9c94a356
......@@ -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,13 +85,18 @@ 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
* style mapping over some form of malloc()+read(), as the
......
......@@ -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, NULL);
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);
*ignored = 0;
/* first process builtins */
error = ignore_lookup_in_rules(
&ignores->ign_internal->rules, &path, ignored);
if (error == GIT_SUCCESS)
return error;
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;
}
}
/* 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;
}
found:
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;
return GIT_SUCCESS;
}
......@@ -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
This diff is collapsed. Click to expand it.
/*
* 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 *);
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)
{
return iter->advance(iter);
}
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 an ignored workdir directory.
*
* When a workdir iterator encounters a directory that is ignored, it will
* just return a current entry for the directory with is_ignored returning
* true. If you are iterating over the index or a tree in parallel and a
* file in the ignored directory has been added to the index/tree already,
* then it may be necessary to iterate into the directory even though it is
* ignored. Call this function to do that.
*
* Note that if the tracked file in the ignored directory has been deleted,
* this may end up acting like a full "advance" call and advance past the
* directory completely. You must handle that case.
*/
extern int git_iterator_advance_into_ignored_directory(git_iterator *iter);
#endif
......@@ -421,6 +421,11 @@ static int _check_dir_contents(
return error;
}
int git_path_contains(git_buf *dir, const char *item)
{
return _check_dir_contents(dir, item, 0, &git_path_exists);
}
int git_path_contains_dir(git_buf *base, const char *subdir, int append_if_exists)
{
return _check_dir_contents(base, subdir, append_if_exists, &git_path_isdir);
......@@ -522,3 +527,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;
}
......@@ -9,6 +9,7 @@
#include "common.h"
#include "buffer.h"
#include "vector.h"
/**
* Path manipulation utils
......@@ -129,6 +130,15 @@ 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
......@@ -216,4 +226,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
......@@ -25,6 +25,23 @@ static int resize_vector(git_vector *v)
return GIT_SUCCESS;
}
int git_vector_alloc(
git_vector **vptr, unsigned int initial_size, git_vector_cmp cmp)
{
int error;
git_vector *v = git__malloc(sizeof(git_vector));
if (!v) {
*vptr = NULL;
return GIT_ENOMEM;
}
if ((error = git_vector_init(v, initial_size, cmp)) < GIT_SUCCESS) {
git__free(v);
v = NULL;
}
*vptr = v;
return error;
}
void git_vector_free(git_vector *v)
{
......@@ -188,6 +205,21 @@ int git_vector_remove(git_vector *v, unsigned int idx)
return GIT_SUCCESS;
}
int git_vector_pop(git_vector *v, void **element)
{
assert(v);
if (v->length == 0)
return git__throw(GIT_ENOTFOUND, "Can't remove element from empty list");
if (element != NULL)
*element = v->contents[v->length - 1];
v->length--;
return GIT_SUCCESS;
}
void git_vector_uniq(git_vector *v)
{
git_vector_cmp cmp;
......
......@@ -22,6 +22,7 @@ typedef struct git_vector {
#define GIT_VECTOR_INIT {0}
int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp);
int git_vector_alloc(git_vector **v, unsigned int initial_size, git_vector_cmp cmp);
void git_vector_free(git_vector *v);
void git_vector_clear(git_vector *v);
......@@ -38,6 +39,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 +54,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);
int git_vector_pop(git_vector *v, void **element);
void git_vector_uniq(git_vector *v);
#endif
#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;
}
#include "fileops.h"
extern git_tree *resolve_commit_oid_to_tree(
git_repository *repo, const char *partial_oid);
#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));
cl_git_pass(git_iterator_current(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));
cl_git_pass(git_iterator_current(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 (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));
cl_git_pass(git_iterator_current(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");
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment