Commit 36d72a51 by Vicent Martí

Merge pull request #570 from arrbee/uniform-iterators

Uniform iterators for trees, index, and workdir
parents 8d36b253 0534641d
......@@ -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);
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:
/* 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
/*
* 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;
}
/*
* 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
......@@ -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(dir, item, &git_path_exists);
}
int git_path_contains_dir(git_buf *base, const char *subdir)
{
return _check_dir_contents(base, subdir, append_if_exists, &git_path_isdir);
return _check_dir_contents(base, subdir, &git_path_isdir);
}
int git_path_contains_file(git_buf *base, const char *file, int append_if_exists)
int git_path_contains_file(git_buf *base, const char *file)
{
return _check_dir_contents(base, file, append_if_exists, &git_path_isfile);
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;
}
......@@ -9,6 +9,7 @@
#include "common.h"
#include "buffer.h"
#include "vector.h"
/**
* Path manipulation utils
......@@ -129,24 +130,31 @@ 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
#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, &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");
}
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