Commit 9273399b by Vicent Martí

Merge pull request #610 from arrbee/status-rewrite

Rewrite status using diff
parents ae4cae4e 952f94c8
......@@ -40,7 +40,7 @@
#include "git2/net.h"
#include "git2/status.h"
#include "git2/indexer.h"
#include "git2/submodule.h"
#include "git2/notes.h"
#endif
......@@ -39,7 +39,9 @@ enum {
GIT_DIFF_IGNORE_SUBMODULES = (1 << 5),
GIT_DIFF_PATIENCE = (1 << 6),
GIT_DIFF_INCLUDE_IGNORED = (1 << 7),
GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8)
GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8),
GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9),
GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10),
};
/**
......
......@@ -47,6 +47,79 @@ GIT_BEGIN_DECL
GIT_EXTERN(int) git_status_foreach(git_repository *repo, int (*callback)(const char *, unsigned int, void *), void *payload);
/**
* Select the files on which to report status.
*
* - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the
* rough equivalent of `git status --porcelain` where each file
* will receive a callback indicating its status in the index and
* in the workdir.
* - GIT_STATUS_SHOW_INDEX_ONLY will only make callbacks for index
* side of status. The status of the index contents relative to
* the HEAD will be given.
* - GIT_STATUS_SHOW_WORKDIR_ONLY will only make callbacks for the
* workdir side of status, reporting the status of workdir content
* relative to the index.
* - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR behaves like index-only
* followed by workdir-only, causing two callbacks to be issued
* per file (first index then workdir). This is slightly more
* efficient than making separate calls. This makes it easier to
* emulate the output of a plain `git status`.
*/
typedef enum {
GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0,
GIT_STATUS_SHOW_INDEX_ONLY = 1,
GIT_STATUS_SHOW_WORKDIR_ONLY = 2,
GIT_STATUS_SHOW_INDEX_THEN_WORKDIR = 3,
} git_status_show_t;
/**
* Flags to control status callbacks
*
* - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should
* be made on untracked files. These will only be made if the
* workdir files are included in the status "show" option.
* - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should
* get callbacks. Again, these callbacks will only be made if
* the workdir files are included in the status "show" option.
* Right now, there is no option to include all files in
* directories that are ignored completely.
* - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback
* should be made even on unmodified files.
* - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories
* which appear to be submodules should just be skipped over.
* - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the
* contents of untracked directories should be included in the
* status. Normally if an entire directory is new, then just
* the top-level directory will be included (with a trailing
* slash on the entry name). Given this flag, the directory
* itself will not be included, but all the files in it will.
*/
#define GIT_STATUS_OPT_INCLUDE_UNTRACKED (1 << 0)
#define GIT_STATUS_OPT_INCLUDE_IGNORED (1 << 1)
#define GIT_STATUS_OPT_INCLUDE_UNMODIFIED (1 << 2)
#define GIT_STATUS_OPT_EXCLUDE_SUBMODULES (1 << 3)
#define GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS (1 << 4)
/**
* Options to control how callbacks will be made by
* `git_status_foreach_ext()`.
*/
typedef struct {
git_status_show_t show;
unsigned int flags;
git_strarray pathspec;
} git_status_options;
/**
* Gather file status information and run callbacks as requested.
*/
GIT_EXTERN(int) git_status_foreach_ext(
git_repository *repo,
git_status_options *opts,
int (*callback)(const char *, unsigned int, void *),
void *payload);
/**
* Get file status for a single file
*
* @param status_flags the status value
......
/*
* Copyright (C) 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_git_submodule_h__
#define INCLUDE_git_submodule_h__
#include "common.h"
#include "types.h"
#include "oid.h"
/**
* @file git2/submodule.h
* @brief Git submodule management utilities
* @defgroup git_submodule Git submodule management routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
typedef enum {
GIT_SUBMODULE_UPDATE_CHECKOUT = 0,
GIT_SUBMODULE_UPDATE_REBASE = 1,
GIT_SUBMODULE_UPDATE_MERGE = 2
} git_submodule_update_t;
typedef enum {
GIT_SUBMODULE_IGNORE_ALL = 0, /* never dirty */
GIT_SUBMODULE_IGNORE_DIRTY = 1, /* only dirty if HEAD moved */
GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */
GIT_SUBMODULE_IGNORE_NONE = 3 /* any change or untracked == dirty */
} git_submodule_ignore_t;
/**
* Description of submodule
*
* This record describes a submodule found in a repository. There
* should be an entry for every submodule found in the HEAD and for
* every submodule described in .gitmodules. The fields are as follows:
*
* - `name` is the name of the submodule from .gitmodules.
* - `path` is the path to the submodule from the repo working directory.
* It is almost always the same as `name`.
* - `url` is the url for the submodule.
* - `oid` is the HEAD SHA1 for the submodule.
* - `update` is a value from above - see gitmodules(5) update.
* - `ignore` is a value from above - see gitmodules(5) ignore.
* - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules.
* - `refcount` is for internal use.
*
* If the submodule has been added to .gitmodules but not yet git added,
* then the `oid` will be zero. If the submodule has been deleted, but
* the delete has not been committed yet, then the `oid` will be set, but
* the `url` will be NULL.
*/
typedef struct {
char *name;
char *path;
char *url;
git_oid oid; /* sha1 of submodule HEAD ref or zero if not committed */
git_submodule_update_t update;
git_submodule_ignore_t ignore;
int fetch_recurse;
int refcount;
} git_submodule;
/**
* Iterate over all submodules of a repository.
*
* @param repo The repository
* @param callback Function to be called with the name of each submodule.
* Return a non-zero value to terminate the iteration.
* @param payload Extra data to pass to callback
* @return 0 on success, -1 on error, or non-zero return value of callback
*/
GIT_EXTERN(int) git_submodule_foreach(
git_repository *repo,
int (*callback)(const char *name, void *payload),
void *payload);
/**
* Lookup submodule information by name or path.
*
* Given either the submodule name or path (they are ususally the same),
* this returns a structure describing the submodule. If the submodule
* does not exist, this will return GIT_ENOTFOUND and set the submodule
* pointer to NULL.
*
* @param submodule Pointer to submodule description object pointer..
* @param repo The repository.
* @param name The name of the submodule. Trailing slashes will be ignored.
* @return 0 on success, GIT_ENOTFOUND if submodule does not exist, -1 on error
*/
GIT_EXTERN(int) git_submodule_lookup(
git_submodule **submodule,
git_repository *repo,
const char *name);
/** @} */
GIT_END_DECL
#endif
......@@ -196,7 +196,8 @@ bool git_attr_cache__is_cached(git_repository *repo, const char *path)
const char *cache_key = path;
if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0)
cache_key += strlen(git_repository_workdir(repo));
return (git_hashtable_lookup(repo->attrcache.files, cache_key) != NULL);
return (git_hashtable_lookup(
git_repository_attr_cache(repo)->files, cache_key) != NULL);
}
int git_attr_cache__lookup_or_create_file(
......@@ -207,7 +208,7 @@ int git_attr_cache__lookup_or_create_file(
git_attr_file **file_ptr)
{
int error;
git_attr_cache *cache = &repo->attrcache;
git_attr_cache *cache = git_repository_attr_cache(repo);
git_attr_file *file = NULL;
if ((file = git_hashtable_lookup(cache->files, key)) != NULL) {
......@@ -293,7 +294,6 @@ static int collect_attr_files(
{
int error;
git_buf dir = GIT_BUF_INIT;
git_config *cfg;
const char *workdir = git_repository_workdir(repo);
attr_walk_up_info info;
......@@ -312,7 +312,8 @@ static int collect_attr_files(
* - $GIT_PREFIX/etc/gitattributes
*/
error = push_attrs(repo, files, repo->path_repository, GIT_ATTR_FILE_INREPO);
error = push_attrs(
repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO);
if (error < 0)
goto cleanup;
......@@ -322,22 +323,18 @@ static int collect_attr_files(
if (error < 0)
goto cleanup;
if (!(error = git_repository_config(&cfg, repo))) {
const char *core_attribs = NULL;
git_config_get_string(cfg, GIT_ATTR_CONFIG, &core_attribs);
git_clearerror(); /* don't care if attributesfile is not set */
if (core_attribs)
error = push_attrs(repo, files, NULL, core_attribs);
git_config_free(cfg);
if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
error = push_attrs(
repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file);
if (error < 0)
goto cleanup;
}
if (!error) {
error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
if (!error)
error = push_attrs(repo, files, NULL, dir.ptr);
else if (error == GIT_ENOTFOUND)
error = 0;
}
error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
if (!error)
error = push_attrs(repo, files, NULL, dir.ptr);
else if (error == GIT_ENOTFOUND)
error = 0;
cleanup:
if (error < 0)
......@@ -350,11 +347,26 @@ static int collect_attr_files(
int git_attr_cache__init(git_repository *repo)
{
git_attr_cache *cache = &repo->attrcache;
git_attr_cache *cache = git_repository_attr_cache(repo);
git_config *cfg;
if (cache->initialized)
return 0;
/* cache config settings for attributes and ignores */
if (git_repository_config(&cfg, repo) < 0)
return -1;
if (git_config_get_string(cfg, GIT_ATTR_CONFIG, &cache->cfg_attr_file)) {
giterr_clear();
cache->cfg_attr_file = NULL;
}
if (git_config_get_string(cfg, GIT_IGNORE_CONFIG, &cache->cfg_excl_file)) {
giterr_clear();
cache->cfg_excl_file = NULL;
}
git_config_free(cfg);
/* allocate hashtable for attribute and ignore file contents */
if (cache->files == NULL) {
cache->files = git_hashtable_alloc(
8, git_hash__strhash_cb, git_hash__strcmp_cb);
......@@ -362,6 +374,7 @@ int git_attr_cache__init(git_repository *repo)
return -1;
}
/* allocate hashtable for attribute macros */
if (cache->macros == NULL) {
cache->macros = git_hashtable_alloc(
8, git_hash__strhash_cb, git_hash__strcmp_cb);
......@@ -378,30 +391,30 @@ int git_attr_cache__init(git_repository *repo)
void git_attr_cache_flush(
git_repository *repo)
{
git_hashtable *table;
if (!repo)
return;
if (repo->attrcache.files) {
if ((table = git_repository_attr_cache(repo)->files) != NULL) {
git_attr_file *file;
GIT_HASHTABLE_FOREACH_VALUE(repo->attrcache.files, file,
git_attr_file__free(file));
GIT_HASHTABLE_FOREACH_VALUE(table, file, git_attr_file__free(file));
git_hashtable_free(table);
git_hashtable_free(repo->attrcache.files);
repo->attrcache.files = NULL;
git_repository_attr_cache(repo)->files = NULL;
}
if (repo->attrcache.macros) {
if ((table = git_repository_attr_cache(repo)->macros) != NULL) {
git_attr_rule *rule;
GIT_HASHTABLE_FOREACH_VALUE(repo->attrcache.macros, rule,
git_attr_rule__free(rule));
GIT_HASHTABLE_FOREACH_VALUE(table, rule, git_attr_rule__free(rule));
git_hashtable_free(table);
git_hashtable_free(repo->attrcache.macros);
repo->attrcache.macros = NULL;
git_repository_attr_cache(repo)->macros = NULL;
}
repo->attrcache.initialized = 0;
git_repository_attr_cache(repo)->initialized = 0;
}
int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
......@@ -411,5 +424,5 @@ int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
return 0;
return git_hashtable_insert(
repo->attrcache.macros, macro->match.pattern, macro);
git_repository_attr_cache(repo)->macros, macro->match.pattern, macro);
}
......@@ -9,10 +9,15 @@
#include "attr_file.h"
#define GIT_ATTR_CONFIG "core.attributesfile"
#define GIT_IGNORE_CONFIG "core.excludesfile"
typedef struct {
int initialized;
git_hashtable *files; /* hash path to git_attr_file of rules */
git_hashtable *macros; /* hash name to vector<git_attr_assignment> */
const char *cfg_attr_file; /* cached value of core.attributesfile */
const char *cfg_excl_file; /* cached value of core.excludesfile */
} git_attr_cache;
extern int git_attr_cache__init(git_repository *repo);
......
......@@ -495,8 +495,8 @@ int git_attr_assignment__parse(
/* expand macros (if given a repo with a macro cache) */
if (repo != NULL && assign->value == git_attr__true) {
git_attr_rule *macro =
git_hashtable_lookup(repo->attrcache.macros, assign->name);
git_attr_rule *macro = git_hashtable_lookup(
git_repository_attr_cache(repo)->macros, assign->name);
if (macro != NULL) {
unsigned int i;
......
......@@ -14,7 +14,6 @@
#define GIT_ATTR_FILE ".gitattributes"
#define GIT_ATTR_FILE_INREPO "info/attributes"
#define GIT_ATTR_FILE_SYSTEM "gitattributes"
#define GIT_ATTR_CONFIG "core.attributesfile"
#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0)
#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1)
......
......@@ -256,8 +256,8 @@ int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
{
va_list ap;
int i;
size_t total_size = 0;
char *out;
size_t total_size = 0, original_size = buf->size;
char *out, *original = buf->ptr;
if (buf->size > 0 && buf->ptr[buf->size - 1] != separator)
++total_size; /* space for initial separator */
......@@ -281,8 +281,9 @@ int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
va_end(ap);
/* expand buffer if needed */
if (total_size > 0 &&
git_buf_grow(buf, buf->size + total_size + 1) < 0)
if (total_size == 0)
return 0;
if (git_buf_grow(buf, buf->size + total_size + 1) < 0)
return -1;
out = buf->ptr + buf->size;
......@@ -300,12 +301,23 @@ int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...)
if (!segment)
continue;
/* deal with join that references buffer's original content */
if (segment >= original && segment < original + original_size) {
size_t offset = (segment - original);
segment = buf->ptr + offset;
segment_len = original_size - offset;
} else {
segment_len = strlen(segment);
}
/* skip leading separators */
if (out > buf->ptr && out[-1] == separator)
while (*segment == separator) segment++;
while (segment_len > 0 && *segment == separator) {
segment++;
segment_len--;
}
/* copy over next buffer */
segment_len = strlen(segment);
if (segment_len > 0) {
memmove(out, segment, segment_len);
out += segment_len;
......
......@@ -140,7 +140,7 @@ int git_config_add_file(git_config *cfg, git_config_file *file, int priority)
int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data)
{
int ret = GIT_SUCCESS;
int ret = 0;
unsigned int i;
file_internal *internal;
git_config_file *file;
......@@ -201,25 +201,25 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value)
return file->set(file, name, value);
}
static int parse_bool(int *out, const char *value)
int git_config_parse_bool(int *out, const char *value)
{
/* A missing value means true */
if (value == NULL) {
*out = 1;
return GIT_SUCCESS;
return 0;
}
if (!strcasecmp(value, "true") ||
!strcasecmp(value, "yes") ||
!strcasecmp(value, "on")) {
*out = 1;
return GIT_SUCCESS;
return 0;
}
if (!strcasecmp(value, "false") ||
!strcasecmp(value, "no") ||
!strcasecmp(value, "off")) {
*out = 0;
return GIT_SUCCESS;
return 0;
}
return GIT_EINVALIDTYPE;
......@@ -283,46 +283,58 @@ static int parse_int32(int32_t *out, const char *value)
/***********
* Getters
***********/
int git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out)
int git_config_lookup_map_value(
git_cvar_map *maps, size_t map_n, const char *value, int *out)
{
size_t i;
const char *value;
int error;
error = git_config_get_string(cfg, name, &value);
if (error < GIT_SUCCESS)
return error;
if (!value)
return GIT_ENOTFOUND;
for (i = 0; i < map_n; ++i) {
git_cvar_map *m = maps + i;
switch (m->cvar_type) {
case GIT_CVAR_FALSE:
case GIT_CVAR_TRUE: {
int bool_val;
case GIT_CVAR_FALSE:
case GIT_CVAR_TRUE: {
int bool_val;
if (git_config_parse_bool(&bool_val, value) == 0 &&
bool_val == (int)m->cvar_type) {
*out = m->map_value;
return 0;
}
break;
}
if (parse_bool(&bool_val, value) == 0 &&
bool_val == (int)m->cvar_type) {
*out = m->map_value;
return 0;
}
case GIT_CVAR_INT32:
if (parse_int32(out, value) == 0)
return 0;
break;
break;
case GIT_CVAR_STRING:
if (strcasecmp(value, m->str_match) == 0) {
*out = m->map_value;
return 0;
}
break;
}
}
case GIT_CVAR_INT32:
if (parse_int32(out, value) == 0)
return 0;
return GIT_ENOTFOUND;
}
break;
int git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out)
{
const char *value;
int error;
case GIT_CVAR_STRING:
if (strcasecmp(value, m->str_match) == 0) {
*out = m->map_value;
return 0;
}
}
}
error = git_config_get_string(cfg, name, &value);
if (error < 0)
return error;
if (!git_config_lookup_map_value(maps, map_n, value, out))
return 0;
giterr_set(GITERR_CONFIG,
"Failed to map the '%s' config variable with a valid value", name);
......@@ -372,7 +384,7 @@ int git_config_get_bool(git_config *cfg, const char *name, int *out)
if (ret < 0)
return ret;
if (parse_bool(out, value) == 0)
if (git_config_parse_bool(out, value) == 0)
return 0;
if (parse_int32(out, value) == 0) {
......@@ -449,7 +461,7 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex
internal = git_vector_get(&cfg->files, i - 1);
file = internal->file;
ret = file->set_multivar(file, name, regexp, value);
if (ret < GIT_SUCCESS && ret != GIT_ENOTFOUND)
if (ret < 0 && ret != GIT_ENOTFOUND)
return ret;
}
......
......@@ -25,4 +25,9 @@ struct git_config {
extern int git_config_find_global_r(git_buf *global_config_path);
extern int git_config_find_system_r(git_buf *system_config_path);
extern int git_config_parse_bool(int *out, const char *bool_string);
extern int git_config_lookup_map_value(
git_cvar_map *maps, size_t map_n, const char *value, int *out);
#endif
......@@ -192,6 +192,9 @@ static int file_foreach(git_config_file *backend, int (*fn)(const char *, const
cvar_t *var;
const char *key;
if (!b->values)
return 0;
GIT_HASHTABLE_FOREACH(b->values, key, var,
do {
if (fn(key, var->value, data) < 0)
......
/*
* Copyright (C) 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_config_file_h__
#define INCLUDE_config_file_h__
#include "git2/config.h"
GIT_INLINE(int) git_config_file_open(git_config_file *cfg)
{
return cfg->open(cfg);
}
GIT_INLINE(void) git_config_file_free(git_config_file *cfg)
{
cfg->free(cfg);
}
GIT_INLINE(int) git_config_file_foreach(
git_config_file *cfg,
int (*fn)(const char *key, const char *value, void *data),
void *data)
{
return cfg->foreach(cfg, fn, data);
}
#endif
......@@ -8,6 +8,7 @@
#include "git2/diff.h"
#include "diff.h"
#include "fileops.h"
#include "config.h"
static void diff_delta__free(git_diff_delta *delta)
{
......@@ -132,7 +133,17 @@ static int diff_delta__from_one(
git_delta_t status,
const git_index_entry *entry)
{
git_diff_delta *delta = diff_delta__alloc(diff, status, entry->path);
git_diff_delta *delta;
if (status == GIT_DELTA_IGNORED &&
(diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
return 0;
if (status == GIT_DELTA_UNTRACKED &&
(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
GITERR_CHECK_ALLOC(delta);
/* This fn is just for single-sided diffs */
......@@ -168,6 +179,10 @@ static int diff_delta__from_two(
{
git_diff_delta *delta;
if (status == GIT_DELTA_UNMODIFIED &&
(diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
return 0;
if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
const git_index_entry *temp = old;
old = new;
......@@ -219,15 +234,38 @@ static int diff_delta__cmp(const void *a, const void *b)
return val ? val : ((int)da->status - (int)db->status);
}
static int config_bool(git_config *cfg, const char *name, int defvalue)
{
int val = defvalue;
if (git_config_get_bool(cfg, name, &val) < 0)
giterr_clear();
return val;
}
static git_diff_list *git_diff_list_alloc(
git_repository *repo, const git_diff_options *opts)
{
git_config *cfg;
git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
if (diff == NULL)
return NULL;
diff->repo = repo;
/* load config values that affect diff behavior */
if (git_repository_config(&cfg, repo) < 0)
goto fail;
if (config_bool(cfg, "core.symlinks", 1))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
if (config_bool(cfg, "core.ignorestat", 0))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
if (config_bool(cfg, "core.filemode", 1))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT;
if (config_bool(cfg, "core.trustctime", 1))
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
git_config_free(cfg);
if (opts == NULL)
return diff;
......@@ -238,10 +276,8 @@ static git_diff_list *git_diff_list_alloc(
diff->opts.dst_prefix = diff_strdup_prefix(
opts->dst_prefix ? opts->dst_prefix : DIFF_DST_PREFIX_DEFAULT);
if (!diff->opts.src_prefix || !diff->opts.dst_prefix) {
git__free(diff);
return NULL;
}
if (!diff->opts.src_prefix || !diff->opts.dst_prefix)
goto fail;
if (diff->opts.flags & GIT_DIFF_REVERSE) {
char *swap = diff->opts.src_prefix;
......@@ -249,16 +285,19 @@ static git_diff_list *git_diff_list_alloc(
diff->opts.dst_prefix = swap;
}
if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0) {
git__free(diff->opts.src_prefix);
git__free(diff->opts.dst_prefix);
git__free(diff);
return NULL;
}
if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0)
goto fail;
/* TODO: do something safe with the pathspec strarray */
return diff;
fail:
git_vector_free(&diff->deltas);
git__free(diff->opts.src_prefix);
git__free(diff->opts.dst_prefix);
git__free(diff);
return NULL;
}
void git_diff_list_free(git_diff_list *diff)
......@@ -312,6 +351,8 @@ static int oid_for_workdir_item(
return result;
}
#define EXEC_BIT_MASK 0000111
static int maybe_modified(
git_iterator *old,
const git_index_entry *oitem,
......@@ -320,53 +361,92 @@ static int maybe_modified(
git_diff_list *diff)
{
git_oid noid, *use_noid = NULL;
git_delta_t status = GIT_DELTA_MODIFIED;
unsigned int omode = oitem->mode;
unsigned int nmode = nitem->mode;
GIT_UNUSED(old);
/* support "assume unchanged" & "skip worktree" bits */
if ((oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) != 0 ||
(oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
return 0;
/* on platforms with no symlinks, promote plain files to symlinks */
if (S_ISLNK(omode) && S_ISREG(nmode) &&
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);
/* on platforms with no execmode, clear exec bit from comparisons */
if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) {
omode = omode & ~EXEC_BIT_MASK;
nmode = nmode & ~EXEC_BIT_MASK;
}
/* support "assume unchanged" (badly, b/c we still stat everything) */
if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
if (GIT_MODE_TYPE(oitem->mode) != GIT_MODE_TYPE(nitem->mode)) {
/* support "skip worktree" index bit */
else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
status = GIT_DELTA_UNMODIFIED;
/* if basic type of file changed, then split into delete and add */
else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
return -1;
return 0;
}
if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
oitem->mode == nitem->mode)
return 0;
/* if oids and modes match, then file is unmodified */
else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
omode == nmode)
status = GIT_DELTA_UNMODIFIED;
/* if we have a workdir item with an unknown oid, check deeper */
else if (git_oid_iszero(&nitem->oid) && new->type == GIT_ITERATOR_WORKDIR) {
/* TODO: add check against index file st_mtime to avoid racy-git */
if (git_oid_iszero(&nitem->oid) && new->type == GIT_ITERATOR_WORKDIR) {
/* if they files look exactly alike, then we'll assume the same */
if (oitem->file_size == nitem->file_size &&
oitem->ctime.seconds == nitem->ctime.seconds &&
(!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
(oitem->ctime.seconds == nitem->ctime.seconds)) &&
oitem->mtime.seconds == nitem->mtime.seconds &&
oitem->dev == nitem->dev &&
(!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) ||
(oitem->dev == nitem->dev)) &&
oitem->ino == nitem->ino &&
oitem->uid == nitem->uid &&
oitem->gid == nitem->gid)
return 0;
status = GIT_DELTA_UNMODIFIED;
else if (S_ISGITLINK(nmode)) {
git_submodule *sub;
if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0)
status = GIT_DELTA_UNMODIFIED;
else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)
return -1;
else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL)
status = GIT_DELTA_UNMODIFIED;
else {
/* TODO: support other GIT_SUBMODULE_IGNORE values */
status = GIT_DELTA_UNMODIFIED;
}
}
/* TODO: check git attributes so we will not have to read the file
* in if it is marked binary.
*/
if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
return -1;
if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
oitem->mode == nitem->mode)
return 0;
else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
omode == nmode)
status = GIT_DELTA_UNMODIFIED;
/* store calculated oid so we don't have to recalc later */
use_noid = &noid;
}
return diff_delta__from_two(
diff, GIT_DELTA_MODIFIED, oitem, nitem, use_noid);
return diff_delta__from_two(diff, status, oitem, nitem, use_noid);
}
static int diff_from_iterators(
......@@ -418,7 +498,12 @@ static int diff_from_iterators(
is_ignored = git_iterator_current_is_ignored(new);
if (S_ISDIR(nitem->mode)) {
if (git__prefixcmp(oitem->path, nitem->path) == 0) {
/* recurse into directory if explicitly requested or
* if there are tracked items inside the directory
*/
if ((diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) ||
(oitem && git__prefixcmp(oitem->path, nitem->path) == 0))
{
if (is_ignored)
ignore_prefix = nitem->path;
if (git_iterator_advance_into_directory(new, &nitem) < 0)
......@@ -538,23 +623,27 @@ int git_diff_merge(
const git_diff_list *from)
{
int error = 0;
unsigned int i = 0, j = 0;
git_vector onto_new;
git_diff_delta *delta;
unsigned int i, j;
assert(onto && from);
if (!from->deltas.length)
return 0;
if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0)
return -1;
while (!error && (i < onto->deltas.length || j < from->deltas.length)) {
git_diff_delta *o = git_vector_get(&onto->deltas, i);
const git_diff_delta *f = git_vector_get_const(&from->deltas, j);
const char *opath = !o ? NULL : o->old.path ? o->old.path : o->new.path;
const char *fpath = !f ? NULL : f->old.path ? f->old.path : f->new.path;
for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
int cmp = !f ? -1 : !o ? 1 : strcmp(o->old.path, f->old.path);
if (opath && (!fpath || strcmp(opath, fpath) < 0)) {
if (cmp < 0) {
delta = diff_delta__dup(o);
i++;
} else if (fpath && (!opath || strcmp(opath, fpath) > 0)) {
} else if (cmp > 0) {
delta = diff_delta__dup(f);
j++;
} else {
......@@ -563,10 +652,11 @@ int git_diff_merge(
j++;
}
error = !delta ? -1 : git_vector_insert(&onto_new, delta);
if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
break;
}
if (error == 0) {
if (!error) {
git_vector_swap(&onto->deltas, &onto_new);
onto->new_src = from->new_src;
}
......@@ -577,3 +667,4 @@ int git_diff_merge(
return error;
}
......@@ -13,12 +13,21 @@
#include "iterator.h"
#include "repository.h"
enum {
GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */
GIT_DIFFCAPS_TRUST_EXEC_BIT = (1 << 2), /* use st_mode exec bit? */
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
struct git_diff_list {
git_repository *repo;
git_diff_options opts;
git_vector deltas; /* vector of git_diff_file_delta */
git_iterator_type_t old_src;
git_iterator_type_t new_src;
uint32_t diffcaps;
};
#endif
......
......@@ -314,7 +314,8 @@ int git_diff_foreach(
git_blob *old_blob = NULL, *new_blob = NULL;
git_map old_data, new_data;
if (delta->status == GIT_DELTA_UNMODIFIED)
if (delta->status == GIT_DELTA_UNMODIFIED &&
(diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
continue;
if (delta->status == GIT_DELTA_IGNORED &&
......@@ -377,7 +378,8 @@ int git_diff_foreach(
*/
if (git_oid_cmp(&delta->old.oid, &delta->new.oid) == 0) {
delta->status = GIT_DELTA_UNMODIFIED;
goto cleanup;
if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
goto cleanup;
}
}
}
......
#include "ignore.h"
#include "path.h"
#include "git2/config.h"
#define GIT_IGNORE_INTERNAL "[internal]exclude"
#define GIT_IGNORE_FILE_INREPO "info/exclude"
#define GIT_IGNORE_FILE ".gitignore"
#define GIT_IGNORE_CONFIG "core.excludesfile"
static int load_ignore_file(
git_repository *repo, const char *path, git_attr_file *ignores)
......@@ -73,7 +71,6 @@ static int push_one_ignore(void *ref, git_buf *path)
int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ignores)
{
int error = 0;
git_config *cfg;
const char *workdir = git_repository_workdir(repo);
assert(ignores);
......@@ -104,26 +101,19 @@ int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ig
/* load .git/info/exclude */
error = push_ignore(repo, &ignores->ign_global,
repo->path_repository, GIT_IGNORE_FILE_INREPO);
git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
if (error < 0)
goto cleanup;
/* load core.excludesfile */
if ((error = git_repository_config(&cfg, repo)) == 0) {
const char *core_ignore;
error = git_config_get_string(cfg, GIT_IGNORE_CONFIG, &core_ignore);
if (error == 0 && core_ignore != NULL)
error = push_ignore(repo, &ignores->ign_global, NULL, core_ignore);
else {
error = 0;
giterr_clear(); /* don't care if attributesfile is not set */
}
git_config_free(cfg);
}
if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
error = push_ignore(repo, &ignores->ign_global, NULL,
git_repository_attr_cache(repo)->cfg_excl_file);
cleanup:
if (error < 0)
git_ignore__free(ignores);
return error;
}
......
......@@ -9,6 +9,7 @@
#include "tree.h"
#include "ignore.h"
#include "buffer.h"
#include "git2/submodule.h"
typedef struct tree_iterator_frame tree_iterator_frame;
struct tree_iterator_frame {
......@@ -121,7 +122,6 @@ static int tree_iterator__advance(
break;
tree_iterator__pop_frame(ti);
git_buf_rtruncate_at_char(&ti->path, '/');
}
if (te && entry_is_tree(te))
......@@ -425,9 +425,25 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
return 0; /* if error, ignore it and ignore file */
/* detect submodules */
if (S_ISDIR(wi->entry.mode) &&
git_path_contains(&wi->path, DOT_GIT) == true)
wi->entry.mode = S_IFGITLINK;
if (S_ISDIR(wi->entry.mode)) {
bool is_submodule = git_path_contains(&wi->path, DOT_GIT);
/* if there is no .git, still check submodules data */
if (!is_submodule) {
int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path);
is_submodule = (res == 0);
if (res == GIT_ENOTFOUND)
giterr_clear();
}
/* if submodule, mark as GITLINK and remove trailing slash */
if (is_submodule) {
size_t len = strlen(wi->entry.path);
assert(wi->entry.path[len - 1] == '/');
wi->entry.path[len - 1] = '\0';
wi->entry.mode = S_IFGITLINK;
}
}
return 0;
}
......@@ -485,7 +501,9 @@ int git_iterator_advance_into_directory(
workdir_iterator *wi = (workdir_iterator *)iter;
if (iter->type == GIT_ITERATOR_WORKDIR &&
wi->entry.path && S_ISDIR(wi->entry.mode))
wi->entry.path &&
S_ISDIR(wi->entry.mode) &&
!S_ISGITLINK(wi->entry.mode))
{
if (workdir_iterator__expand_dir(wi) < 0)
/* if error loading or if empty, skip the directory. */
......
......@@ -64,6 +64,7 @@ void git_repository_free(git_repository *repo)
git_cache_free(&repo->objects);
git_repository__refcache_free(&repo->references);
git_attr_cache_flush(repo);
git_submodule_config_free(repo);
git__free(repo->path_repository);
git__free(repo->workdir);
......
......@@ -83,6 +83,7 @@ struct git_repository {
git_cache objects;
git_refcache references;
git_attr_cache attrcache;
git_hashtable *submodules;
char *path_repository;
char *workdir;
......@@ -100,6 +101,11 @@ void git_object__free(void *object);
int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);
void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid);
GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo)
{
return &repo->attrcache;
}
/*
* Weak pointers to repository internals.
*
......@@ -120,4 +126,9 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo);
int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar);
void git_repository__cvar_cache_clear(git_repository *repo);
/*
* Submodule cache
*/
extern void git_submodule_config_free(git_repository *repo);
#endif
......@@ -15,6 +15,207 @@
#include "repository.h"
#include "ignore.h"
#include "git2/diff.h"
#include "diff.h"
static int resolve_head_to_tree(git_tree **tree, git_repository *repo)
{
git_reference *head = NULL;
git_object *obj = NULL;
if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0)
return -1;
if (git_reference_oid(head) == NULL) {
git_reference *resolved;
if (git_reference_resolve(&resolved, head) < 0) {
/* cannot resolve HEAD - probably brand new repo */
giterr_clear();
git_reference_free(head);
return GIT_ENOTFOUND;
}
git_reference_free(head);
head = resolved;
}
if (git_object_lookup(&obj, repo, git_reference_oid(head), GIT_OBJ_ANY) < 0)
goto fail;
switch (git_object_type(obj)) {
case GIT_OBJ_TREE:
*tree = (git_tree *)obj;
break;
case GIT_OBJ_COMMIT:
if (git_commit_tree(tree, (git_commit *)obj) < 0)
goto fail;
git_object_free(obj);
break;
default:
goto fail;
}
return 0;
fail:
git_object_free(obj);
git_reference_free(head);
return -1;
}
static unsigned int index_delta2status(git_delta_t index_status)
{
unsigned int st = GIT_STATUS_CURRENT;
switch (index_status) {
case GIT_DELTA_ADDED:
case GIT_DELTA_COPIED:
case GIT_DELTA_RENAMED:
st = GIT_STATUS_INDEX_NEW;
break;
case GIT_DELTA_DELETED:
st = GIT_STATUS_INDEX_DELETED;
break;
case GIT_DELTA_MODIFIED:
st = GIT_STATUS_INDEX_MODIFIED;
break;
default:
break;
}
return st;
}
static unsigned int workdir_delta2status(git_delta_t workdir_status)
{
unsigned int st = GIT_STATUS_CURRENT;
switch (workdir_status) {
case GIT_DELTA_ADDED:
case GIT_DELTA_COPIED:
case GIT_DELTA_RENAMED:
case GIT_DELTA_UNTRACKED:
st = GIT_STATUS_WT_NEW;
break;
case GIT_DELTA_DELETED:
st = GIT_STATUS_WT_DELETED;
break;
case GIT_DELTA_MODIFIED:
st = GIT_STATUS_WT_MODIFIED;
break;
case GIT_DELTA_IGNORED:
st = GIT_STATUS_IGNORED;
break;
default:
break;
}
return st;
}
int git_status_foreach_ext(
git_repository *repo,
git_status_options *opts,
int (*cb)(const char *, unsigned int, void *),
void *cbdata)
{
int err = 0, cmp;
git_diff_options diffopt;
git_diff_list *idx2head = NULL, *wd2idx = NULL;
git_tree *head = NULL;
git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
git_diff_delta *i2h, *w2i;
unsigned int i, j, i_max, j_max;
assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
switch (resolve_head_to_tree(&head, repo)) {
case 0: break;
case GIT_ENOTFOUND: return 0;
default: return -1;
}
memset(&diffopt, 0, sizeof(diffopt));
memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED;
if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
/* TODO: support EXCLUDE_SUBMODULES flag */
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
(err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0)
goto cleanup;
if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
(err = git_diff_workdir_to_index(repo, &diffopt, &wd2idx)) < 0)
goto cleanup;
if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
for (i = 0; !err && i < idx2head->deltas.length; i++) {
i2h = GIT_VECTOR_GET(&idx2head->deltas, i);
err = cb(i2h->old.path, index_delta2status(i2h->status), cbdata);
}
git_diff_list_free(idx2head);
idx2head = NULL;
}
i_max = idx2head ? idx2head->deltas.length : 0;
j_max = wd2idx ? wd2idx->deltas.length : 0;
for (i = 0, j = 0; !err && (i < i_max || j < j_max); ) {
i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;
cmp = !w2i ? -1 : !i2h ? 1 : strcmp(i2h->old.path, w2i->old.path);
if (cmp < 0) {
err = cb(i2h->old.path, index_delta2status(i2h->status), cbdata);
i++;
} else if (cmp > 0) {
err = cb(w2i->old.path, workdir_delta2status(w2i->status), cbdata);
j++;
} else {
err = cb(i2h->old.path, index_delta2status(i2h->status) |
workdir_delta2status(w2i->status), cbdata);
i++; j++;
}
}
cleanup:
git_tree_free(head);
git_diff_list_free(idx2head);
git_diff_list_free(wd2idx);
return err;
}
int git_status_foreach(
git_repository *repo,
int (*callback)(const char *, unsigned int, void *),
void *payload)
{
git_status_options opts;
opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED |
GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
return git_status_foreach_ext(repo, &opts, callback, payload);
}
/*
* the old stuff
*/
struct status_entry {
git_index_time mtime;
......@@ -461,7 +662,7 @@ static int status_cmp(const void *a, const void *b)
#define DEFAULT_SIZE 16
int git_status_foreach(
int git_status_foreach_old(
git_repository *repo,
int (*callback)(const char *, unsigned int, void *),
void *payload)
......
/*
* Copyright (C) 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 "common.h"
#include "git2/config.h"
#include "git2/types.h"
#include "git2/repository.h"
#include "git2/index.h"
#include "git2/submodule.h"
#include "buffer.h"
#include "hashtable.h"
#include "vector.h"
#include "posix.h"
#include "config_file.h"
#include "config.h"
#include "repository.h"
static git_cvar_map _sm_update_map[] = {
{GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},
{GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},
{GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}
};
static git_cvar_map _sm_ignore_map[] = {
{GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},
{GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},
{GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},
{GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}
};
static uint32_t strhash_no_trailing_slash(const void *key, int hash_id)
{
static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
0x01010101,
0x12345678,
0xFEDCBA98
};
size_t key_len = key ? strlen((const char *)key) : 0;
if (key_len > 0 && ((const char *)key)[key_len - 1] == '/')
key_len--;
return git__hash(key, (int)key_len, hash_seeds[hash_id]);
}
static int strcmp_no_trailing_slash(const void *a, const void *b)
{
const char *astr = (const char *)a;
const char *bstr = (const char *)b;
size_t alen = a ? strlen(astr) : 0;
size_t blen = b ? strlen(bstr) : 0;
int cmp;
if (alen > 0 && astr[alen - 1] == '/')
alen--;
if (blen > 0 && bstr[blen - 1] == '/')
blen--;
cmp = strncmp(astr, bstr, min(alen, blen));
if (cmp == 0)
cmp = (alen < blen) ? -1 : (alen > blen) ? 1 : 0;
return cmp;
}
static git_submodule *submodule_alloc(const char *name)
{
git_submodule *sm = git__calloc(1, sizeof(git_submodule));
if (sm == NULL)
return sm;
sm->path = sm->name = git__strdup(name);
if (!sm->name) {
git__free(sm);
return NULL;
}
return sm;
}
static void submodule_release(git_submodule *sm, int decr)
{
if (!sm)
return;
sm->refcount -= decr;
if (sm->refcount == 0) {
if (sm->name != sm->path)
git__free(sm->path);
git__free(sm->name);
git__free(sm->url);
git__free(sm);
}
}
static int submodule_from_entry(
git_hashtable *smcfg, git_index_entry *entry)
{
git_submodule *sm;
void *old_sm;
sm = git_hashtable_lookup(smcfg, entry->path);
if (!sm)
sm = submodule_alloc(entry->path);
git_oid_cpy(&sm->oid, &entry->oid);
if (strcmp(sm->path, entry->path) != 0) {
if (sm->path != sm->name) {
git__free(sm->path);
sm->path = sm->name;
}
sm->path = git__strdup(entry->path);
if (!sm->path)
goto fail;
}
if (git_hashtable_insert2(smcfg, sm->path, sm, &old_sm) < 0)
goto fail;
sm->refcount++;
if (old_sm && ((git_submodule *)old_sm) != sm) {
/* TODO: log warning about multiple entrys for same submodule path */
submodule_release(old_sm, 1);
}
return 0;
fail:
submodule_release(sm, 0);
return -1;
}
static int submodule_from_config(
const char *key, const char *value, void *data)
{
git_hashtable *smcfg = data;
const char *namestart;
const char *property;
git_buf name = GIT_BUF_INIT;
git_submodule *sm;
void *old_sm = NULL;
bool is_path;
if (git__prefixcmp(key, "submodule.") != 0)
return 0;
namestart = key + strlen("submodule.");
property = strrchr(namestart, '.');
if (property == NULL)
return 0;
property++;
is_path = (strcmp(property, "path") == 0);
if (git_buf_set(&name, namestart, property - namestart - 1) < 0)
return -1;
sm = git_hashtable_lookup(smcfg, name.ptr);
if (!sm && is_path)
sm = git_hashtable_lookup(smcfg, value);
if (!sm)
sm = submodule_alloc(name.ptr);
if (!sm)
goto fail;
if (strcmp(sm->name, name.ptr) != 0) {
assert(sm->path == sm->name);
sm->name = git_buf_detach(&name);
if (git_hashtable_insert2(smcfg, sm->name, sm, &old_sm) < 0)
goto fail;
sm->refcount++;
}
else if (is_path && strcmp(sm->path, value) != 0) {
assert(sm->path == sm->name);
if ((sm->path = git__strdup(value)) == NULL ||
git_hashtable_insert2(smcfg, sm->path, sm, &old_sm) < 0)
goto fail;
sm->refcount++;
}
if (old_sm && ((git_submodule *)old_sm) != sm) {
/* TODO: log entry about multiple submodules with same path */
submodule_release(old_sm, 1);
}
if (is_path)
return 0;
/* copy other properties into submodule entry */
if (strcmp(property, "url") == 0) {
if (sm->url) {
git__free(sm->url);
sm->url = NULL;
}
if ((sm->url = git__strdup(value)) == NULL)
goto fail;
}
else if (strcmp(property, "update") == 0) {
int val;
if (git_config_lookup_map_value(
_sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) {
giterr_set(GITERR_INVALID,
"Invalid value for submodule update property: '%s'", value);
goto fail;
}
sm->update = (git_submodule_update_t)val;
}
else if (strcmp(property, "fetchRecurseSubmodules") == 0) {
if (git_config_parse_bool(&sm->fetch_recurse, value) < 0)
goto fail;
}
else if (strcmp(property, "ignore") == 0) {
int val;
if (git_config_lookup_map_value(
_sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) {
giterr_set(GITERR_INVALID,
"Invalid value for submodule ignore property: '%s'", value);
goto fail;
}
sm->ignore = (git_submodule_ignore_t)val;
}
/* ignore other unknown submodule properties */
return 0;
fail:
submodule_release(sm, 0);
git_buf_free(&name);
return -1;
}
static int load_submodule_config(git_repository *repo)
{
int error;
git_index *index;
unsigned int i, max_i;
git_oid gitmodules_oid;
git_hashtable *smcfg;
struct git_config_file *mods = NULL;
if (repo->submodules)
return 0;
/* submodule data is kept in a hashtable with each submodule stored
* under both its name and its path. These are usually the same, but
* that is not guaranteed.
*/
smcfg = git_hashtable_alloc(
4, strhash_no_trailing_slash, strcmp_no_trailing_slash);
GITERR_CHECK_ALLOC(smcfg);
/* scan index for gitmodules (and .gitmodules entry) */
if ((error = git_repository_index(&index, repo)) < 0)
goto cleanup;
memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));
max_i = git_index_entrycount(index);
for (i = 0; i < max_i; i++) {
git_index_entry *entry = git_index_get(index, i);
if (S_ISGITLINK(entry->mode)) {
if ((error = submodule_from_entry(smcfg, entry)) < 0)
goto cleanup;
}
else if (strcmp(entry->path, ".gitmodules") == 0)
git_oid_cpy(&gitmodules_oid, &entry->oid);
}
/* load .gitmodules from workdir if it exists */
if (git_repository_workdir(repo) != NULL) {
/* look in workdir for .gitmodules */
git_buf path = GIT_BUF_INIT;
if (!git_buf_joinpath(
&path, git_repository_workdir(repo), ".gitmodules") &&
git_path_isfile(path.ptr))
{
if (!(error = git_config_file__ondisk(&mods, path.ptr)))
error = git_config_file_open(mods);
}
git_buf_free(&path);
}
/* load .gitmodules from object cache if not in workdir */
if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) {
/* TODO: is it worth loading gitmodules from object cache? */
}
/* process .gitmodules info */
if (!error && mods != NULL)
error = git_config_file_foreach(mods, submodule_from_config, smcfg);
/* store submodule config in repo */
if (!error)
repo->submodules = smcfg;
cleanup:
if (mods != NULL)
git_config_file_free(mods);
if (error)
git_hashtable_free(smcfg);
return error;
}
void git_submodule_config_free(git_repository *repo)
{
git_hashtable *smcfg = repo->submodules;
git_submodule *sm;
repo->submodules = NULL;
if (smcfg == NULL)
return;
GIT_HASHTABLE_FOREACH_VALUE(smcfg, sm, { submodule_release(sm,1); });
git_hashtable_free(smcfg);
}
static int submodule_cmp(const void *a, const void *b)
{
return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);
}
int git_submodule_foreach(
git_repository *repo,
int (*callback)(const char *name, void *payload),
void *payload)
{
int error;
git_submodule *sm;
git_vector seen = GIT_VECTOR_INIT;
seen._cmp = submodule_cmp;
if ((error = load_submodule_config(repo)) < 0)
return error;
GIT_HASHTABLE_FOREACH_VALUE(
repo->submodules, sm, {
/* usually the following will not come into play */
if (sm->refcount > 1) {
if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND)
continue;
if ((error = git_vector_insert(&seen, sm)) < 0)
break;
}
if ((error = callback(sm->name, payload)) < 0)
break;
});
git_vector_free(&seen);
return error;
}
int git_submodule_lookup(
git_submodule **sm_ptr, /* NULL allowed if user only wants to test */
git_repository *repo,
const char *name) /* trailing slash is allowed */
{
git_submodule *sm;
if (load_submodule_config(repo) < 0)
return -1;
sm = git_hashtable_lookup(repo->submodules, name);
if (sm_ptr)
*sm_ptr = sm;
return sm ? 0 : GIT_ENOTFOUND;
}
......@@ -32,7 +32,7 @@ struct git_treebuilder {
GIT_INLINE(unsigned int) entry_is_tree(const struct git_tree_entry *e)
{
return e->attr & 040000;
return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr));
}
void git_tree__free(git_tree *tree);
......
......@@ -44,6 +44,8 @@ GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int
return (position < v->length) ? v->contents[position] : NULL;
}
#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL)
GIT_INLINE(void *) git_vector_last(git_vector *v)
{
return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL;
......
......@@ -139,6 +139,40 @@ void test_diff_iterator__tree_3(void)
tree_iterator_test("status", "0017bd4ab1e", 8, expected_tree_3);
}
/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */
const char *expected_tree_4[] = {
"attr0",
"attr1",
"attr2",
"attr3",
"binfile",
"gitattributes",
"macro_bad",
"macro_test",
"root_test1",
"root_test2",
"root_test3",
"root_test4.txt",
"sub/abc",
"sub/file",
"sub/sub/file",
"sub/sub/subsub.txt",
"sub/subdir_test1",
"sub/subdir_test2.txt",
"subdir/.gitattributes",
"subdir/abc",
"subdir/subdir_test1",
"subdir/subdir_test2.txt",
"subdir2/subdir2_test1",
NULL
};
void test_diff_iterator__tree_4(void)
{
tree_iterator_test(
"attr", "24fa9a9fc4e202313e24b648087495441dab432b",
23, expected_tree_4);
}
/* -- INDEX ITERATOR TESTS -- */
......@@ -188,6 +222,12 @@ static const char *expected_index_0[] = {
"root_test2",
"root_test3",
"root_test4.txt",
"sub/abc",
"sub/file",
"sub/sub/file",
"sub/sub/subsub.txt",
"sub/subdir_test1",
"sub/subdir_test2.txt",
"subdir/.gitattributes",
"subdir/abc",
"subdir/subdir_test1",
......@@ -208,6 +248,12 @@ static const char *expected_index_oids_0[] = {
"4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d",
"108bb4e7fd7b16490dc33ff7d972151e73d7166e",
"fe773770c5a6cc7185580c9204b1ff18a33ff3fc",
"3e42ffc54a663f9401cc25843d6c0e71a33e4249",
"45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
"45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
"9e5bdc47d6a80f2be0ea3049ad74231b94609242",
"e563cf4758f0d646f1b14b76016aa17fa9e549a4",
"fb5067b1aef3ac1ada4b379dbcb7d17255df7d78",
"99eae476896f4907224978b88e5ecaa6c5bb67a9",
"3e42ffc54a663f9401cc25843d6c0e71a33e4249",
"e563cf4758f0d646f1b14b76016aa17fa9e549a4",
......@@ -217,7 +263,7 @@ static const char *expected_index_oids_0[] = {
void test_diff_iterator__index_0(void)
{
index_iterator_test("attr", 17, expected_index_0, expected_index_oids_0);
index_iterator_test("attr", 23, expected_index_0, expected_index_oids_0);
}
static const char *expected_index_1[] = {
......
......@@ -8,6 +8,8 @@ struct status_entry_counts {
int expected_entry_count;
};
/* entries for a plain copy of tests/resources/status */
static const char *entry_paths0[] = {
"file_deleted",
"ignored_file",
......@@ -48,3 +50,153 @@ static const unsigned int entry_statuses0[] = {
static const size_t entry_count0 = 15;
/* entries for a copy of tests/resources/status with all content
* deleted from the working directory
*/
static const char *entry_paths2[] = {
"current_file",
"file_deleted",
"modified_file",
"staged_changes",
"staged_changes_file_deleted",
"staged_changes_modified_file",
"staged_delete_file_deleted",
"staged_delete_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 unsigned int entry_statuses2[] = {
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_INDEX_DELETED,
GIT_STATUS_INDEX_DELETED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
};
static const size_t entry_count2 = 15;
/* entries for a copy of tests/resources/status with some mods */
static const char *entry_paths3[] = {
".HEADER",
"42-is-not-prime.sigh",
"README.md",
"current_file",
"current_file/",
"file_deleted",
"ignored_file",
"modified_file",
"new_file",
"staged_changes",
"staged_changes_file_deleted",
"staged_changes_modified_file",
"staged_delete_file_deleted",
"staged_delete_modified_file",
"staged_new_file",
"staged_new_file_deleted_file",
"staged_new_file_modified_file",
"subdir",
"subdir/current_file",
"subdir/deleted_file",
"subdir/modified_file",
};
static const unsigned int entry_statuses3[] = {
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_IGNORED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_INDEX_DELETED,
GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED,
GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
};
static const size_t entry_count3 = 21;
/* entries for a copy of tests/resources/status with some mods
* and different options to the status call
*/
static const char *entry_paths4[] = {
".new_file",
"current_file",
"current_file/current_file",
"current_file/modified_file",
"current_file/new_file",
"file_deleted",
"modified_file",
"new_file",
"staged_changes",
"staged_changes_file_deleted",
"staged_changes_modified_file",
"staged_delete_file_deleted",
"staged_delete_modified_file",
"staged_new_file",
"staged_new_file_deleted_file",
"staged_new_file_modified_file",
"subdir",
"subdir/current_file",
"subdir/deleted_file",
"subdir/modified_file",
"zzz_new_dir/new_file",
"zzz_new_file"
};
static const unsigned int entry_statuses4[] = {
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW,
GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED,
GIT_STATUS_INDEX_DELETED,
GIT_STATUS_WT_NEW | GIT_STATUS_INDEX_DELETED,
GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_DELETED,
GIT_STATUS_WT_NEW,
GIT_STATUS_WT_NEW,
};
static const size_t entry_count4 = 22;
#include "clar_libgit2.h"
#include "buffer.h"
#include "path.h"
#include "posix.h"
static git_repository *g_repo = NULL;
void test_status_submodules__initialize(void)
{
git_buf modpath = GIT_BUF_INIT;
g_repo = cl_git_sandbox_init("submodules");
cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_buf_sets(&modpath, git_repository_workdir(g_repo)));
cl_assert(git_path_dirname_r(&modpath, modpath.ptr) >= 0);
cl_git_pass(git_buf_joinpath(&modpath, modpath.ptr, "testrepo.git\n"));
p_rename("submodules/gitmodules", "submodules/.gitmodules");
cl_git_append2file("submodules/.gitmodules", modpath.ptr);
p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git");
}
void test_status_submodules__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_status_submodules__api(void)
{
git_submodule *sm;
cl_assert(git_submodule_lookup(NULL, g_repo, "nonexistent") == GIT_ENOTFOUND);
cl_assert(git_submodule_lookup(NULL, g_repo, "modified") == GIT_ENOTFOUND);
cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo"));
cl_assert(sm != NULL);
cl_assert_equal_s("testrepo", sm->name);
cl_assert_equal_s("testrepo", sm->path);
}
static int
cb_status__submodule_count(const char *p, unsigned int s, void *payload)
{
volatile int *count = (int *)payload;
GIT_UNUSED(p);
GIT_UNUSED(s);
(*count)++;
return 0;
}
void test_status_submodules__0(void)
{
int counts = 0;
cl_assert(git_path_isdir("submodules/.git"));
cl_assert(git_path_isdir("submodules/testrepo/.git"));
cl_assert(git_path_isfile("submodules/.gitmodules"));
cl_git_pass(
git_status_foreach(g_repo, cb_status__submodule_count, &counts)
);
cl_assert(counts == 6);
}
static const char *expected_files[] = {
".gitmodules",
"added",
"deleted",
"ignored",
"modified",
"untracked"
};
static unsigned int expected_status[] = {
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_INDEX_NEW,
GIT_STATUS_INDEX_DELETED,
GIT_STATUS_IGNORED,
GIT_STATUS_WT_MODIFIED,
GIT_STATUS_WT_NEW
};
static int
cb_status__match(const char *p, unsigned int s, void *payload)
{
volatile int *index = (int *)payload;
cl_assert_equal_s(expected_files[*index], p);
cl_assert(expected_status[*index] == s);
(*index)++;
return 0;
}
void test_status_submodules__1(void)
{
int index = 0;
cl_assert(git_path_isdir("submodules/.git"));
cl_assert(git_path_isdir("submodules/testrepo/.git"));
cl_assert(git_path_isfile("submodules/.gitmodules"));
cl_git_pass(
git_status_foreach(g_repo, cb_status__match, &index)
);
cl_assert(index == 6);
}
......@@ -2,7 +2,9 @@
#include "fileops.h"
#include "ignore.h"
#include "status_data.h"
#include "posix.h"
#include "util.h"
#include "path.h"
/**
* Auxiliary methods
......@@ -43,7 +45,6 @@ cb_status__count(const char *p, unsigned int s, void *payload)
return 0;
}
/**
* Initializer
*
......@@ -68,6 +69,7 @@ void test_status_worktree__cleanup(void)
/**
* Tests - Status determination on a working tree
*/
/* this test is equivalent to t18-status.c:statuscb0 */
void test_status_worktree__whole_repository(void)
{
struct status_entry_counts counts;
......@@ -87,6 +89,7 @@ void test_status_worktree__whole_repository(void)
cl_assert(counts.wrong_sorted_path == 0);
}
/* this test is equivalent to t18-status.c:statuscb1 */
void test_status_worktree__empty_repository(void)
{
int count = 0;
......@@ -97,6 +100,121 @@ void test_status_worktree__empty_repository(void)
cl_assert(count == 0);
}
static int remove_file_cb(void *data, git_buf *file)
{
const char *filename = git_buf_cstr(file);
GIT_UNUSED(data);
if (git__suffixcmp(filename, ".git") == 0)
return 0;
if (git_path_isdir(filename))
cl_git_pass(git_futils_rmdir_r(filename, 1));
else
cl_git_pass(p_unlink(git_buf_cstr(file)));
return 0;
}
/* this test is equivalent to t18-status.c:statuscb2 */
void test_status_worktree__purged_worktree(void)
{
struct status_entry_counts counts;
git_repository *repo = cl_git_sandbox_init("status");
git_buf workdir = GIT_BUF_INIT;
/* first purge the contents of the worktree */
cl_git_pass(git_buf_sets(&workdir, git_repository_workdir(repo)));
cl_git_pass(git_path_direach(&workdir, remove_file_cb, NULL));
/* now get status */
memset(&counts, 0x0, sizeof(struct status_entry_counts));
counts.expected_entry_count = entry_count2;
counts.expected_paths = entry_paths2;
counts.expected_statuses = entry_statuses2;
cl_git_pass(
git_status_foreach(repo, cb_status__normal, &counts)
);
cl_assert(counts.entry_count == counts.expected_entry_count);
cl_assert(counts.wrong_status_flags_count == 0);
cl_assert(counts.wrong_sorted_path == 0);
}
/* this test is similar to t18-status.c:statuscb3 */
void test_status_worktree__swap_subdir_and_file(void)
{
struct status_entry_counts counts;
git_repository *repo = cl_git_sandbox_init("status");
git_status_options opts;
/* first alter the contents of the worktree */
cl_git_pass(p_rename("status/current_file", "status/swap"));
cl_git_pass(p_rename("status/subdir", "status/current_file"));
cl_git_pass(p_rename("status/swap", "status/subdir"));
cl_git_mkfile("status/.HEADER", "dummy");
cl_git_mkfile("status/42-is-not-prime.sigh", "dummy");
cl_git_mkfile("status/README.md", "dummy");
/* now get status */
memset(&counts, 0x0, sizeof(struct status_entry_counts));
counts.expected_entry_count = entry_count3;
counts.expected_paths = entry_paths3;
counts.expected_statuses = entry_statuses3;
memset(&opts, 0, sizeof(opts));
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_INCLUDE_IGNORED;
cl_git_pass(
git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
);
cl_assert(counts.entry_count == counts.expected_entry_count);
cl_assert(counts.wrong_status_flags_count == 0);
cl_assert(counts.wrong_sorted_path == 0);
}
void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void)
{
struct status_entry_counts counts;
git_repository *repo = cl_git_sandbox_init("status");
git_status_options opts;
/* first alter the contents of the worktree */
cl_git_pass(p_rename("status/current_file", "status/swap"));
cl_git_pass(p_rename("status/subdir", "status/current_file"));
cl_git_pass(p_rename("status/swap", "status/subdir"));
cl_git_mkfile("status/.new_file", "dummy");
cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", NULL, 0777));
cl_git_mkfile("status/zzz_new_dir/new_file", "dummy");
cl_git_mkfile("status/zzz_new_file", "dummy");
/* now get status */
memset(&counts, 0x0, sizeof(struct status_entry_counts));
counts.expected_entry_count = entry_count4;
counts.expected_paths = entry_paths4;
counts.expected_statuses = entry_statuses4;
memset(&opts, 0, sizeof(opts));
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
/* TODO: set pathspec to "current_file" eventually */
cl_git_pass(
git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
);
cl_assert(counts.entry_count == counts.expected_entry_count);
cl_assert(counts.wrong_status_flags_count == 0);
cl_assert(counts.wrong_sorted_path == 0);
}
/* this test is equivalent to t18-status.c:singlestatus0 */
void test_status_worktree__single_file(void)
{
int i;
......@@ -111,6 +229,54 @@ void test_status_worktree__single_file(void)
}
}
/* this test is equivalent to t18-status.c:singlestatus1 */
void test_status_worktree__single_nonexistent_file(void)
{
int error;
unsigned int status_flags;
git_repository *repo = cl_git_sandbox_init("status");
error = git_status_file(&status_flags, repo, "nonexistent");
cl_git_fail(error);
cl_assert(error == GIT_ENOTFOUND);
}
/* this test is equivalent to t18-status.c:singlestatus2 */
void test_status_worktree__single_nonexistent_file_empty_repo(void)
{
int error;
unsigned int status_flags;
git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
error = git_status_file(&status_flags, repo, "nonexistent");
cl_git_fail(error);
cl_assert(error == GIT_ENOTFOUND);
}
/* this test is equivalent to t18-status.c:singlestatus3 */
void test_status_worktree__single_file_empty_repo(void)
{
unsigned int status_flags;
git_repository *repo = cl_git_sandbox_init("empty_standard_repo");
cl_git_mkfile("empty_standard_repo/new_file", "new_file\n");
cl_git_pass(git_status_file(&status_flags, repo, "new_file"));
cl_assert(status_flags == GIT_STATUS_WT_NEW);
}
/* this test is equivalent to t18-status.c:singlestatus4 */
void test_status_worktree__single_folder(void)
{
int error;
unsigned int status_flags;
git_repository *repo = cl_git_sandbox_init("status");
error = git_status_file(&status_flags, repo, "subdir");
cl_git_fail(error);
}
void test_status_worktree__ignores(void)
{
int i, ignored;
......@@ -133,3 +299,87 @@ void test_status_worktree__ignores(void)
);
cl_assert(ignored);
}
static int cb_status__check_592(const char *p, unsigned int s, void *payload)
{
GIT_UNUSED(payload);
if (s != GIT_STATUS_WT_DELETED || (payload != NULL && strcmp(p, (const char *)payload) != 0))
return -1;
return 0;
}
void test_status_worktree__issue_592(void)
{
git_repository *repo;
git_buf path = GIT_BUF_INIT;
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "l.txt"));
cl_git_pass(p_unlink(git_buf_cstr(&path)));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, "l.txt"));
git_buf_free(&path);
}
void test_status_worktree__issue_592_2(void)
{
git_repository *repo;
git_buf path = GIT_BUF_INIT;
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c/a.txt"));
cl_git_pass(p_unlink(git_buf_cstr(&path)));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
git_buf_free(&path);
}
void test_status_worktree__issue_592_3(void)
{
git_repository *repo;
git_buf path = GIT_BUF_INIT;
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c"));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), 1));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
git_buf_free(&path);
}
void test_status_worktree__issue_592_4(void)
{
git_repository *repo;
git_buf path = GIT_BUF_INIT;
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t/b.txt"));
cl_git_pass(p_unlink(git_buf_cstr(&path)));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, "t/b.txt"));
git_buf_free(&path);
}
void test_status_worktree__issue_592_5(void)
{
git_repository *repo;
git_buf path = GIT_BUF_INIT;
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t"));
cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), 1));
cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));
git_buf_free(&path);
}
......@@ -4,3 +4,5 @@
a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Russell Belfer <arrbee@arrbee.com> 1327611749 -0800 commit: Updating files so we can do diffs
370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a f5b0af1fb4f5c0cd7aad880711d368a07333c307 Russell Belfer <arrbee@arrbee.com> 1327621027 -0800 commit: Updating test data
f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer <arrbee@arrbee.com> 1328653313 -0800 commit: Some whitespace only changes for testing purposes
a97cc019851d401a4f1d091cb91a15890a0dd1ba 217878ab49e1314388ea2e32dc6fdb58a1b969e0 Russell Belfer <arrbee@arrbee.com> 1332734901 -0700 commit: added files in sub/sub
217878ab49e1314388ea2e32dc6fdb58a1b969e0 24fa9a9fc4e202313e24b648087495441dab432b Russell Belfer <arrbee@arrbee.com> 1332735555 -0700 commit: adding more files in sub for tree status
......@@ -4,3 +4,5 @@
a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a Russell Belfer <arrbee@arrbee.com> 1327611749 -0800 commit: Updating files so we can do diffs
370fe9ec224ce33e71f9e5ec2bd1142ce9937a6a f5b0af1fb4f5c0cd7aad880711d368a07333c307 Russell Belfer <arrbee@arrbee.com> 1327621027 -0800 commit: Updating test data
f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer <arrbee@arrbee.com> 1328653313 -0800 commit: Some whitespace only changes for testing purposes
a97cc019851d401a4f1d091cb91a15890a0dd1ba 217878ab49e1314388ea2e32dc6fdb58a1b969e0 Russell Belfer <arrbee@arrbee.com> 1332734901 -0700 commit: added files in sub/sub
217878ab49e1314388ea2e32dc6fdb58a1b969e0 24fa9a9fc4e202313e24b648087495441dab432b Russell Belfer <arrbee@arrbee.com> 1332735555 -0700 commit: adding more files in sub for tree status
a97cc019851d401a4f1d091cb91a15890a0dd1ba
24fa9a9fc4e202313e24b648087495441dab432b
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
hideDotFiles = dotGitOnly
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 4d383e87f0371ba8fa353f3912db6862b2625e85 nulltoken <emeric.fermas@gmail.com> 1331989635 +0100 commit (initial): Initial commit
4d383e87f0371ba8fa353f3912db6862b2625e85 e38fcc7a6060f5eb5b876e836b52ae4769363f21 nulltoken <emeric.fermas@gmail.com> 1332227062 +0100 commit (amend): Initial commit
0000000000000000000000000000000000000000 4d383e87f0371ba8fa353f3912db6862b2625e85 nulltoken <emeric.fermas@gmail.com> 1331989635 +0100 commit (initial): Initial commit
4d383e87f0371ba8fa353f3912db6862b2625e85 e38fcc7a6060f5eb5b876e836b52ae4769363f21 nulltoken <emeric.fermas@gmail.com> 1332227062 +0100 commit (amend): Initial commit
e38fcc7a6060f5eb5b876e836b52ae4769363f21
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
Unnamed repository; edit this file 'description' to name the repository.
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
ignored
09176a980273d801a3e37cc45c84af1366501ed9 refs/heads/master
0000000000000000000000000000000000000000 09176a980273d801a3e37cc45c84af1366501ed9 Russell Belfer <arrbee@arrbee.com> 1332365253 -0700 commit (initial): initial commit
09176a980273d801a3e37cc45c84af1366501ed9 97896810b3210244a62a82458b8e0819ecfc6850 Russell Belfer <arrbee@arrbee.com> 1332780781 -0700 commit: Setting up gitmodules
0000000000000000000000000000000000000000 09176a980273d801a3e37cc45c84af1366501ed9 Russell Belfer <arrbee@arrbee.com> 1332365253 -0700 commit (initial): initial commit
09176a980273d801a3e37cc45c84af1366501ed9 97896810b3210244a62a82458b8e0819ecfc6850 Russell Belfer <arrbee@arrbee.com> 1332780781 -0700 commit: Setting up gitmodules
P pack-b69d04bb39ac274669e2184e45bd90015d02ef5b.pack
# pack-refs with: peeled
09176a980273d801a3e37cc45c84af1366501ed9 refs/heads/master
97896810b3210244a62a82458b8e0819ecfc6850
[submodule "testrepo"]
path = testrepo
url =
\ No newline at end of file
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = /Users/rb/src/libgit2/tests/resources/testrepo.git
[branch "master"]
remote = origin
merge = refs/heads/master
Unnamed repository; edit this file 'description' to name the repository.
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Russell Belfer <arrbee@arrbee.com> 1332366307 -0700 clone: from /Users/rb/src/libgit2/tests/resources/testrepo.git
0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Russell Belfer <arrbee@arrbee.com> 1332366307 -0700 clone: from /Users/rb/src/libgit2/tests/resources/testrepo.git
# pack-refs with: peeled
b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/test
^e90810b8df3e80c413d903f631643c716887138d
1385f264afb75a56a5bec74243be9b367ba4ca08 refs/tags/point_to_blob
7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b
^e90810b8df3e80c413d903f631643c716887138d
e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/test
763d71aadf09a7951596c9746c024e7eece7c7af refs/remotes/origin/subtrees
4a202b346bb0fb0db7eff3cffeb3c70babbd2045 refs/remotes/origin/packed-test
41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 refs/remotes/origin/packed
a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/remotes/origin/master
a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/br2
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