summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/attr.c75
-rw-r--r--src/attr.h5
-rw-r--r--src/attr_file.c4
-rw-r--r--src/attr_file.h1
-rw-r--r--src/buffer.c24
-rw-r--r--src/config.c78
-rw-r--r--src/config.h5
-rw-r--r--src/config_file.c4
-rw-r--r--src/config_file.h31
-rw-r--r--src/diff.c171
-rw-r--r--src/diff.h9
-rw-r--r--src/diff_output.c6
-rw-r--r--src/fileops.c13
-rw-r--r--src/ignore.c20
-rw-r--r--src/iterator.c28
-rw-r--r--src/odb_loose.c2
-rw-r--r--src/repository.c1
-rw-r--r--src/repository.h11
-rw-r--r--src/status.c205
-rw-r--r--src/submodule.c376
-rw-r--r--src/tree.h2
-rw-r--r--src/vector.h2
22 files changed, 934 insertions, 139 deletions
diff --git a/src/attr.c b/src/attr.c
index 2c5add34f..5cf96acf7 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -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);
}
diff --git a/src/attr.h b/src/attr.h
index eccda0ed7..350c0ebad 100644
--- a/src/attr.h
+++ b/src/attr.h
@@ -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);
diff --git a/src/attr_file.c b/src/attr_file.c
index 646bd044c..6568313e5 100644
--- a/src/attr_file.c
+++ b/src/attr_file.c
@@ -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;
diff --git a/src/attr_file.h b/src/attr_file.h
index 6284c5386..53e479ad9 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -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)
diff --git a/src/buffer.c b/src/buffer.c
index ec0302b9a..c23803564 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -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;
diff --git a/src/config.c b/src/config.c
index 77598d6a6..250bfa652 100644
--- a/src/config.c
+++ b/src/config.c
@@ -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;
}
diff --git a/src/config.h b/src/config.h
index 59d1d9a26..82e98ce51 100644
--- a/src/config.h
+++ b/src/config.h
@@ -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
diff --git a/src/config_file.c b/src/config_file.c
index 077e2c03f..e16606512 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -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)
@@ -331,6 +334,7 @@ static int config_get_multivar(
var = var->next;
} while (var != NULL);
+ regfree(&regex);
} else {
/* no regex; go through all the variables */
do {
diff --git a/src/config_file.h b/src/config_file.h
new file mode 100644
index 000000000..0080b5713
--- /dev/null
+++ b/src/config_file.h
@@ -0,0 +1,31 @@
+/*
+ * 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
+
diff --git a/src/diff.c b/src/diff.c
index 69c944c63..54e8dd166 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -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;
}
+
diff --git a/src/diff.h b/src/diff.h
index 7d69199ea..b4a375586 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -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
diff --git a/src/diff_output.c b/src/diff_output.c
index 638cabca5..f4c214314 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -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;
}
}
}
diff --git a/src/fileops.c b/src/fileops.c
index f1f820ab7..b3bb3890e 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -62,7 +62,18 @@ int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode
int git_futils_creat_locked(const char *path, const mode_t mode)
{
- int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
+ int fd;
+
+#ifdef GIT_WIN32
+ wchar_t* buf;
+
+ buf = gitwin_to_utf16(path);
+ fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
+ git__free(buf);
+#else
+ fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);
+#endif
+
if (fd < 0) {
giterr_set(GITERR_OS, "Failed to create locked file '%s'", path);
return -1;
diff --git a/src/ignore.c b/src/ignore.c
index be00efd1b..1827eda82 100644
--- a/src/ignore.c
+++ b/src/ignore.c
@@ -1,11 +1,9 @@
#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;
}
diff --git a/src/iterator.c b/src/iterator.c
index cc15b5f67..3a3be1755 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -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. */
diff --git a/src/odb_loose.c b/src/odb_loose.c
index 085df428a..b593d1846 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -772,7 +772,7 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_
static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type)
{
- int error, header_len;
+ int error = 0, header_len;
git_buf final_path = GIT_BUF_INIT;
char header[64];
git_filebuf fbuf = GIT_FILEBUF_INIT;
diff --git a/src/repository.c b/src/repository.c
index 45bedcbe0..4e0f9d491 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -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);
diff --git a/src/repository.h b/src/repository.h
index b5dcc1340..178f29742 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -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
diff --git a/src/status.c b/src/status.c
index 2221db3d9..7cd914f21 100644
--- a/src/status.c
+++ b/src/status.c
@@ -15,6 +15,209 @@
#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;
+
+ git_reference_free(head);
+
+ 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 +664,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)
diff --git a/src/submodule.c b/src/submodule.c
new file mode 100644
index 000000000..907e43e88
--- /dev/null
+++ b/src/submodule.c
@@ -0,0 +1,376 @@
+/*
+ * 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++;
+ }
+ git_buf_free(&name);
+
+ 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__weakptr(&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;
+}
diff --git a/src/tree.h b/src/tree.h
index 0bff41312..fd00afde5 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -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);
diff --git a/src/vector.h b/src/vector.h
index 180edbf7c..5bc27914a 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -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;