*/
#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
+#include "lockfile.h"
#include "cache-tree.h"
#include "refs.h"
#include "dir.h"
#include "resolve-undo.h"
#include "strbuf.h"
#include "varint.h"
+#include "split-index.h"
+#include "sigchain.h"
#include "utf8.h"
static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
#define CACHE_EXT_TREE 0x54524545 /* "TREE" */
#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
+#define CACHE_EXT_LINK 0x6c696e6b /* "link" */
+
+/* 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)
struct index_state the_index;
+static const char *alternate_index_output;
static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
{
{
struct cache_entry *old = istate->cache[nr];
+ replace_index_entry_in_base(istate, old, ce);
remove_name_hash(istate, old);
free(old);
set_index_entry(istate, nr, ce);
- istate->cache_changed = 1;
+ ce->ce_flags |= CE_UPDATE_IN_BASE;
+ istate->cache_changed |= CE_ENTRY_CHANGED;
}
void rename_index_entry_at(struct index_state *istate, int nr, const char *new_name)
copy_cache_entry(new, old);
new->ce_flags &= ~CE_HASHED;
new->ce_namelen = namelen;
+ new->index = 0;
memcpy(new->name, new_name, namelen + 1);
- cache_tree_invalidate_path(istate->cache_tree, old->name);
+ cache_tree_invalidate_path(istate, old->name);
remove_index_entry_at(istate, nr);
add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
}
return c1 - c2;
}
-int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2)
+int name_compare(const char *name1, size_t len1, const char *name2, size_t len2)
{
- int len = len1 < len2 ? len1 : len2;
- int cmp;
-
- cmp = memcmp(name1, name2, len);
+ size_t min_len = (len1 < len2) ? len1 : len2;
+ int cmp = memcmp(name1, name2, min_len);
if (cmp)
return cmp;
if (len1 < len2)
return -1;
if (len1 > len2)
return 1;
+ return 0;
+}
+
+int cache_name_stage_compare(const char *name1, int len1, int stage1, const char *name2, int len2, int stage2)
+{
+ int cmp;
+
+ cmp = name_compare(name1, len1, name2, len2);
+ if (cmp)
+ return cmp;
if (stage1 < stage2)
return -1;
return 0;
}
-int cache_name_compare(const char *name1, int len1, const char *name2, int len2)
-{
- return cache_name_stage_compare(name1, len1, 0, name2, len2, 0);
-}
-
static int index_name_stage_pos(const struct index_state *istate, const char *name, int namelen, int stage)
{
int first, last;
record_resolve_undo(istate, ce);
remove_name_hash(istate, ce);
- free(ce);
- istate->cache_changed = 1;
+ save_or_free_index_entry(istate, ce);
+ istate->cache_changed |= CE_ENTRY_REMOVED;
istate->cache_nr--;
if (pos >= istate->cache_nr)
return 0;
for (i = j = 0; i < istate->cache_nr; i++) {
if (ce_array[i]->ce_flags & CE_REMOVE) {
remove_name_hash(istate, ce_array[i]);
- free(ce_array[i]);
+ save_or_free_index_entry(istate, ce_array[i]);
}
else
ce_array[j++] = ce_array[i];
}
- istate->cache_changed = 1;
+ if (j == istate->cache_nr)
+ return;
+ istate->cache_changed |= CE_ENTRY_REMOVED;
istate->cache_nr = j;
}
int pos = index_name_pos(istate, path, strlen(path));
if (pos < 0)
pos = -pos-1;
- cache_tree_invalidate_path(istate->cache_tree, path);
+ cache_tree_invalidate_path(istate, path);
while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
remove_index_entry_at(istate, pos);
return 0;
* So we use the CE_ADDED flag to verify that the alias was an old
* one before we accept it as
*/
-static struct cache_entry *create_alias_ce(struct cache_entry *ce, struct cache_entry *alias)
+static struct cache_entry *create_alias_ce(struct index_state *istate,
+ struct cache_entry *ce,
+ struct cache_entry *alias)
{
int len;
struct cache_entry *new;
new = xcalloc(1, cache_entry_size(len));
memcpy(new->name, alias->name, len);
copy_cache_entry(new, ce);
- free(ce);
+ save_or_free_index_entry(istate, ce);
return new;
}
set_object_name_for_intent_to_add_entry(ce);
if (ignore_case && alias && different_name(ce, alias))
- ce = create_alias_ce(ce, alias);
+ ce = create_alias_ce(istate, ce, alias);
ce->ce_flags |= CE_ADDED;
/* It was suspected to be racily clean, but it turns out to be Ok */
int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
int new_only = option & ADD_CACHE_NEW_ONLY;
- cache_tree_invalidate_path(istate->cache_tree, ce->name);
+ 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));
/* existing match? Just replace it. */
istate->cache + pos,
(istate->cache_nr - pos - 1) * sizeof(ce));
set_index_entry(istate, pos, ce);
- istate->cache_changed = 1;
+ istate->cache_changed |= CE_ENTRY_ADDED;
return 0;
}
return ce;
}
+ if (has_symlink_leading_path(ce->name, ce_namelen(ce))) {
+ if (ignore_missing)
+ return ce;
+ if (err)
+ *err = ENOENT;
+ return NULL;
+ }
+
if (lstat(ce->name, &st) < 0) {
if (ignore_missing && errno == ENOENT)
return ce;
!(ce->ce_flags & CE_VALID))
updated->ce_flags &= ~CE_VALID;
+ /* istate->cache_changed is updated in the caller */
return updated;
}
* means the index is not valid anymore.
*/
ce->ce_flags &= ~CE_VALID;
- istate->cache_changed = 1;
+ ce->ce_flags |= CE_UPDATE_IN_BASE;
+ istate->cache_changed |= CE_ENTRY_CHANGED;
}
if (quiet)
continue;
#define INDEX_FORMAT_DEFAULT 3
-static int index_format_config(const char *var, const char *value, void *cb)
-{
- unsigned int *version = cb;
- if (!strcmp(var, "index.version")) {
- *version = git_config_int(var, value);
- return 0;
- }
- return 1;
-}
-
static unsigned int get_index_format_default(void)
{
char *envversion = getenv("GIT_INDEX_VERSION");
char *endp;
+ int value;
unsigned int version = INDEX_FORMAT_DEFAULT;
if (!envversion) {
- git_config(index_format_config, &version);
+ if (!git_config_get_int("index.version", &value))
+ version = value;
if (version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < version) {
warning(_("index.version set, but the value is invalid.\n"
"Using version %i"), INDEX_FORMAT_DEFAULT);
case CACHE_EXT_RESOLVE_UNDO:
istate->resolve_undo = resolve_undo_read(data, sz);
break;
+ case CACHE_EXT_LINK:
+ if (read_link_extension(istate, data, sz))
+ return -1;
+ break;
default:
if (*ext < 'A' || 'Z' < *ext)
return error("index uses %.4s extension, which we do not understand",
return 0;
}
+int hold_locked_index(struct lock_file *lk, int die_on_error)
+{
+ return hold_lock_file_for_update(lk, get_index_file(),
+ die_on_error
+ ? LOCK_DIE_ON_ERROR
+ : 0);
+}
+
int read_index(struct index_state *istate)
{
return read_index_from(istate, get_index_file());
ce->ce_stat_data.sd_size = get_be32(&ondisk->size);
ce->ce_flags = flags & ~CE_NAMEMASK;
ce->ce_namelen = len;
+ ce->index = 0;
hashcpy(ce->sha1, ondisk->sha1);
memcpy(ce->name, name, len);
ce->name[len] = '\0';
return ce;
}
+static void check_ce_order(struct cache_entry *ce, struct cache_entry *next_ce)
+{
+ int name_compare = strcmp(ce->name, next_ce->name);
+ if (0 < name_compare)
+ die("unordered stage entries in index");
+ if (!name_compare) {
+ if (!ce_stage(ce))
+ die("multiple stage entries for merged file '%s'",
+ ce->name);
+ if (ce_stage(ce) > ce_stage(next_ce))
+ die("unordered stage entries for '%s'",
+ ce->name);
+ }
+}
+
/* remember to discard_cache() before reading a different cache! */
-int read_index_from(struct index_state *istate, const char *path)
+int do_read_index(struct index_state *istate, const char *path, int must_exist)
{
int fd, i;
struct stat st;
istate->timestamp.nsec = 0;
fd = open(path, O_RDONLY);
if (fd < 0) {
- if (errno == ENOENT)
+ if (!must_exist && errno == ENOENT)
return 0;
- die_errno("index file open failed");
+ die_errno("%s: index file open failed", path);
}
if (fstat(fd, &st))
if (verify_hdr(hdr, mmap_size) < 0)
goto unmap;
- hashcpy(istate->sha1, (unsigned char *)hdr + mmap_size - 20);
+ hashcpy(istate->sha1, (const unsigned char *)hdr + mmap_size - 20);
istate->version = ntohl(hdr->hdr_version);
istate->cache_nr = ntohl(hdr->hdr_entries);
istate->cache_alloc = alloc_nr(istate->cache_nr);
ce = create_from_disk(disk_ce, &consumed, previous_name);
set_index_entry(istate, i, ce);
+ if (i > 0)
+ check_ce_order(istate->cache[i - 1], ce);
+
src_offset += consumed;
}
strbuf_release(&previous_name_buf);
die("index file corrupt");
}
+int read_index_from(struct index_state *istate, const char *path)
+{
+ struct split_index *split_index;
+ int ret;
+
+ /* istate->initialized covers both .git/index and .git/sharedindex.xxx */
+ if (istate->initialized)
+ return istate->cache_nr;
+
+ ret = do_read_index(istate, path, 0);
+ split_index = istate->split_index;
+ if (!split_index)
+ return ret;
+
+ if (is_null_sha1(split_index->base_sha1))
+ return ret;
+
+ 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))
+ 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));
+ merge_base_index(istate);
+ return ret;
+}
+
int is_index_unborn(struct index_state *istate)
{
return (!istate->cache_nr && !istate->timestamp.sec);
{
int i;
- for (i = 0; i < istate->cache_nr; i++)
+ 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;
free(istate->cache);
istate->cache = NULL;
istate->cache_alloc = 0;
+ discard_split_index(istate);
return 0;
}
(ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0;
}
-static int ce_flush(git_SHA_CTX *context, int fd)
+static int ce_flush(git_SHA_CTX *context, int fd, unsigned char *sha1)
{
unsigned int left = write_buffer_len;
/* 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;
}
ondisk->size = htonl(ce->ce_stat_data.sd_size);
hashcpy(ondisk->sha1, ce->sha1);
- flags = ce->ce_flags;
+ flags = ce->ce_flags & ~CE_NAMEMASK;
flags |= (ce_namelen(ce) >= CE_NAMEMASK ? CE_NAMEMASK : ce_namelen(ce));
ondisk->flags = htons(flags);
if (ce->ce_flags & CE_EXTENDED) {
{
int size;
struct ondisk_cache_entry *ondisk;
+ int saved_namelen = saved_namelen; /* compiler workaround */
char *name;
int result;
+ if (ce->ce_flags & CE_STRIP_NAME) {
+ saved_namelen = ce_namelen(ce);
+ ce->ce_namelen = 0;
+ }
+
if (!previous_name) {
size = ondisk_ce_size(ce);
ondisk = xcalloc(1, size);
strbuf_splice(previous_name, common, to_remove,
ce->name + common, ce_namelen(ce) - common);
}
+ if (ce->ce_flags & CE_STRIP_NAME) {
+ ce->ce_namelen = saved_namelen;
+ ce->ce_flags &= ~CE_STRIP_NAME;
+ }
result = ce_write(c, fd, ondisk, size);
free(ondisk);
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_index(istate, lockfile->fd))
- commit_locked_index(lockfile);
- else
+ verify_index(istate) &&
+ write_locked_index(istate, lockfile, COMMIT_LOCK))
rollback_lock_file(lockfile);
}
-int write_index(struct index_state *istate, int newfd)
+static int do_write_index(struct index_state *istate, int newfd,
+ int strip_extensions)
{
git_SHA_CTX c;
struct cache_header hdr;
}
}
- if (!istate->version)
+ if (!istate->version) {
istate->version = get_index_format_default();
+ if (getenv("GIT_TEST_SPLIT_INDEX"))
+ init_split_index(istate);
+ }
/* demote version 3 to version 2 when the latter suffices */
if (istate->version == 3 || istate->version == 2)
strbuf_release(&previous_name_buf);
/* Write extension data here */
- if (istate->cache_tree) {
+ if (!strip_extensions && istate->split_index) {
+ struct strbuf sb = STRBUF_INIT;
+
+ err = write_link_extension(&sb, istate) < 0 ||
+ write_index_ext_header(&c, newfd, CACHE_EXT_LINK,
+ sb.len) < 0 ||
+ ce_write(&c, newfd, sb.buf, sb.len) < 0;
+ strbuf_release(&sb);
+ if (err)
+ return -1;
+ }
+ if (!strip_extensions && istate->cache_tree) {
struct strbuf sb = STRBUF_INIT;
cache_tree_write(&sb, istate->cache_tree);
if (err)
return -1;
}
- if (istate->resolve_undo) {
+ if (!strip_extensions && istate->resolve_undo) {
struct strbuf sb = STRBUF_INIT;
resolve_undo_write(&sb, istate->resolve_undo);
return -1;
}
- if (ce_flush(&c, newfd) || fstat(newfd, &st))
+ if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))
return -1;
istate->timestamp.sec = (unsigned int)st.st_mtime;
istate->timestamp.nsec = ST_MTIME_NSEC(st);
return 0;
}
+void set_alternate_index_output(const char *name)
+{
+ alternate_index_output = name;
+}
+
+static int commit_locked_index(struct lock_file *lk)
+{
+ if (alternate_index_output)
+ return commit_lock_file_to(lk, alternate_index_output);
+ else
+ return commit_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, lock->fd, 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;
+}
+
+static int write_split_index(struct index_state *istate,
+ struct lock_file *lock,
+ unsigned flags)
+{
+ int ret;
+ prepare_to_write_split_index(istate);
+ ret = do_write_locked_index(istate, lock, flags);
+ finish_writing_split_index(istate);
+ return ret;
+}
+
+static char *temporary_sharedindex;
+
+static void remove_temporary_sharedindex(void)
+{
+ if (temporary_sharedindex) {
+ unlink_or_warn(temporary_sharedindex);
+ free(temporary_sharedindex);
+ temporary_sharedindex = NULL;
+ }
+}
+
+static void remove_temporary_sharedindex_on_signal(int signo)
+{
+ remove_temporary_sharedindex();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+static int write_shared_index(struct index_state *istate,
+ struct lock_file *lock, unsigned flags)
+{
+ struct split_index *si = istate->split_index;
+ static int installed_handler;
+ int fd, ret;
+
+ temporary_sharedindex = git_pathdup("sharedindex_XXXXXX");
+ fd = mkstemp(temporary_sharedindex);
+ if (fd < 0) {
+ free(temporary_sharedindex);
+ temporary_sharedindex = NULL;
+ hashclr(si->base_sha1);
+ return do_write_locked_index(istate, lock, flags);
+ }
+ if (!installed_handler) {
+ atexit(remove_temporary_sharedindex);
+ sigchain_push_common(remove_temporary_sharedindex_on_signal);
+ }
+ move_cache_to_base_index(istate);
+ ret = do_write_index(si->base, fd, 1);
+ close(fd);
+ if (ret) {
+ remove_temporary_sharedindex();
+ return ret;
+ }
+ ret = rename(temporary_sharedindex,
+ git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
+ free(temporary_sharedindex);
+ temporary_sharedindex = NULL;
+ if (!ret)
+ hashcpy(si->base_sha1, si->base->sha1);
+ return ret;
+}
+
+int write_locked_index(struct index_state *istate, struct lock_file *lock,
+ unsigned flags)
+{
+ struct split_index *si = istate->split_index;
+
+ if (!si || alternate_index_output ||
+ (istate->cache_changed & ~EXTMASK)) {
+ if (si)
+ hashclr(si->base_sha1);
+ return do_write_locked_index(istate, lock, flags);
+ }
+
+ if (getenv("GIT_TEST_SPLIT_INDEX")) {
+ int v = si->base_sha1[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 (ret)
+ return ret;
+ }
+
+ return write_split_index(istate, lock, flags);
+}
+
/*
* Read the index file that is potentially unmerged into given
* index_state, dropping any unmerged entries. Returns true if
if (add_index_entry(istate, new_ce, 0))
return error("%s: cannot drop to stage #0",
new_ce->name);
- i = index_name_pos(istate, new_ce->name, len);
}
return unmerged;
}