summaryrefslogtreecommitdiff
path: root/read-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'read-cache.c')
-rw-r--r--read-cache.c1171
1 files changed, 914 insertions, 257 deletions
diff --git a/read-cache.c b/read-cache.c
index db27766055..8d04d78a58 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -5,11 +5,15 @@
*/
#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
+#include "config.h"
+#include "diff.h"
+#include "diffcore.h"
#include "tempfile.h"
#include "lockfile.h"
#include "cache-tree.h"
#include "refs.h"
#include "dir.h"
+#include "object-store.h"
#include "tree.h"
#include "commit.h"
#include "blob.h"
@@ -18,9 +22,7 @@
#include "varint.h"
#include "split-index.h"
#include "utf8.h"
-
-static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
- unsigned int options);
+#include "fsmonitor.h"
/* Mask for the name length in ce_flags in the on-disk index */
@@ -40,11 +42,54 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
#define CACHE_EXT_LINK 0x6c696e6b /* "link" */
#define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */
+#define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \
- SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED)
+ SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED | FSMONITOR_CHANGED)
+
+
+/*
+ * This is an estimate of the pathname length in the index. We use
+ * this for V4 index files to guess the un-deltafied size of the index
+ * in memory because of pathname deltafication. This is not required
+ * for V2/V3 index formats because their pathnames are not compressed.
+ * If the initial amount of memory set aside is not sufficient, the
+ * mem pool will allocate extra memory.
+ */
+#define CACHE_ENTRY_PATH_LENGTH 80
+
+static inline struct cache_entry *mem_pool__ce_alloc(struct mem_pool *mem_pool, size_t len)
+{
+ struct cache_entry *ce;
+ ce = mem_pool_alloc(mem_pool, cache_entry_size(len));
+ ce->mem_pool_allocated = 1;
+ return ce;
+}
+
+static inline struct cache_entry *mem_pool__ce_calloc(struct mem_pool *mem_pool, size_t len)
+{
+ struct cache_entry * ce;
+ ce = mem_pool_calloc(mem_pool, 1, cache_entry_size(len));
+ ce->mem_pool_allocated = 1;
+ return ce;
+}
+
+static struct mem_pool *find_mem_pool(struct index_state *istate)
+{
+ struct mem_pool **pool_ptr;
+
+ if (istate->split_index && istate->split_index->base)
+ pool_ptr = &istate->split_index->base->ce_mem_pool;
+ else
+ pool_ptr = &istate->ce_mem_pool;
+
+ if (!*pool_ptr)
+ mem_pool_init(pool_ptr, 0);
+
+ return *pool_ptr;
+}
struct index_state the_index;
static const char *alternate_index_output;
@@ -61,28 +106,30 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
replace_index_entry_in_base(istate, old, ce);
remove_name_hash(istate, old);
- free(old);
+ discard_cache_entry(old);
+ ce->ce_flags &= ~CE_HASHED;
set_index_entry(istate, nr, ce);
ce->ce_flags |= CE_UPDATE_IN_BASE;
+ mark_fsmonitor_invalid(istate, ce);
istate->cache_changed |= CE_ENTRY_CHANGED;
}
void rename_index_entry_at(struct index_state *istate, int nr, const char *new_name)
{
- struct cache_entry *old = istate->cache[nr], *new;
+ struct cache_entry *old_entry = istate->cache[nr], *new_entry;
int namelen = strlen(new_name);
- new = xmalloc(cache_entry_size(namelen));
- copy_cache_entry(new, old);
- new->ce_flags &= ~CE_HASHED;
- new->ce_namelen = namelen;
- new->index = 0;
- memcpy(new->name, new_name, namelen + 1);
+ new_entry = make_empty_cache_entry(istate, namelen);
+ copy_cache_entry(new_entry, old_entry);
+ new_entry->ce_flags &= ~CE_HASHED;
+ new_entry->ce_namelen = namelen;
+ new_entry->index = 0;
+ memcpy(new_entry->name, new_name, namelen + 1);
- cache_tree_invalidate_path(istate, old->name);
- untracked_cache_remove_from_index(istate, old->name);
+ cache_tree_invalidate_path(istate, old_entry->name);
+ untracked_cache_remove_from_index(istate, old_entry->name);
remove_index_entry_at(istate, nr);
- add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
+ add_index_entry(istate, new_entry, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
}
void fill_stat_data(struct stat_data *sd, struct stat *st)
@@ -152,19 +199,21 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
if (assume_unchanged)
ce->ce_flags |= CE_VALID;
- if (S_ISREG(st->st_mode))
+ if (S_ISREG(st->st_mode)) {
ce_mark_uptodate(ce);
+ mark_fsmonitor_valid(ce);
+ }
}
static int ce_compare_data(const struct cache_entry *ce, struct stat *st)
{
int match = -1;
- int fd = open(ce->name, O_RDONLY);
+ int fd = git_open_cloexec(ce->name, O_RDONLY);
if (fd >= 0) {
- unsigned char sha1[20];
- if (!index_fd(sha1, fd, st, OBJ_BLOB, ce->name, 0))
- match = hashcmp(sha1, ce->sha1);
+ struct object_id oid;
+ if (!index_fd(&oid, fd, st, OBJ_BLOB, ce->name, 0))
+ match = !oideq(&oid, &ce->oid);
/* index_fd() closed the file descriptor already */
}
return match;
@@ -181,7 +230,7 @@ static int ce_compare_link(const struct cache_entry *ce, size_t expected_size)
if (strbuf_readlink(&sb, ce->name, expected_size))
return -1;
- buffer = read_sha1_file(ce->sha1, &type, &size);
+ buffer = read_object_file(&ce->oid, &type, &size);
if (buffer) {
if (size == sb.len)
match = memcmp(buffer, sb.buf, size);
@@ -193,7 +242,7 @@ static int ce_compare_link(const struct cache_entry *ce, size_t expected_size)
static int ce_compare_gitlink(const struct cache_entry *ce)
{
- unsigned char sha1[20];
+ struct object_id oid;
/*
* We don't actually require that the .git directory
@@ -203,9 +252,9 @@ static int ce_compare_gitlink(const struct cache_entry *ce)
*
* If so, we consider it always to match.
*/
- if (resolve_gitlink_ref(ce->name, "HEAD", sha1) < 0)
+ if (resolve_gitlink_ref(ce->name, "HEAD", &oid) < 0)
return 0;
- return hashcmp(sha1, ce->sha1);
+ return !oideq(&oid, &ce->oid);
}
static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st)
@@ -222,6 +271,7 @@ static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st)
case S_IFDIR:
if (S_ISGITLINK(ce->ce_mode))
return ce_compare_gitlink(ce) ? DATA_CHANGED : 0;
+ /* else fallthrough */
default:
return TYPE_CHANGED;
}
@@ -265,7 +315,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
/* Racily smudged entry? */
if (!ce->ce_stat_data.sd_size) {
- if (!is_empty_blob_sha1(ce->sha1))
+ if (!is_empty_blob_sha1(ce->oid.hash))
changed |= DATA_CHANGED;
}
@@ -302,7 +352,7 @@ int match_stat_data_racy(const struct index_state *istate,
return match_stat_data(sd, st);
}
-int ie_match_stat(const struct index_state *istate,
+int ie_match_stat(struct index_state *istate,
const struct cache_entry *ce, struct stat *st,
unsigned int options)
{
@@ -310,7 +360,10 @@ int ie_match_stat(const struct index_state *istate,
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
+ int ignore_fsmonitor = options & CE_MATCH_IGNORE_FSMONITOR;
+ if (!ignore_fsmonitor)
+ refresh_fsmonitor(istate);
/*
* If it's marked as always valid in the index, it's
* valid whatever the checked-out copy says.
@@ -321,6 +374,8 @@ int ie_match_stat(const struct index_state *istate,
return 0;
if (!ignore_valid && (ce->ce_flags & CE_VALID))
return 0;
+ if (!ignore_fsmonitor && (ce->ce_flags & CE_FSMONITOR_VALID))
+ return 0;
/*
* Intent-to-add entries have not been added, so the index entry
@@ -358,7 +413,7 @@ int ie_match_stat(const struct index_state *istate,
return changed;
}
-int ie_modified(const struct index_state *istate,
+int ie_modified(struct index_state *istate,
const struct cache_entry *ce,
struct stat *st, unsigned int options)
{
@@ -506,7 +561,6 @@ int index_name_pos(const struct index_state *istate, const char *name, int namel
return index_name_stage_pos(istate, name, namelen, 0);
}
-/* Remove entry, return true if there are more entries to go.. */
int remove_index_entry_at(struct index_state *istate, int pos)
{
struct cache_entry *ce = istate->cache[pos];
@@ -518,9 +572,8 @@ int remove_index_entry_at(struct index_state *istate, int pos)
istate->cache_nr--;
if (pos >= istate->cache_nr)
return 0;
- memmove(istate->cache + pos,
- istate->cache + pos + 1,
- (istate->cache_nr - pos) * sizeof(struct cache_entry *));
+ MOVE_ARRAY(istate->cache + pos, istate->cache + pos + 1,
+ istate->cache_nr - pos);
return 1;
}
@@ -608,39 +661,43 @@ static struct cache_entry *create_alias_ce(struct index_state *istate,
struct cache_entry *alias)
{
int len;
- struct cache_entry *new;
+ struct cache_entry *new_entry;
if (alias->ce_flags & CE_ADDED)
die("Will not add file alias '%s' ('%s' already exists in index)", ce->name, alias->name);
/* Ok, create the new entry using the name of the existing alias */
len = ce_namelen(alias);
- new = xcalloc(1, cache_entry_size(len));
- memcpy(new->name, alias->name, len);
- copy_cache_entry(new, ce);
+ new_entry = make_empty_cache_entry(istate, len);
+ memcpy(new_entry->name, alias->name, len);
+ copy_cache_entry(new_entry, ce);
save_or_free_index_entry(istate, ce);
- return new;
+ return new_entry;
}
void set_object_name_for_intent_to_add_entry(struct cache_entry *ce)
{
- unsigned char sha1[20];
- if (write_sha1_file("", 0, blob_type, sha1))
+ struct object_id oid;
+ if (write_object_file("", 0, blob_type, &oid))
die("cannot create an empty blob in the object database");
- hashcpy(ce->sha1, sha1);
+ oidcpy(&ce->oid, &oid);
}
-int add_to_index(struct index_state *istate, const char *path, struct stat *st, int flags, int force_mode)
+int add_to_index(struct index_state *istate, const char *path, struct stat *st, int flags)
{
- int size, namelen, was_same;
+ int namelen, was_same;
mode_t st_mode = st->st_mode;
- struct cache_entry *ce, *alias;
+ struct cache_entry *ce, *alias = NULL;
unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY;
int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND);
int pretend = flags & ADD_CACHE_PRETEND;
int intent_only = flags & ADD_CACHE_INTENT;
int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|
(intent_only ? ADD_CACHE_NEW_ONLY : 0));
+ int newflags = HASH_WRITE_OBJECT;
+
+ if (flags & HASH_RENORMALIZE)
+ newflags |= HASH_RENORMALIZE;
if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
return error("%s: can only add regular files, symbolic links or git-directories", path);
@@ -650,8 +707,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
while (namelen && path[namelen-1] == '/')
namelen--;
}
- size = cache_entry_size(namelen);
- ce = xcalloc(1, size);
+ ce = make_empty_cache_entry(istate, namelen);
memcpy(ce->name, path, namelen);
ce->ce_namelen = namelen;
if (!intent_only)
@@ -659,11 +715,10 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
else
ce->ce_flags |= CE_INTENT_TO_ADD;
- if (S_ISREG(st_mode) && force_mode)
- ce->ce_mode = create_ce_mode(force_mode);
- else if (trust_executable_bit && has_symlinks)
+
+ if (trust_executable_bit && has_symlinks) {
ce->ce_mode = create_ce_mode(st_mode);
- else {
+ } else {
/* If there is an existing entry, pick the mode bits and type
* from it, otherwise assume unexecutable regular file.
*/
@@ -682,20 +737,24 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
if (ignore_case) {
adjust_dirname_case(istate, ce->name);
}
+ if (!(flags & HASH_RENORMALIZE)) {
+ alias = index_file_exists(istate, ce->name,
+ ce_namelen(ce), ignore_case);
+ if (alias &&
+ !ce_stage(alias) &&
+ !ie_match_stat(istate, alias, st, ce_option)) {
+ /* Nothing changed, really */
+ if (!S_ISGITLINK(alias->ce_mode))
+ ce_mark_uptodate(alias);
+ alias->ce_flags |= CE_ADDED;
- alias = index_file_exists(istate, ce->name, ce_namelen(ce), ignore_case);
- if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) {
- /* Nothing changed, really */
- if (!S_ISGITLINK(alias->ce_mode))
- ce_mark_uptodate(alias);
- alias->ce_flags |= CE_ADDED;
-
- free(ce);
- return 0;
+ discard_cache_entry(ce);
+ return 0;
+ }
}
if (!intent_only) {
- if (index_path(ce->sha1, path, st, HASH_WRITE_OBJECT)) {
- free(ce);
+ if (index_path(&ce->oid, path, st, newflags)) {
+ discard_cache_entry(ce);
return error("unable to index file %s", path);
}
} else
@@ -708,13 +767,13 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
/* It was suspected to be racily clean, but it turns out to be Ok */
was_same = (alias &&
!ce_stage(alias) &&
- !hashcmp(alias->sha1, ce->sha1) &&
+ oideq(&alias->oid, &ce->oid) &&
ce->ce_mode == alias->ce_mode);
if (pretend)
- free(ce);
+ discard_cache_entry(ce);
else if (add_index_entry(istate, ce, add_option)) {
- free(ce);
+ discard_cache_entry(ce);
return error("unable to add %s to index", path);
}
if (verbose && !was_same)
@@ -722,43 +781,107 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
return 0;
}
-int add_file_to_index(struct index_state *istate, const char *path,
- int flags, int force_mode)
+int add_file_to_index(struct index_state *istate, const char *path, int flags)
{
struct stat st;
if (lstat(path, &st))
die_errno("unable to stat '%s'", path);
- return add_to_index(istate, path, &st, flags, force_mode);
+ return add_to_index(istate, path, &st, flags);
+}
+
+struct cache_entry *make_empty_cache_entry(struct index_state *istate, size_t len)
+{
+ return mem_pool__ce_calloc(find_mem_pool(istate), len);
}
-struct cache_entry *make_cache_entry(unsigned int mode,
- const unsigned char *sha1, const char *path, int stage,
- unsigned int refresh_options)
+struct cache_entry *make_empty_transient_cache_entry(size_t len)
+{
+ return xcalloc(1, cache_entry_size(len));
+}
+
+struct cache_entry *make_cache_entry(struct index_state *istate,
+ unsigned int mode,
+ const struct object_id *oid,
+ const char *path,
+ int stage,
+ unsigned int refresh_options)
{
- int size, len;
struct cache_entry *ce, *ret;
+ int len;
- if (!verify_path(path)) {
+ if (!verify_path(path, mode)) {
error("Invalid path '%s'", path);
return NULL;
}
len = strlen(path);
- size = cache_entry_size(len);
- ce = xcalloc(1, size);
+ ce = make_empty_cache_entry(istate, len);
- hashcpy(ce->sha1, sha1);
+ oidcpy(&ce->oid, oid);
memcpy(ce->name, path, len);
ce->ce_flags = create_ce_flags(stage);
ce->ce_namelen = len;
ce->ce_mode = create_ce_mode(mode);
- ret = refresh_cache_entry(ce, refresh_options);
+ ret = refresh_cache_entry(&the_index, ce, refresh_options);
if (ret != ce)
- free(ce);
+ discard_cache_entry(ce);
return ret;
}
+struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct object_id *oid,
+ const char *path, int stage)
+{
+ struct cache_entry *ce;
+ int len;
+
+ if (!verify_path(path, mode)) {
+ error("Invalid path '%s'", path);
+ return NULL;
+ }
+
+ len = strlen(path);
+ ce = make_empty_transient_cache_entry(len);
+
+ oidcpy(&ce->oid, oid);
+ memcpy(ce->name, path, len);
+ ce->ce_flags = create_ce_flags(stage);
+ ce->ce_namelen = len;
+ ce->ce_mode = create_ce_mode(mode);
+
+ return ce;
+}
+
+/*
+ * Chmod an index entry with either +x or -x.
+ *
+ * Returns -1 if the chmod for the particular cache entry failed (if it's
+ * not a regular file), -2 if an invalid flip argument is passed in, 0
+ * otherwise.
+ */
+int chmod_index_entry(struct index_state *istate, struct cache_entry *ce,
+ char flip)
+{
+ if (!S_ISREG(ce->ce_mode))
+ return -1;
+ switch (flip) {
+ case '+':
+ ce->ce_mode |= 0111;
+ break;
+ case '-':
+ ce->ce_mode &= ~0111;
+ break;
+ default:
+ return -2;
+ }
+ cache_tree_invalidate_path(istate, ce->name);
+ ce->ce_flags |= CE_UPDATE_IN_BASE;
+ mark_fsmonitor_invalid(istate, ce);
+ istate->cache_changed |= CE_ENTRY_CHANGED;
+
+ return 0;
+}
+
int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
{
int len = ce_namelen(a);
@@ -773,7 +896,7 @@ int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
* Also, we don't want double slashes or slashes at the
* end that can make pathnames ambiguous.
*/
-static int verify_dotfile(const char *rest)
+static int verify_dotfile(const char *rest, unsigned mode)
{
/*
* The first character was '.', but that
@@ -787,8 +910,13 @@ static int verify_dotfile(const char *rest)
switch (*rest) {
/*
- * ".git" followed by NUL or slash is bad. This
- * shares the path end test with the ".." case.
+ * ".git" followed by NUL or slash is bad. Note that we match
+ * case-insensitively here, even if ignore_case is not set.
+ * This outlaws ".GIT" everywhere out of an abundance of caution,
+ * since there's really no good reason to allow it.
+ *
+ * Once we've seen ".git", we can also find ".gitmodules", etc (also
+ * case-insensitively).
*/
case 'g':
case 'G':
@@ -796,8 +924,15 @@ static int verify_dotfile(const char *rest)
break;
if (rest[2] != 't' && rest[2] != 'T')
break;
- rest += 2;
- /* fallthrough */
+ if (rest[3] == '\0' || is_dir_sep(rest[3]))
+ return 0;
+ if (S_ISLNK(mode)) {
+ rest += 3;
+ if (skip_iprefix(rest, "modules", &rest) &&
+ (*rest == '\0' || is_dir_sep(*rest)))
+ return 0;
+ }
+ break;
case '.':
if (rest[1] == '\0' || is_dir_sep(rest[1]))
return 0;
@@ -805,7 +940,7 @@ static int verify_dotfile(const char *rest)
return 1;
}
-int verify_path(const char *path)
+int verify_path(const char *path, unsigned mode)
{
char c;
@@ -818,12 +953,25 @@ int verify_path(const char *path)
return 1;
if (is_dir_sep(c)) {
inside:
- if (protect_hfs && is_hfs_dotgit(path))
- return 0;
- if (protect_ntfs && is_ntfs_dotgit(path))
- return 0;
+ if (protect_hfs) {
+ if (is_hfs_dotgit(path))
+ return 0;
+ if (S_ISLNK(mode)) {
+ if (is_hfs_dotgitmodules(path))
+ return 0;
+ }
+ }
+ if (protect_ntfs) {
+ if (is_ntfs_dotgit(path))
+ return 0;
+ if (S_ISLNK(mode)) {
+ if (is_ntfs_dotgitmodules(path))
+ return 0;
+ }
+ }
+
c = *path++;
- if ((c == '.' && !verify_dotfile(path)) ||
+ if ((c == '.' && !verify_dotfile(path, mode)) ||
is_dir_sep(c) || c == '\0')
return 0;
}
@@ -864,9 +1012,32 @@ static int has_file_name(struct index_state *istate,
return retval;
}
+
+/*
+ * Like strcmp(), but also return the offset of the first change.
+ * If strings are equal, return the length.
+ */
+int strcmp_offset(const char *s1, const char *s2, size_t *first_change)
+{
+ size_t k;
+
+ if (!first_change)
+ return strcmp(s1, s2);
+
+ for (k = 0; s1[k] == s2[k]; k++)
+ if (s1[k] == '\0')
+ break;
+
+ *first_change = k;
+ return (unsigned char)s1[k] - (unsigned char)s2[k];
+}
+
/*
* Do we have another file with a pathname that is a proper
* subset of the name we're trying to add?
+ *
+ * That is, is there another file in the index with a path
+ * that matches a sub-directory in the given entry?
*/
static int has_dir_name(struct index_state *istate,
const struct cache_entry *ce, int pos, int ok_to_replace)
@@ -875,9 +1046,51 @@ static int has_dir_name(struct index_state *istate,
int stage = ce_stage(ce);
const char *name = ce->name;
const char *slash = name + ce_namelen(ce);
+ size_t len_eq_last;
+ int cmp_last = 0;
+
+ /*
+ * We are frequently called during an iteration on a sorted
+ * list of pathnames and while building a new index. Therefore,
+ * there is a high probability that this entry will eventually
+ * be appended to the index, rather than inserted in the middle.
+ * If we can confirm that, we can avoid binary searches on the
+ * components of the pathname.
+ *
+ * Compare the entry's full path with the last path in the index.
+ */
+ if (istate->cache_nr > 0) {
+ cmp_last = strcmp_offset(name,
+ istate->cache[istate->cache_nr - 1]->name,
+ &len_eq_last);
+ if (cmp_last > 0) {
+ if (len_eq_last == 0) {
+ /*
+ * The entry sorts AFTER the last one in the
+ * index and their paths have no common prefix,
+ * so there cannot be a F/D conflict.
+ */
+ return retval;
+ } else {
+ /*
+ * The entry sorts AFTER the last one in the
+ * index, but has a common prefix. Fall through
+ * to the loop below to disect the entry's path
+ * and see where the difference is.
+ */
+ }
+ } else if (cmp_last == 0) {
+ /*
+ * The entry exactly matches the last one in the
+ * index, but because of multiple stage and CE_REMOVE
+ * items, we fall through and let the regular search
+ * code handle it.
+ */
+ }
+ }
for (;;) {
- int len;
+ size_t len;
for (;;) {
if (*--slash == '/')
@@ -887,6 +1100,67 @@ static int has_dir_name(struct index_state *istate,
}
len = slash - name;
+ if (cmp_last > 0) {
+ /*
+ * (len + 1) is a directory boundary (including
+ * the trailing slash). And since the loop is
+ * decrementing "slash", the first iteration is
+ * the longest directory prefix; subsequent
+ * iterations consider parent directories.
+ */
+
+ if (len + 1 <= len_eq_last) {
+ /*
+ * The directory prefix (including the trailing
+ * slash) also appears as a prefix in the last
+ * entry, so the remainder cannot collide (because
+ * strcmp said the whole path was greater).
+ *
+ * EQ: last: xxx/A
+ * this: xxx/B
+ *
+ * LT: last: xxx/file_A
+ * this: xxx/file_B
+ */
+ return retval;
+ }
+
+ if (len > len_eq_last) {
+ /*
+ * This part of the directory prefix (excluding
+ * the trailing slash) is longer than the known
+ * equal portions, so this sub-directory cannot
+ * collide with a file.
+ *
+ * GT: last: xxxA
+ * this: xxxB/file
+ */
+ return retval;
+ }
+
+ if (istate->cache_nr > 0 &&
+ ce_namelen(istate->cache[istate->cache_nr - 1]) > len) {
+ /*
+ * The directory prefix lines up with part of
+ * a longer file or directory name, but sorts
+ * after it, so this sub-directory cannot
+ * collide with a file.
+ *
+ * last: xxx/yy-file (because '-' sorts before '/')
+ * this: xxx/yy/abc
+ */
+ return retval;
+ }
+
+ /*
+ * This is a possible collision. Fall through and
+ * let the regular search code handle it.
+ *
+ * last: xxx
+ * this: xxx/file
+ */
+ }
+
pos = index_name_stage_pos(istate, name, len, stage);
if (pos >= 0) {
/*
@@ -978,7 +1252,16 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
cache_tree_invalidate_path(istate, ce->name);
- pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
+
+ /*
+ * If this entry's path sorts after the last entry in the index,
+ * we can avoid searching for it.
+ */
+ if (istate->cache_nr > 0 &&
+ strcmp(ce->name, istate->cache[istate->cache_nr - 1]->name) > 0)
+ pos = -istate->cache_nr - 1;
+ else
+ pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce));
/* existing match? Just replace it. */
if (pos >= 0) {
@@ -1005,7 +1288,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
if (!ok_to_add)
return -1;
- if (!verify_path(ce->name))
+ if (!verify_path(ce->name, ce->ce_mode))
return error("Invalid path '%s'", ce->name);
if (!skip_df_check &&
@@ -1039,9 +1322,8 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
/* Add it in.. */
istate->cache_nr++;
if (istate->cache_nr > pos + 1)
- memmove(istate->cache + pos + 1,
- istate->cache + pos,
- (istate->cache_nr - pos - 1) * sizeof(ce));
+ MOVE_ARRAY(istate->cache + pos + 1, istate->cache + pos,
+ istate->cache_nr - pos - 1);
set_index_entry(istate, pos, ce);
istate->cache_changed |= CE_ENTRY_ADDED;
return 0;
@@ -1065,15 +1347,18 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
{
struct stat st;
struct cache_entry *updated;
- int changed, size;
+ int changed;
int refresh = options & CE_MATCH_REFRESH;
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
int ignore_missing = options & CE_MATCH_IGNORE_MISSING;
+ int ignore_fsmonitor = options & CE_MATCH_IGNORE_FSMONITOR;
if (!refresh || ce_uptodate(ce))
return ce;
+ if (!ignore_fsmonitor)
+ refresh_fsmonitor(istate);
/*
* CE_VALID or CE_SKIP_WORKTREE means the user promised us
* that the change to the work tree does not matter and told
@@ -1087,6 +1372,10 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
ce_mark_uptodate(ce);
return ce;
}
+ if (!ignore_fsmonitor && (ce->ce_flags & CE_FSMONITOR_VALID)) {
+ ce_mark_uptodate(ce);
+ return ce;
+ }
if (has_symlink_leading_path(ce->name, ce_namelen(ce))) {
if (ignore_missing)
@@ -1124,8 +1413,10 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
* because CE_UPTODATE flag is in-core only;
* we are not going to write this change out.
*/
- if (!S_ISGITLINK(ce->ce_mode))
+ if (!S_ISGITLINK(ce->ce_mode)) {
ce_mark_uptodate(ce);
+ mark_fsmonitor_valid(ce);
+ }
return ce;
}
}
@@ -1136,9 +1427,9 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
return NULL;
}
- size = ce_size(ce);
- updated = xmalloc(size);
- memcpy(updated, ce, size);
+ updated = make_empty_cache_entry(istate, ce_namelen(ce));
+ copy_cache_entry(updated, ce);
+ memcpy(updated->name, ce->name, ce->ce_namelen + 1);
fill_stat_cache_info(updated, &st);
/*
* If ignore_valid is not set, we should leave CE_VALID bit
@@ -1186,13 +1477,14 @@ int refresh_index(struct index_state *istate, unsigned int flags,
const char *added_fmt;
const char *unmerged_fmt;
+ trace_performance_enter();
modified_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n");
deleted_fmt = (in_porcelain ? "D\t%s\n" : "%s: needs update\n");
typechange_fmt = (in_porcelain ? "T\t%s\n" : "%s needs update\n");
added_fmt = (in_porcelain ? "A\t%s\n" : "%s needs update\n");
unmerged_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n");
for (i = 0; i < istate->cache_nr; i++) {
- struct cache_entry *ce, *new;
+ struct cache_entry *ce, *new_entry;
int cache_errno = 0;
int changed = 0;
int filtered = 0;
@@ -1201,7 +1493,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
if (ignore_submodules && S_ISGITLINK(ce->ce_mode))
continue;
- if (pathspec && !ce_path_match(ce, pathspec, seen))
+ if (pathspec && !ce_path_match(&the_index, ce, pathspec, seen))
filtered = 1;
if (ce_stage(ce)) {
@@ -1221,10 +1513,10 @@ int refresh_index(struct index_state *istate, unsigned int flags,
if (filtered)
continue;
- new = refresh_cache_ent(istate, ce, options, &cache_errno, &changed);
- if (new == ce)
+ new_entry = refresh_cache_ent(istate, ce, options, &cache_errno, &changed);
+ if (new_entry == ce)
continue;
- if (!new) {
+ if (!new_entry) {
const char *fmt;
if (really && cache_errno == EINVAL) {
@@ -1233,6 +1525,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
*/
ce->ce_flags &= ~CE_VALID;
ce->ce_flags |= CE_UPDATE_IN_BASE;
+ mark_fsmonitor_invalid(istate, ce);
istate->cache_changed |= CE_ENTRY_CHANGED;
}
if (quiet)
@@ -1252,15 +1545,17 @@ int refresh_index(struct index_state *istate, unsigned int flags,
continue;
}
- replace_index_entry(istate, i, new);
+ replace_index_entry(istate, i, new_entry);
}
+ trace_performance_leave("refresh index");
return has_errors;
}
-static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
- unsigned int options)
+struct cache_entry *refresh_cache_entry(struct index_state *istate,
+ struct cache_entry *ce,
+ unsigned int options)
{
- return refresh_cache_ent(&the_index, ce, options, NULL, NULL);
+ return refresh_cache_ent(istate, ce, options, NULL, NULL);
}
@@ -1341,6 +1636,7 @@ struct ondisk_cache_entry_extended {
};
/* These are only used for v3 or lower */
+#define align_padding_size(size, len) ((size + (len) + 8) & ~7) - (size + len)
#define align_flex_name(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
#define ondisk_cache_entry_size(len) align_flex_name(ondisk_cache_entry,len)
#define ondisk_cache_entry_extended_size(len) align_flex_name(ondisk_cache_entry_extended,len)
@@ -1348,10 +1644,16 @@ struct ondisk_cache_entry_extended {
ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
ondisk_cache_entry_size(ce_namelen(ce)))
+/* Allow fsck to force verification of the index checksum. */
+int verify_index_checksum;
+
+/* Allow fsck to force verification of the cache entry order. */
+int verify_ce_order;
+
static int verify_hdr(struct cache_header *hdr, unsigned long size)
{
- git_SHA_CTX c;
- unsigned char sha1[20];
+ git_hash_ctx c;
+ unsigned char hash[GIT_MAX_RAWSZ];
int hdr_version;
if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
@@ -1359,10 +1661,14 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
hdr_version = ntohl(hdr->hdr_version);
if (hdr_version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < hdr_version)
return error("bad index version %d", hdr_version);
- git_SHA1_Init(&c);
- git_SHA1_Update(&c, hdr, size - 20);
- git_SHA1_Final(sha1, &c);
- if (hashcmp(sha1, (unsigned char *)hdr + size - 20))
+
+ if (!verify_index_checksum)
+ return 0;
+
+ the_hash_algo->init_fn(&c);
+ the_hash_algo->update_fn(&c, hdr, size - the_hash_algo->rawsz);
+ the_hash_algo->final_fn(hash, &c);
+ if (!hasheq(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz))
return error("bad index file sha1 signature");
return 0;
}
@@ -1384,6 +1690,9 @@ static int read_index_extension(struct index_state *istate,
case CACHE_EXT_UNTRACKED:
istate->untracked = read_untracked_extension(data, sz);
break;
+ case CACHE_EXT_FSMONITOR:
+ read_fsmonitor_extension(istate, data, sz);
+ break;
default:
if (*ext < 'A' || 'Z' < *ext)
return error("index uses %.4s extension, which we do not understand",
@@ -1394,25 +1703,23 @@ static int read_index_extension(struct index_state *istate,
return 0;
}
-int hold_locked_index(struct lock_file *lk, int die_on_error)
+int hold_locked_index(struct lock_file *lk, int lock_flags)
{
- return hold_lock_file_for_update(lk, get_index_file(),
- die_on_error
- ? LOCK_DIE_ON_ERROR
- : 0);
+ return hold_lock_file_for_update(lk, get_index_file(), lock_flags);
}
int read_index(struct index_state *istate)
{
- return read_index_from(istate, get_index_file());
+ return read_index_from(istate, get_index_file(), get_git_dir());
}
-static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *ondisk,
+static struct cache_entry *cache_entry_from_ondisk(struct mem_pool *mem_pool,
+ struct ondisk_cache_entry *ondisk,
unsigned int flags,
const char *name,
size_t len)
{
- struct cache_entry *ce = xmalloc(cache_entry_size(len));
+ struct cache_entry *ce = mem_pool__ce_alloc(mem_pool, len);
ce->ce_stat_data.sd_ctime.sec = get_be32(&ondisk->ctime.sec);
ce->ce_stat_data.sd_mtime.sec = get_be32(&ondisk->mtime.sec);
@@ -1427,7 +1734,7 @@ static struct cache_entry *cache_entry_from_ondisk(struct ondisk_cache_entry *on
ce->ce_flags = flags & ~CE_NAMEMASK;
ce->ce_namelen = len;
ce->index = 0;
- hashcpy(ce->sha1, ondisk->sha1);
+ hashcpy(ce->oid.hash, ondisk->sha1);
memcpy(ce->name, name, len);
ce->name[len] = '\0';
return ce;
@@ -1454,7 +1761,8 @@ static unsigned long expand_name_field(struct strbuf *name, const char *cp_)
return (const char *)ep + 1 - cp_;
}
-static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk,
+static struct cache_entry *create_from_disk(struct mem_pool *mem_pool,
+ struct ondisk_cache_entry *ondisk,
unsigned long *ent_size,
struct strbuf *previous_name)
{
@@ -1485,13 +1793,13 @@ static struct cache_entry *create_from_disk(struct ondisk_cache_entry *ondisk,
/* v3 and earlier */
if (len == CE_NAMEMASK)
len = strlen(name);
- ce = cache_entry_from_ondisk(ondisk, flags, name, len);
+ ce = cache_entry_from_ondisk(mem_pool, ondisk, flags, name, len);
*ent_size = ondisk_ce_size(ce);
} else {
unsigned long consumed;
consumed = expand_name_field(previous_name, name);
- ce = cache_entry_from_ondisk(ondisk, flags,
+ ce = cache_entry_from_ondisk(mem_pool, ondisk, flags,
previous_name->buf,
previous_name->len);
@@ -1504,6 +1812,9 @@ static void check_ce_order(struct index_state *istate)
{
unsigned int i;
+ if (!verify_ce_order)
+ return;
+
for (i = 1; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i - 1];
struct cache_entry *next_ce = istate->cache[i];
@@ -1538,10 +1849,44 @@ static void tweak_untracked_cache(struct index_state *istate)
}
}
+static void tweak_split_index(struct index_state *istate)
+{
+ switch (git_config_get_split_index()) {
+ case -1: /* unset: do nothing */
+ break;
+ case 0: /* false */
+ remove_split_index(istate);
+ break;
+ case 1: /* true */
+ add_split_index(istate);
+ break;
+ default: /* unknown value: do nothing */
+ break;
+ }
+}
+
static void post_read_index_from(struct index_state *istate)
{
check_ce_order(istate);
tweak_untracked_cache(istate);
+ tweak_split_index(istate);
+ tweak_fsmonitor(istate);
+}
+
+static size_t estimate_cache_size_from_compressed(unsigned int entries)
+{
+ return entries * (sizeof(struct cache_entry) + CACHE_ENTRY_PATH_LENGTH);
+}
+
+static size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
+{
+ long per_entry = sizeof(struct cache_entry) - sizeof(struct ondisk_cache_entry);
+
+ /*
+ * Account for potential alignment differences.
+ */
+ per_entry += align_padding_size(sizeof(struct cache_entry), -sizeof(struct ondisk_cache_entry));
+ return ondisk_size + entries * per_entry;
}
/* remember to discard_cache() before reading a different cache! */
@@ -1571,7 +1916,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
die_errno("cannot stat the open index");
mmap_size = xsize_t(st.st_size);
- if (mmap_size < sizeof(struct cache_header) + 20)
+ if (mmap_size < sizeof(struct cache_header) + the_hash_algo->rawsz)
die("index file smaller than expected");
mmap = xmmap(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0);
@@ -1583,17 +1928,22 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
if (verify_hdr(hdr, mmap_size) < 0)
goto unmap;
- hashcpy(istate->sha1, (const unsigned char *)hdr + mmap_size - 20);
+ hashcpy(istate->oid.hash, (const unsigned char *)hdr + mmap_size - the_hash_algo->rawsz);
istate->version = ntohl(hdr->hdr_version);
istate->cache_nr = ntohl(hdr->hdr_entries);
istate->cache_alloc = alloc_nr(istate->cache_nr);
istate->cache = xcalloc(istate->cache_alloc, sizeof(*istate->cache));
istate->initialized = 1;
- if (istate->version == 4)
+ if (istate->version == 4) {
previous_name = &previous_name_buf;
- else
+ mem_pool_init(&istate->ce_mem_pool,
+ estimate_cache_size_from_compressed(istate->cache_nr));
+ } else {
previous_name = NULL;
+ mem_pool_init(&istate->ce_mem_pool,
+ estimate_cache_size(mmap_size, istate->cache_nr));
+ }
src_offset = sizeof(*hdr);
for (i = 0; i < istate->cache_nr; i++) {
@@ -1602,7 +1952,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
unsigned long consumed;
disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset);
- ce = create_from_disk(disk_ce, &consumed, previous_name);
+ ce = create_from_disk(istate->ce_mem_pool, disk_ce, &consumed, previous_name);
set_index_entry(istate, i, ce);
src_offset += consumed;
@@ -1611,7 +1961,7 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist)
istate->timestamp.sec = st.st_mtime;
istate->timestamp.nsec = ST_MTIME_NSEC(st);
- while (src_offset <= mmap_size - 20 - 8) {
+ while (src_offset <= mmap_size - the_hash_algo->rawsz - 8) {
/* After an array of active_nr index entries,
* there can be arbitrary number of extended
* sections, each of which is prefixed with
@@ -1637,38 +1987,59 @@ unmap:
die("index file corrupt");
}
-int read_index_from(struct index_state *istate, const char *path)
+/*
+ * Signal that the shared index is used by updating its mtime.
+ *
+ * This way, shared index can be removed if they have not been used
+ * for some time.
+ */
+static void freshen_shared_index(const char *shared_index, int warn)
+{
+ if (!check_and_freshen_file(shared_index, 1) && warn)
+ warning("could not freshen shared index '%s'", shared_index);
+}
+
+int read_index_from(struct index_state *istate, const char *path,
+ const char *gitdir)
{
struct split_index *split_index;
int ret;
+ char *base_oid_hex;
+ char *base_path;
/* istate->initialized covers both .git/index and .git/sharedindex.xxx */
if (istate->initialized)
return istate->cache_nr;
+ trace_performance_enter();
ret = do_read_index(istate, path, 0);
+ trace_performance_leave("read cache %s", path);
split_index = istate->split_index;
- if (!split_index || is_null_sha1(split_index->base_sha1)) {
+ if (!split_index || is_null_oid(&split_index->base_oid)) {
post_read_index_from(istate);
return ret;
}
+ trace_performance_enter();
if (split_index->base)
discard_index(split_index->base);
else
split_index->base = xcalloc(1, sizeof(*split_index->base));
- ret = do_read_index(split_index->base,
- git_path("sharedindex.%s",
- sha1_to_hex(split_index->base_sha1)), 1);
- if (hashcmp(split_index->base_sha1, split_index->base->sha1))
+
+ base_oid_hex = oid_to_hex(&split_index->base_oid);
+ base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex);
+ ret = do_read_index(split_index->base, base_path, 1);
+ if (!oideq(&split_index->base_oid, &split_index->base->oid))
die("broken index, expect %s in %s, got %s",
- sha1_to_hex(split_index->base_sha1),
- git_path("sharedindex.%s",
- sha1_to_hex(split_index->base_sha1)),
- sha1_to_hex(split_index->base->sha1));
+ base_oid_hex, base_path,
+ oid_to_hex(&split_index->base->oid));
+
+ freshen_shared_index(base_path, 0);
merge_base_index(istate);
post_read_index_from(istate);
+ free(base_path);
+ trace_performance_leave("read cache %s", base_path);
return ret;
}
@@ -1679,17 +2050,15 @@ int is_index_unborn(struct index_state *istate)
int discard_index(struct index_state *istate)
{
- int i;
+ /*
+ * Cache entries in istate->cache[] should have been allocated
+ * from the memory pool associated with this index, or from an
+ * associated split_index. There is no need to free individual
+ * cache entries. validate_cache_entries can detect when this
+ * assertion does not hold.
+ */
+ validate_cache_entries(istate);
- for (i = 0; i < istate->cache_nr; i++) {
- if (istate->cache[i]->index &&
- istate->split_index &&
- istate->split_index->base &&
- istate->cache[i]->index <= istate->split_index->base->cache_nr &&
- istate->cache[i] == istate->split_index->base->cache[istate->cache[i]->index - 1])
- continue;
- free(istate->cache[i]);
- }
resolve_undo_clear_index(istate);
istate->cache_nr = 0;
istate->cache_changed = 0;
@@ -1698,15 +2067,52 @@ int discard_index(struct index_state *istate)
free_name_hash(istate);
cache_tree_free(&(istate->cache_tree));
istate->initialized = 0;
- free(istate->cache);
- istate->cache = NULL;
+ FREE_AND_NULL(istate->cache);
istate->cache_alloc = 0;
discard_split_index(istate);
free_untracked_cache(istate->untracked);
istate->untracked = NULL;
+
+ if (istate->ce_mem_pool) {
+ mem_pool_discard(istate->ce_mem_pool, should_validate_cache_entries());
+ istate->ce_mem_pool = NULL;
+ }
+
return 0;
}
+/*
+ * Validate the cache entries of this index.
+ * All cache entries associated with this index
+ * should have been allocated by the memory pool
+ * associated with this index, or by a referenced
+ * split index.
+ */
+void validate_cache_entries(const struct index_state *istate)
+{
+ int i;
+
+ if (!should_validate_cache_entries() ||!istate || !istate->initialized)
+ return;
+
+ for (i = 0; i < istate->cache_nr; i++) {
+ if (!istate) {
+ die("internal error: cache entry is not allocated from expected memory pool");
+ } else if (!istate->ce_mem_pool ||
+ !mem_pool_contains(istate->ce_mem_pool, istate->cache[i])) {
+ if (!istate->split_index ||
+ !istate->split_index->base ||
+ !istate->split_index->base->ce_mem_pool ||
+ !mem_pool_contains(istate->split_index->base->ce_mem_pool, istate->cache[i])) {
+ die("internal error: cache entry is not allocated from expected memory pool");
+ }
+ }
+ }
+
+ if (istate->split_index)
+ validate_cache_entries(istate->split_index->base);
+}
+
int unmerged_index(const struct index_state *istate)
{
int i;
@@ -1717,23 +2123,61 @@ int unmerged_index(const struct index_state *istate)
return 0;
}
+int index_has_changes(const struct index_state *istate,
+ struct tree *tree,
+ struct strbuf *sb)
+{
+ struct object_id cmp;
+ int i;
+
+ if (istate != &the_index) {
+ BUG("index_has_changes cannot yet accept istate != &the_index; do_diff_cache needs updating first.");
+ }
+ if (tree)
+ cmp = tree->object.oid;
+ if (tree || !get_oid_tree("HEAD", &cmp)) {
+ struct diff_options opt;
+
+ diff_setup(&opt);
+ opt.flags.exit_with_status = 1;
+ if (!sb)
+ opt.flags.quick = 1;
+ do_diff_cache(&cmp, &opt);
+ diffcore_std(&opt);
+ for (i = 0; sb && i < diff_queued_diff.nr; i++) {
+ if (i)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);
+ }
+ diff_flush(&opt);
+ return opt.flags.has_changes != 0;
+ } else {
+ for (i = 0; sb && i < istate->cache_nr; i++) {
+ if (i)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, istate->cache[i]->name);
+ }
+ return !!istate->cache_nr;
+ }
+}
+
#define WRITE_BUFFER_SIZE 8192
static unsigned char write_buffer[WRITE_BUFFER_SIZE];
static unsigned long write_buffer_len;
-static int ce_write_flush(git_SHA_CTX *context, int fd)
+static int ce_write_flush(git_hash_ctx *context, int fd)
{
unsigned int buffered = write_buffer_len;
if (buffered) {
- git_SHA1_Update(context, write_buffer, buffered);
- if (write_in_full(fd, write_buffer, buffered) != buffered)
+ the_hash_algo->update_fn(context, write_buffer, buffered);
+ if (write_in_full(fd, write_buffer, buffered) < 0)
return -1;
write_buffer_len = 0;
}
return 0;
}
-static int ce_write(git_SHA_CTX *context, int fd, void *data, unsigned int len)
+static int ce_write(git_hash_ctx *context, int fd, void *data, unsigned int len)
{
while (len) {
unsigned int buffered = write_buffer_len;
@@ -1755,7 +2199,7 @@ static int ce_write(git_SHA_CTX *context, int fd, void *data, unsigned int len)
return 0;
}
-static int write_index_ext_header(git_SHA_CTX *context, int fd,
+static int write_index_ext_header(git_hash_ctx *context, int fd,
unsigned int ext, unsigned int sz)
{
ext = htonl(ext);
@@ -1764,27 +2208,27 @@ static int write_index_ext_header(git_SHA_CTX *context, int fd,
(ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0;
}
-static int ce_flush(git_SHA_CTX *context, int fd, unsigned char *sha1)
+static int ce_flush(git_hash_ctx *context, int fd, unsigned char *hash)
{
unsigned int left = write_buffer_len;
if (left) {
write_buffer_len = 0;
- git_SHA1_Update(context, write_buffer, left);
+ the_hash_algo->update_fn(context, write_buffer, left);
}
- /* Flush first if not enough space for SHA1 signature */
- if (left + 20 > WRITE_BUFFER_SIZE) {
- if (write_in_full(fd, write_buffer, left) != left)
+ /* Flush first if not enough space for hash signature */
+ if (left + the_hash_algo->rawsz > WRITE_BUFFER_SIZE) {
+ if (write_in_full(fd, write_buffer, left) < 0)
return -1;
left = 0;
}
- /* Append the SHA1 signature at the end */
- git_SHA1_Final(write_buffer + left, context);
- hashcpy(sha1, write_buffer + left);
- left += 20;
- return (write_in_full(fd, write_buffer, left) != left) ? -1 : 0;
+ /* Append the hash signature at the end */
+ the_hash_algo->final_fn(write_buffer + left, context);
+ hashcpy(hash, write_buffer + left);
+ left += the_hash_algo->rawsz;
+ return (write_in_full(fd, write_buffer, left) < 0) ? -1 : 0;
}
static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
@@ -1837,7 +2281,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
}
/* Copy miscellaneous fields but not the name */
-static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
+static void copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
struct cache_entry *ce)
{
short flags;
@@ -1852,7 +2296,7 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
ondisk->uid = htonl(ce->ce_stat_data.sd_uid);
ondisk->gid = htonl(ce->ce_stat_data.sd_gid);
ondisk->size = htonl(ce->ce_stat_data.sd_size);
- hashcpy(ondisk->sha1, ce->sha1);
+ hashcpy(ondisk->sha1, ce->oid.hash);
flags = ce->ce_flags & ~CE_NAMEMASK;
flags |= (ce_namelen(ce) >= CE_NAMEMASK ? CE_NAMEMASK : ce_namelen(ce));
@@ -1861,32 +2305,37 @@ static char *copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk,
struct ondisk_cache_entry_extended *ondisk2;
ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16);
- return ondisk2->name;
- }
- else {
- return ondisk->name;
}
}
-static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
- struct strbuf *previous_name)
+static int ce_write_entry(git_hash_ctx *c, int fd, struct cache_entry *ce,
+ struct strbuf *previous_name, struct ondisk_cache_entry *ondisk)
{
int size;
- struct ondisk_cache_entry *ondisk;
- int saved_namelen = saved_namelen; /* compiler workaround */
- char *name;
int result;
+ unsigned int saved_namelen;
+ int stripped_name = 0;
+ static unsigned char padding[8] = { 0x00 };
if (ce->ce_flags & CE_STRIP_NAME) {
saved_namelen = ce_namelen(ce);
ce->ce_namelen = 0;
+ stripped_name = 1;
}
+ if (ce->ce_flags & CE_EXTENDED)
+ size = offsetof(struct ondisk_cache_entry_extended, name);
+ else
+ size = offsetof(struct ondisk_cache_entry, name);
+
if (!previous_name) {
- size = ondisk_ce_size(ce);
- ondisk = xcalloc(1, size);
- name = copy_cache_entry_to_ondisk(ondisk, ce);
- memcpy(name, ce->name, ce_namelen(ce));
+ int len = ce_namelen(ce);
+ copy_cache_entry_to_ondisk(ondisk, ce);
+ result = ce_write(c, fd, ondisk, size);
+ if (!result)
+ result = ce_write(c, fd, ce->name, len);
+ if (!result)
+ result = ce_write(c, fd, padding, align_padding_size(size, len));
} else {
int common, to_remove, prefix_size;
unsigned char to_remove_vi[16];
@@ -1899,27 +2348,23 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce,
to_remove = previous_name->len - common;
prefix_size = encode_varint(to_remove, to_remove_vi);
- if (ce->ce_flags & CE_EXTENDED)
- size = offsetof(struct ondisk_cache_entry_extended, name);
- else
- size = offsetof(struct ondisk_cache_entry, name);
- size += prefix_size + (ce_namelen(ce) - common + 1);
-
- ondisk = xcalloc(1, size);
- name = copy_cache_entry_to_ondisk(ondisk, ce);
- memcpy(name, to_remove_vi, prefix_size);
- memcpy(name + prefix_size, ce->name + common, ce_namelen(ce) - common);
+ copy_cache_entry_to_ondisk(ondisk, ce);
+ result = ce_write(c, fd, ondisk, size);
+ if (!result)
+ result = ce_write(c, fd, to_remove_vi, prefix_size);
+ if (!result)
+ result = ce_write(c, fd, ce->name + common, ce_namelen(ce) - common);
+ if (!result)
+ result = ce_write(c, fd, padding, 1);
strbuf_splice(previous_name, common, to_remove,
ce->name + common, ce_namelen(ce) - common);
}
- if (ce->ce_flags & CE_STRIP_NAME) {
+ if (stripped_name) {
ce->ce_namelen = saved_namelen;
ce->ce_flags &= ~CE_STRIP_NAME;
}
- result = ce_write(c, fd, ondisk, size);
- free(ondisk);
return result;
}
@@ -1932,7 +2377,7 @@ static int verify_index_from(const struct index_state *istate, const char *path)
int fd;
ssize_t n;
struct stat st;
- unsigned char sha1[20];
+ unsigned char hash[GIT_MAX_RAWSZ];
if (!istate->initialized)
return 0;
@@ -1944,14 +2389,14 @@ static int verify_index_from(const struct index_state *istate, const char *path)
if (fstat(fd, &st))
goto out;
- if (st.st_size < sizeof(struct cache_header) + 20)
+ if (st.st_size < sizeof(struct cache_header) + the_hash_algo->rawsz)
goto out;
- n = pread_in_full(fd, sha1, 20, st.st_size - 20);
- if (n != 20)
+ n = pread_in_full(fd, hash, the_hash_algo->rawsz, st.st_size - the_hash_algo->rawsz);
+ if (n != the_hash_algo->rawsz)
goto out;
- if (hashcmp(istate->sha1, sha1))
+ if (!hasheq(istate->oid.hash, hash))
goto out;
close(fd);
@@ -1980,27 +2425,36 @@ static int has_racy_timestamp(struct index_state *istate)
return 0;
}
-/*
- * Opportunistically update the index but do not complain if we can't
- */
void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
{
if ((istate->cache_changed || has_racy_timestamp(istate)) &&
- verify_index(istate) &&
- write_locked_index(istate, lockfile, COMMIT_LOCK))
+ verify_index(istate))
+ write_locked_index(istate, lockfile, COMMIT_LOCK);
+ else
rollback_lock_file(lockfile);
}
-static int do_write_index(struct index_state *istate, int newfd,
+/*
+ * On success, `tempfile` is closed. If it is the temporary file
+ * of a `struct lock_file`, we will therefore effectively perform
+ * a 'close_lock_file_gently()`. Since that is an implementation
+ * detail of lockfiles, callers of `do_write_index()` should not
+ * rely on it.
+ */
+static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
int strip_extensions)
{
- git_SHA_CTX c;
+ uint64_t start = getnanotime();
+ int newfd = tempfile->fd;
+ git_hash_ctx c;
struct cache_header hdr;
- int i, err, removed, extended, hdr_version;
+ int i, err = 0, removed, extended, hdr_version;
struct cache_entry **cache = istate->cache;
int entries = istate->cache_nr;
struct stat st;
+ struct ondisk_cache_entry_extended ondisk;
struct strbuf previous_name_buf = STRBUF_INIT, *previous_name;
+ int drop_cache_tree = istate->drop_cache_tree;
for (i = removed = extended = 0; i < entries; i++) {
if (cache[i]->ce_flags & CE_REMOVE)
@@ -2016,7 +2470,7 @@ static int do_write_index(struct index_state *istate, int newfd,
if (!istate->version) {
istate->version = get_index_format_default();
- if (getenv("GIT_TEST_SPLIT_INDEX"))
+ if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0))
init_split_index(istate);
}
@@ -2030,18 +2484,19 @@ static int do_write_index(struct index_state *istate, int newfd,
hdr.hdr_version = htonl(hdr_version);
hdr.hdr_entries = htonl(entries - removed);
- git_SHA1_Init(&c);
+ the_hash_algo->init_fn(&c);
if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
return -1;
previous_name = (hdr_version == 4) ? &previous_name_buf : NULL;
+
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
if (ce->ce_flags & CE_REMOVE)
continue;
if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce))
ce_smudge_racily_clean_entry(ce);
- if (is_null_sha1(ce->sha1)) {
+ if (is_null_oid(&ce->oid)) {
static const char msg[] = "cache entry has null sha1: %s";
static int allow = -1;
@@ -2050,13 +2505,21 @@ static int do_write_index(struct index_state *istate, int newfd,
if (allow)
warning(msg, ce->name);
else
- return error(msg, ce->name);
+ err = error(msg, ce->name);
+
+ drop_cache_tree = 1;
}
- if (ce_write_entry(&c, newfd, ce, previous_name) < 0)
- return -1;
+ if (ce_write_entry(&c, newfd, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0)
+ err = -1;
+
+ if (err)
+ break;
}
strbuf_release(&previous_name_buf);
+ if (err)
+ return err;
+
/* Write extension data here */
if (!strip_extensions && istate->split_index) {
struct strbuf sb = STRBUF_INIT;
@@ -2069,7 +2532,7 @@ static int do_write_index(struct index_state *istate, int newfd,
if (err)
return -1;
}
- if (!strip_extensions && istate->cache_tree) {
+ if (!strip_extensions && !drop_cache_tree && istate->cache_tree) {
struct strbuf sb = STRBUF_INIT;
cache_tree_write(&sb, istate->cache_tree);
@@ -2101,11 +2564,28 @@ static int do_write_index(struct index_state *istate, int newfd,
if (err)
return -1;
}
+ if (!strip_extensions && istate->fsmonitor_last_update) {
+ struct strbuf sb = STRBUF_INIT;
+
+ write_fsmonitor_extension(&sb, istate);
+ err = write_index_ext_header(&c, newfd, CACHE_EXT_FSMONITOR, sb.len) < 0
+ || ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ strbuf_release(&sb);
+ if (err)
+ return -1;
+ }
- if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))
+ if (ce_flush(&c, newfd, istate->oid.hash))
+ return -1;
+ if (close_tempfile_gently(tempfile)) {
+ error(_("could not close '%s'"), tempfile->filename.buf);
+ return -1;
+ }
+ if (stat(tempfile->filename.buf, &st))
return -1;
istate->timestamp.sec = (unsigned int)st.st_mtime;
istate->timestamp.nsec = ST_MTIME_NSEC(st);
+ trace_performance_since(start, "write index, changed mask = %x", istate->cache_changed);
return 0;
}
@@ -2125,17 +2605,12 @@ static int commit_locked_index(struct lock_file *lk)
static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,
unsigned flags)
{
- int ret = do_write_index(istate, get_lock_file_fd(lock), 0);
+ int ret = do_write_index(istate, lock->tempfile, 0);
if (ret)
return ret;
- assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) !=
- (COMMIT_LOCK | CLOSE_LOCK));
if (flags & COMMIT_LOCK)
return commit_locked_index(lock);
- else if (flags & CLOSE_LOCK)
- return close_lock_file(lock);
- else
- return ret;
+ return close_lock_file_gently(lock);
}
static int write_split_index(struct index_state *istate,
@@ -2149,64 +2624,201 @@ static int write_split_index(struct index_state *istate,
return ret;
}
-static struct tempfile temporary_sharedindex;
+static const char *shared_index_expire = "2.weeks.ago";
+
+static unsigned long get_shared_index_expire_date(void)
+{
+ static unsigned long shared_index_expire_date;
+ static int shared_index_expire_date_prepared;
+
+ if (!shared_index_expire_date_prepared) {
+ git_config_get_expiry("splitindex.sharedindexexpire",
+ &shared_index_expire);
+ shared_index_expire_date = approxidate(shared_index_expire);
+ shared_index_expire_date_prepared = 1;
+ }
+
+ return shared_index_expire_date;
+}
+
+static int should_delete_shared_index(const char *shared_index_path)
+{
+ struct stat st;
+ unsigned long expiration;
+
+ /* Check timestamp */
+ expiration = get_shared_index_expire_date();
+ if (!expiration)
+ return 0;
+ if (stat(shared_index_path, &st))
+ return error_errno(_("could not stat '%s'"), shared_index_path);
+ if (st.st_mtime > expiration)
+ return 0;
+
+ return 1;
+}
+
+static int clean_shared_index_files(const char *current_hex)
+{
+ struct dirent *de;
+ DIR *dir = opendir(get_git_dir());
+
+ if (!dir)
+ return error_errno(_("unable to open git dir: %s"), get_git_dir());
+
+ while ((de = readdir(dir)) != NULL) {
+ const char *sha1_hex;
+ const char *shared_index_path;
+ if (!skip_prefix(de->d_name, "sharedindex.", &sha1_hex))
+ continue;
+ if (!strcmp(sha1_hex, current_hex))
+ continue;
+ shared_index_path = git_path("%s", de->d_name);
+ if (should_delete_shared_index(shared_index_path) > 0 &&
+ unlink(shared_index_path))
+ warning_errno(_("unable to unlink: %s"), shared_index_path);
+ }
+ closedir(dir);
+
+ return 0;
+}
static int write_shared_index(struct index_state *istate,
- struct lock_file *lock, unsigned flags)
+ struct tempfile **temp)
{
struct split_index *si = istate->split_index;
- int fd, ret;
+ int ret;
- fd = mks_tempfile(&temporary_sharedindex, git_path("sharedindex_XXXXXX"));
- if (fd < 0) {
- hashclr(si->base_sha1);
- return do_write_locked_index(istate, lock, flags);
- }
move_cache_to_base_index(istate);
- ret = do_write_index(si->base, fd, 1);
+ ret = do_write_index(si->base, *temp, 1);
+ if (ret)
+ return ret;
+ ret = adjust_shared_perm(get_tempfile_path(*temp));
if (ret) {
- delete_tempfile(&temporary_sharedindex);
+ error("cannot fix permission bits on %s", get_tempfile_path(*temp));
return ret;
}
- ret = rename_tempfile(&temporary_sharedindex,
- git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
- if (!ret)
- hashcpy(si->base_sha1, si->base->sha1);
+ ret = rename_tempfile(temp,
+ git_path("sharedindex.%s", oid_to_hex(&si->base->oid)));
+ if (!ret) {
+ oidcpy(&si->base_oid, &si->base->oid);
+ clean_shared_index_files(oid_to_hex(&si->base->oid));
+ }
+
return ret;
}
+static const int default_max_percent_split_change = 20;
+
+static int too_many_not_shared_entries(struct index_state *istate)
+{
+ int i, not_shared = 0;
+ int max_split = git_config_get_max_percent_split_change();
+
+ switch (max_split) {
+ case -1:
+ /* not or badly configured: use the default value */
+ max_split = default_max_percent_split_change;
+ break;
+ case 0:
+ return 1; /* 0% means always write a new shared index */
+ case 100:
+ return 0; /* 100% means never write a new shared index */
+ default:
+ break; /* just use the configured value */
+ }
+
+ /* Count not shared entries */
+ for (i = 0; i < istate->cache_nr; i++) {
+ struct cache_entry *ce = istate->cache[i];
+ if (!ce->index)
+ not_shared++;
+ }
+
+ return (int64_t)istate->cache_nr * max_split < (int64_t)not_shared * 100;
+}
+
int write_locked_index(struct index_state *istate, struct lock_file *lock,
unsigned flags)
{
+ int new_shared_index, ret;
struct split_index *si = istate->split_index;
+ if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
+ cache_tree_verify(istate);
+
+ if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) {
+ if (flags & COMMIT_LOCK)
+ rollback_lock_file(lock);
+ return 0;
+ }
+
+ if (istate->fsmonitor_last_update)
+ fill_fsmonitor_bitmap(istate);
+
if (!si || alternate_index_output ||
(istate->cache_changed & ~EXTMASK)) {
if (si)
- hashclr(si->base_sha1);
- return do_write_locked_index(istate, lock, flags);
+ oidclr(&si->base_oid);
+ ret = do_write_locked_index(istate, lock, flags);
+ goto out;
}
- if (getenv("GIT_TEST_SPLIT_INDEX")) {
- int v = si->base_sha1[0];
+ if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) {
+ int v = si->base_oid.hash[0];
if ((v & 15) < 6)
istate->cache_changed |= SPLIT_INDEX_ORDERED;
}
- if (istate->cache_changed & SPLIT_INDEX_ORDERED) {
- int ret = write_shared_index(istate, lock, flags);
+ if (too_many_not_shared_entries(istate))
+ istate->cache_changed |= SPLIT_INDEX_ORDERED;
+
+ new_shared_index = istate->cache_changed & SPLIT_INDEX_ORDERED;
+
+ if (new_shared_index) {
+ struct tempfile *temp;
+ int saved_errno;
+
+ temp = mks_tempfile(git_path("sharedindex_XXXXXX"));
+ if (!temp) {
+ oidclr(&si->base_oid);
+ ret = do_write_locked_index(istate, lock, flags);
+ goto out;
+ }
+ ret = write_shared_index(istate, &temp);
+
+ saved_errno = errno;
+ if (is_tempfile_active(temp))
+ delete_tempfile(&temp);
+ errno = saved_errno;
+
if (ret)
- return ret;
+ goto out;
+ }
+
+ ret = write_split_index(istate, lock, flags);
+
+ /* Freshen the shared index only if the split-index was written */
+ if (!ret && !new_shared_index) {
+ const char *shared_index = git_path("sharedindex.%s",
+ oid_to_hex(&si->base_oid));
+ freshen_shared_index(shared_index, 1);
}
- return write_split_index(istate, lock, flags);
+out:
+ if (flags & COMMIT_LOCK)
+ rollback_lock_file(lock);
+ return ret;
}
/*
* Read the index file that is potentially unmerged into given
- * index_state, dropping any unmerged entries. Returns true if
- * the index is unmerged. Callers who want to refuse to work
- * from an unmerged state can call this and check its return value,
- * instead of calling read_cache().
+ * index_state, dropping any unmerged entries to stage #0 (potentially
+ * resulting in a path appearing as both a file and a directory in the
+ * index; the caller is responsible to clear out the extra entries
+ * before writing the index to a tree). Returns true if the index is
+ * unmerged. Callers who want to refuse to work from an unmerged
+ * state can call this and check its return value, instead of calling
+ * read_cache().
*/
int read_index_unmerged(struct index_state *istate)
{
@@ -2217,19 +2829,18 @@ int read_index_unmerged(struct index_state *istate)
for (i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
struct cache_entry *new_ce;
- int size, len;
+ int len;
if (!ce_stage(ce))
continue;
unmerged = 1;
len = ce_namelen(ce);
- size = cache_entry_size(len);
- new_ce = xcalloc(1, size);
+ new_ce = make_empty_cache_entry(istate, len);
memcpy(new_ce->name, ce->name, len);
new_ce->ce_flags = create_ce_flags(0) | CE_CONFLICTED;
new_ce->ce_namelen = len;
new_ce->ce_mode = ce->ce_mode;
- if (add_index_entry(istate, new_ce, 0))
+ if (add_index_entry(istate, new_ce, ADD_CACHE_SKIP_DFCHECK))
return error("%s: cannot drop to stage #0",
new_ce->name);
}
@@ -2264,7 +2875,8 @@ int index_name_is_other(const struct index_state *istate, const char *name,
return 1;
}
-void *read_blob_data_from_index(struct index_state *istate, const char *path, unsigned long *size)
+void *read_blob_data_from_index(const struct index_state *istate,
+ const char *path, unsigned long *size)
{
int pos, len;
unsigned long sz;
@@ -2288,7 +2900,7 @@ void *read_blob_data_from_index(struct index_state *istate, const char *path, un
}
if (pos < 0)
return NULL;
- data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
+ data = read_object_file(&istate->cache[pos]->oid, &type, &sz);
if (!data || type != OBJ_BLOB) {
free(data);
return NULL;
@@ -2300,8 +2912,7 @@ void *read_blob_data_from_index(struct index_state *istate, const char *path, un
void stat_validity_clear(struct stat_validity *sv)
{
- free(sv->sd);
- sv->sd = NULL;
+ FREE_AND_NULL(sv->sd);
}
int stat_validity_check(struct stat_validity *sv, const char *path)
@@ -2327,3 +2938,49 @@ void stat_validity_update(struct stat_validity *sv, int fd)
fill_stat_data(sv->sd, &st);
}
}
+
+void move_index_extensions(struct index_state *dst, struct index_state *src)
+{
+ dst->untracked = src->untracked;
+ src->untracked = NULL;
+ dst->cache_tree = src->cache_tree;
+ src->cache_tree = NULL;
+}
+
+struct cache_entry *dup_cache_entry(const struct cache_entry *ce,
+ struct index_state *istate)
+{
+ unsigned int size = ce_size(ce);
+ int mem_pool_allocated;
+ struct cache_entry *new_entry = make_empty_cache_entry(istate, ce_namelen(ce));
+ mem_pool_allocated = new_entry->mem_pool_allocated;
+
+ memcpy(new_entry, ce, size);
+ new_entry->mem_pool_allocated = mem_pool_allocated;
+ return new_entry;
+}
+
+void discard_cache_entry(struct cache_entry *ce)
+{
+ if (ce && should_validate_cache_entries())
+ memset(ce, 0xCD, cache_entry_size(ce->ce_namelen));
+
+ if (ce && ce->mem_pool_allocated)
+ return;
+
+ free(ce);
+}
+
+int should_validate_cache_entries(void)
+{
+ static int validate_index_cache_entries = -1;
+
+ if (validate_index_cache_entries < 0) {
+ if (getenv("GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES"))
+ validate_index_cache_entries = 1;
+ else
+ validate_index_cache_entries = 0;
+ }
+
+ return validate_index_cache_entries;
+}