X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=read-cache.c;h=29144cf879e795c3f5415cd5dd50f53a5dfb0cac;hb=HEAD;hp=7b1354d7590a70ecbd6e508bdd95eafd4793efcc;hpb=63b33047c1428dbf4f480b384962c6c8c0e841dd;p=platform%2Fupstream%2Fgit.git diff --git a/read-cache.c b/read-cache.c index 7b1354d..29144cf 100644 --- a/read-cache.c +++ b/read-cache.c @@ -3,7 +3,6 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" #include "diff.h" @@ -18,11 +17,14 @@ #include "commit.h" #include "blob.h" #include "resolve-undo.h" +#include "run-command.h" #include "strbuf.h" #include "varint.h" #include "split-index.h" #include "utf8.h" #include "fsmonitor.h" +#include "thread-utils.h" +#include "progress.h" /* Mask for the name length in ce_flags in the on-disk index */ @@ -43,6 +45,8 @@ #define CACHE_EXT_LINK 0x6c696e6b /* "link" */ #define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */ #define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */ +#define CACHE_EXT_ENDOFINDEXENTRIES 0x454F4945 /* "EOIE" */ +#define CACHE_EXT_INDEXENTRYOFFSETTABLE 0x49454F54 /* "IEOT" */ /* changes that can be kept in $GIT_DIR/index (basically all extensions) */ #define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \ @@ -85,13 +89,14 @@ static struct mem_pool *find_mem_pool(struct index_state *istate) else pool_ptr = &istate->ce_mem_pool; - if (!*pool_ptr) - mem_pool_init(pool_ptr, 0); + if (!*pool_ptr) { + *pool_ptr = xmalloc(sizeof(**pool_ptr)); + mem_pool_init(*pool_ptr, 0); + } return *pool_ptr; } -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) @@ -192,7 +197,7 @@ int match_stat_data(const struct stat_data *sd, struct stat *st) * cache, ie the parts that aren't tracked by GIT, and only used * to validate the cache. */ -void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) +void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, struct stat *st) { fill_stat_data(&ce->ce_stat_data, st); @@ -201,19 +206,21 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) if (S_ISREG(st->st_mode)) { ce_mark_uptodate(ce); - mark_fsmonitor_valid(ce); + mark_fsmonitor_valid(istate, ce); } } -static int ce_compare_data(const struct cache_entry *ce, struct stat *st) +static int ce_compare_data(struct index_state *istate, + const struct cache_entry *ce, + struct stat *st) { int match = -1; int fd = git_open_cloexec(ce->name, O_RDONLY); if (fd >= 0) { struct object_id oid; - if (!index_fd(&oid, fd, st, OBJ_BLOB, ce->name, 0)) - match = oidcmp(&oid, &ce->oid); + if (!index_fd(istate, &oid, fd, st, OBJ_BLOB, ce->name, 0)) + match = !oideq(&oid, &ce->oid); /* index_fd() closed the file descriptor already */ } return match; @@ -254,14 +261,16 @@ static int ce_compare_gitlink(const struct cache_entry *ce) */ if (resolve_gitlink_ref(ce->name, "HEAD", &oid) < 0) return 0; - return oidcmp(&oid, &ce->oid); + return !oideq(&oid, &ce->oid); } -static int ce_modified_check_fs(const struct cache_entry *ce, struct stat *st) +static int ce_modified_check_fs(struct index_state *istate, + const struct cache_entry *ce, + struct stat *st) { switch (st->st_mode & S_IFMT) { case S_IFREG: - if (ce_compare_data(ce, st)) + if (ce_compare_data(istate, ce, st)) return DATA_CHANGED; break; case S_IFLNK: @@ -308,7 +317,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st) changed |= DATA_CHANGED; return changed; default: - die("internal error: ce_mode is %o", ce->ce_mode); + BUG("unsupported ce_mode: %o", ce->ce_mode); } changed |= match_stat_data(&ce->ce_stat_data, st); @@ -337,7 +346,7 @@ static int is_racy_stat(const struct index_state *istate, ); } -static int is_racy_timestamp(const struct index_state *istate, +int is_racy_timestamp(const struct index_state *istate, const struct cache_entry *ce) { return (!S_ISGITLINK(ce->ce_mode) && @@ -407,7 +416,7 @@ int ie_match_stat(struct index_state *istate, if (assume_racy_is_modified) changed |= DATA_CHANGED; else - changed |= ce_modified_check_fs(ce, st); + changed |= ce_modified_check_fs(istate, ce, st); } return changed; @@ -447,7 +456,7 @@ int ie_modified(struct index_state *istate, (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0)) return changed; - changed_fs = ce_modified_check_fs(ce, st); + changed_fs = ce_modified_check_fs(istate, ce, st); if (changed_fs) return changed | changed_fs; return 0; @@ -542,7 +551,7 @@ static int index_name_stage_pos(const struct index_state *istate, const char *na first = 0; last = istate->cache_nr; while (last > first) { - int next = (last + first) >> 1; + int next = first + ((last - first) >> 1); struct cache_entry *ce = istate->cache[next]; int cmp = cache_name_stage_compare(name, namelen, stage, ce->name, ce_namelen(ce), ce_stage(ce)); if (!cmp) @@ -582,13 +591,19 @@ int remove_index_entry_at(struct index_state *istate, int pos) * CE_REMOVE is set in ce_flags. This is much more effective than * calling remove_index_entry_at() for each entry to be removed. */ -void remove_marked_cache_entries(struct index_state *istate) +void remove_marked_cache_entries(struct index_state *istate, int invalidate) { struct cache_entry **ce_array = istate->cache; unsigned int i, j; for (i = j = 0; i < istate->cache_nr; i++) { if (ce_array[i]->ce_flags & CE_REMOVE) { + if (invalidate) { + cache_tree_invalidate_path(istate, + ce_array[i]->name); + untracked_cache_remove_from_index(istate, + ce_array[i]->name); + } remove_name_hash(istate, ce_array[i]); save_or_free_index_entry(istate, ce_array[i]); } @@ -664,7 +679,8 @@ static struct cache_entry *create_alias_ce(struct index_state *istate, 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); + 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); @@ -679,7 +695,7 @@ void set_object_name_for_intent_to_add_entry(struct cache_entry *ce) { struct object_id oid; if (write_object_file("", 0, blob_type, &oid)) - die("cannot create an empty blob in the object database"); + die(_("cannot create an empty blob in the object database")); oidcpy(&ce->oid, &oid); } @@ -694,16 +710,19 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, 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; + int hash_flags = HASH_WRITE_OBJECT; + struct object_id oid; - if (flags & HASH_RENORMALIZE) - newflags |= HASH_RENORMALIZE; + if (flags & ADD_CACHE_RENORMALIZE) + hash_flags |= 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); + return error(_("%s: can only add regular files, symbolic links or git-directories"), path); namelen = strlen(path); if (S_ISDIR(st_mode)) { + if (resolve_gitlink_ref(path, "HEAD", &oid) < 0) + return error(_("'%s' does not have a commit checked out"), path); while (namelen && path[namelen-1] == '/') namelen--; } @@ -711,7 +730,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, memcpy(ce->name, path, namelen); ce->ce_namelen = namelen; if (!intent_only) - fill_stat_cache_info(ce, st); + fill_stat_cache_info(istate, ce, st); else ce->ce_flags |= CE_INTENT_TO_ADD; @@ -737,7 +756,7 @@ 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)) { + if (!(flags & ADD_CACHE_RENORMALIZE)) { alias = index_file_exists(istate, ce->name, ce_namelen(ce), ignore_case); if (alias && @@ -753,9 +772,9 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, } } if (!intent_only) { - if (index_path(&ce->oid, path, st, newflags)) { + if (index_path(istate, &ce->oid, path, st, hash_flags)) { discard_cache_entry(ce); - return error("unable to index file %s", path); + return error(_("unable to index file '%s'"), path); } } else set_object_name_for_intent_to_add_entry(ce); @@ -767,14 +786,14 @@ 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) && - !oidcmp(&alias->oid, &ce->oid) && + oideq(&alias->oid, &ce->oid) && ce->ce_mode == alias->ce_mode); if (pretend) discard_cache_entry(ce); else if (add_index_entry(istate, ce, add_option)) { discard_cache_entry(ce); - return error("unable to add %s to index", path); + return error(_("unable to add '%s' to index"), path); } if (verbose && !was_same) printf("add '%s'\n", path); @@ -785,7 +804,7 @@ 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); + die_errno(_("unable to stat '%s'"), path); return add_to_index(istate, path, &st, flags); } @@ -810,7 +829,7 @@ struct cache_entry *make_cache_entry(struct index_state *istate, int len; if (!verify_path(path, mode)) { - error("Invalid path '%s'", path); + error(_("invalid path '%s'"), path); return NULL; } @@ -823,7 +842,7 @@ struct cache_entry *make_cache_entry(struct index_state *istate, ce->ce_namelen = len; ce->ce_mode = create_ce_mode(mode); - ret = refresh_cache_entry(&the_index, ce, refresh_options); + ret = refresh_cache_entry(istate, ce, refresh_options); if (ret != ce) discard_cache_entry(ce); return ret; @@ -836,7 +855,7 @@ struct cache_entry *make_transient_cache_entry(unsigned int mode, const struct o int len; if (!verify_path(path, mode)) { - error("Invalid path '%s'", path); + error(_("invalid path '%s'"), path); return NULL; } @@ -942,11 +961,14 @@ static int verify_dotfile(const char *rest, unsigned mode) int verify_path(const char *path, unsigned mode) { - char c; + char c = 0; if (has_dos_drive_prefix(path)) return 0; + if (!is_valid_path(path)) + return 0; + goto inside; for (;;) { if (!c) @@ -954,6 +976,7 @@ int verify_path(const char *path, unsigned mode) if (is_dir_sep(c)) { inside: if (protect_hfs) { + if (is_hfs_dotgit(path)) return 0; if (S_ISLNK(mode)) { @@ -962,6 +985,10 @@ inside: } } if (protect_ntfs) { +#ifdef GIT_WINDOWS_NATIVE + if (c == '\\') + return 0; +#endif if (is_ntfs_dotgit(path)) return 0; if (S_ISLNK(mode)) { @@ -974,7 +1001,15 @@ inside: if ((c == '.' && !verify_dotfile(path, mode)) || is_dir_sep(c) || c == '\0') return 0; + } else if (c == '\\' && protect_ntfs) { + if (is_ntfs_dotgit(path)) + return 0; + if (S_ISLNK(mode)) { + if (is_ntfs_dotgitmodules(path)) + return 0; + } } + c = *path++; } } @@ -1138,20 +1173,6 @@ static int has_dir_name(struct index_state *istate, 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. @@ -1259,7 +1280,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e */ if (istate->cache_nr > 0 && strcmp(ce->name, istate->cache[istate->cache_nr - 1]->name) > 0) - pos = -istate->cache_nr - 1; + pos = index_pos_to_insert_pos(istate->cache_nr); else pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce)); @@ -1289,12 +1310,12 @@ 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, ce->ce_mode)) - return error("Invalid path '%s'", ce->name); + return error(_("invalid path '%s'"), ce->name); if (!skip_df_check && check_file_directory_conflict(istate, ce, pos, ok_to_replace)) { if (!ok_to_replace) - return error("'%s' appears as both a file and as a directory", + return error(_("'%s' appears as both a file and as a directory"), ce->name); pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce)); pos = -pos-1; @@ -1415,7 +1436,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, */ if (!S_ISGITLINK(ce->ce_mode)) { ce_mark_uptodate(ce); - mark_fsmonitor_valid(ce); + mark_fsmonitor_valid(istate, ce); } return ce; } @@ -1430,7 +1451,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, 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); + fill_stat_cache_info(istate, updated, &st); /* * If ignore_valid is not set, we should leave CE_VALID bit * alone. Otherwise, paths marked with --no-assume-unchanged @@ -1455,6 +1476,27 @@ static void show_file(const char * fmt, const char * name, int in_porcelain, printf(fmt, name); } +int repo_refresh_and_write_index(struct repository *repo, + unsigned int refresh_flags, + unsigned int write_flags, + int gentle, + const struct pathspec *pathspec, + char *seen, const char *header_msg) +{ + struct lock_file lock_file = LOCK_INIT; + int fd, ret = 0; + + fd = repo_hold_locked_index(repo, &lock_file, 0); + if (!gentle && fd < 0) + return -1; + if (refresh_index(repo->index, refresh_flags, pathspec, seen, header_msg)) + ret = 1; + if (0 <= fd && write_locked_index(repo->index, &lock_file, COMMIT_LOCK | write_flags)) + ret = -1; + return ret; +} + + int refresh_index(struct index_state *istate, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg) @@ -1476,13 +1518,24 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char *typechange_fmt; const char *added_fmt; const char *unmerged_fmt; - uint64_t start = getnanotime(); - - 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"); + struct progress *progress = NULL; + + if (flags & REFRESH_PROGRESS && isatty(2)) + progress = start_delayed_progress(_("Refresh index"), + istate->cache_nr); + + 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"; + /* + * Use the multi-threaded preload_index() to refresh most of the + * cache entries quickly then in the single threaded loop below, + * we only have to do the special cases that are left. + */ + preload_index(istate, pathspec, 0); for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce, *new_entry; int cache_errno = 0; @@ -1493,7 +1546,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(&the_index, ce, pathspec, seen)) + if (pathspec && !ce_path_match(istate, ce, pathspec, seen)) filtered = 1; if (ce_stage(ce)) { @@ -1516,6 +1569,8 @@ int refresh_index(struct index_state *istate, unsigned int flags, new_entry = refresh_cache_ent(istate, ce, options, &cache_errno, &changed); if (new_entry == ce) continue; + if (progress) + display_progress(progress, i); if (!new_entry) { const char *fmt; @@ -1547,7 +1602,11 @@ int refresh_index(struct index_state *istate, unsigned int flags, replace_index_entry(istate, i, new_entry); } - trace_performance_since(start, "refresh index"); + if (progress) { + display_progress(progress, istate->cache_nr); + stop_progress(&progress); + } + trace_performance_leave("refresh index"); return has_errors; } @@ -1565,16 +1624,17 @@ struct cache_entry *refresh_cache_entry(struct index_state *istate, #define INDEX_FORMAT_DEFAULT 3 -static unsigned int get_index_format_default(void) +static unsigned int get_index_format_default(struct repository *r) { char *envversion = getenv("GIT_INDEX_VERSION"); char *endp; - int value; unsigned int version = INDEX_FORMAT_DEFAULT; if (!envversion) { - if (!git_config_get_int("index.version", &value)) - version = value; + prepare_repo_settings(r); + + if (r->settings.index_version >= 0) + version = r->settings.index_version; 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); @@ -1610,39 +1670,24 @@ struct ondisk_cache_entry { uint32_t uid; uint32_t gid; uint32_t size; - unsigned char sha1[20]; - uint16_t flags; - char name[FLEX_ARRAY]; /* more */ -}; - -/* - * This struct is used when CE_EXTENDED bit is 1 - * The struct must match ondisk_cache_entry exactly from - * ctime till flags - */ -struct ondisk_cache_entry_extended { - struct cache_time ctime; - struct cache_time mtime; - uint32_t dev; - uint32_t ino; - uint32_t mode; - uint32_t uid; - uint32_t gid; - uint32_t size; - unsigned char sha1[20]; - uint16_t flags; - uint16_t flags2; - char name[FLEX_ARRAY]; /* more */ + /* + * unsigned char hash[hashsz]; + * uint16_t flags; + * if (flags & CE_EXTENDED) + * uint16_t flags2; + */ + unsigned char data[GIT_MAX_RAWSZ + 2 * sizeof(uint16_t)]; + char name[FLEX_ARRAY]; }; /* 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 align_flex_name(STRUCT,len) ((offsetof(struct STRUCT,data) + (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) -#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \ - ondisk_cache_entry_extended_size(ce_namelen(ce)) : \ - ondisk_cache_entry_size(ce_namelen(ce))) +#define ondisk_data_size(flags, len) (the_hash_algo->rawsz + \ + ((flags & CE_EXTENDED) ? 2 : 1) * sizeof(uint16_t) + len) +#define ondisk_data_size_max(len) (ondisk_data_size(CE_EXTENDED, len)) +#define ondisk_ce_size(ce) (ondisk_cache_entry_size(ondisk_data_size((ce)->ce_flags, ce_namelen(ce)))) /* Allow fsck to force verification of the index checksum. */ int verify_index_checksum; @@ -1650,17 +1695,17 @@ 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) +static int verify_hdr(const struct cache_header *hdr, unsigned long size) { git_hash_ctx c; unsigned char hash[GIT_MAX_RAWSZ]; int hdr_version; if (hdr->hdr_signature != htonl(CACHE_SIGNATURE)) - return error("bad signature"); + return error(_("bad signature 0x%08x"), hdr->hdr_signature); 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); + return error(_("bad index version %d"), hdr_version); if (!verify_index_checksum) return 0; @@ -1668,13 +1713,13 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size) 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 (hashcmp(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz)) - return error("bad index file sha1 signature"); + if (!hasheq(hash, (unsigned char *)hdr + size - the_hash_algo->rawsz)) + return error(_("bad index file sha1 signature")); return 0; } static int read_index_extension(struct index_state *istate, - const char *ext, void *data, unsigned long sz) + const char *ext, const char *data, unsigned long sz) { switch (CACHE_EXT(ext)) { case CACHE_EXT_TREE: @@ -1693,117 +1738,107 @@ static int read_index_extension(struct index_state *istate, case CACHE_EXT_FSMONITOR: read_fsmonitor_extension(istate, data, sz); break; + case CACHE_EXT_ENDOFINDEXENTRIES: + case CACHE_EXT_INDEXENTRYOFFSETTABLE: + /* already handled in do_read_index() */ + break; default: if (*ext < 'A' || 'Z' < *ext) - return error("index uses %.4s extension, which we do not understand", + return error(_("index uses %.4s extension, which we do not understand"), ext); - fprintf(stderr, "ignoring %.4s extension\n", ext); + fprintf_ln(stderr, _("ignoring %.4s extension"), ext); break; } return 0; } -int hold_locked_index(struct lock_file *lk, int lock_flags) -{ - 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(), get_git_dir()); -} - -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 = 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); - ce->ce_stat_data.sd_ctime.nsec = get_be32(&ondisk->ctime.nsec); - ce->ce_stat_data.sd_mtime.nsec = get_be32(&ondisk->mtime.nsec); - ce->ce_stat_data.sd_dev = get_be32(&ondisk->dev); - ce->ce_stat_data.sd_ino = get_be32(&ondisk->ino); - ce->ce_mode = get_be32(&ondisk->mode); - ce->ce_stat_data.sd_uid = get_be32(&ondisk->uid); - ce->ce_stat_data.sd_gid = get_be32(&ondisk->gid); - 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->oid.hash, ondisk->sha1); - memcpy(ce->name, name, len); - ce->name[len] = '\0'; - return ce; -} - -/* - * Adjacent cache entries tend to share the leading paths, so it makes - * sense to only store the differences in later entries. In the v4 - * on-disk format of the index, each on-disk cache entry stores the - * number of bytes to be stripped from the end of the previous name, - * and the bytes to append to the result, to come up with its name. - */ -static unsigned long expand_name_field(struct strbuf *name, const char *cp_) -{ - const unsigned char *ep, *cp = (const unsigned char *)cp_; - size_t len = decode_varint(&cp); - - if (name->len < len) - die("malformed name field in the index"); - strbuf_remove(name, name->len - len, len); - for (ep = cp; *ep; ep++) - ; /* find the end */ - strbuf_add(name, cp, ep - cp); - return (const char *)ep + 1 - cp_; -} - -static struct cache_entry *create_from_disk(struct mem_pool *mem_pool, +static struct cache_entry *create_from_disk(struct mem_pool *ce_mem_pool, + unsigned int version, struct ondisk_cache_entry *ondisk, unsigned long *ent_size, - struct strbuf *previous_name) + const struct cache_entry *previous_ce) { struct cache_entry *ce; size_t len; const char *name; + const unsigned hashsz = the_hash_algo->rawsz; + const uint16_t *flagsp = (const uint16_t *)(ondisk->data + hashsz); unsigned int flags; + size_t copy_len = 0; + /* + * Adjacent cache entries tend to share the leading paths, so it makes + * sense to only store the differences in later entries. In the v4 + * on-disk format of the index, each on-disk cache entry stores the + * number of bytes to be stripped from the end of the previous name, + * and the bytes to append to the result, to come up with its name. + */ + int expand_name_field = version == 4; /* On-disk flags are just 16 bits */ - flags = get_be16(&ondisk->flags); + flags = get_be16(flagsp); len = flags & CE_NAMEMASK; if (flags & CE_EXTENDED) { - struct ondisk_cache_entry_extended *ondisk2; int extended_flags; - ondisk2 = (struct ondisk_cache_entry_extended *)ondisk; - extended_flags = get_be16(&ondisk2->flags2) << 16; + extended_flags = get_be16(flagsp + 1) << 16; /* We do not yet understand any bit out of CE_EXTENDED_FLAGS */ if (extended_flags & ~CE_EXTENDED_FLAGS) - die("Unknown index entry format %08x", extended_flags); + die(_("unknown index entry format 0x%08x"), extended_flags); flags |= extended_flags; - name = ondisk2->name; + name = (const char *)(flagsp + 2); } else - name = ondisk->name; + name = (const char *)(flagsp + 1); + + if (expand_name_field) { + const unsigned char *cp = (const unsigned char *)name; + size_t strip_len, previous_len; + + /* If we're at the beginning of a block, ignore the previous name */ + strip_len = decode_varint(&cp); + if (previous_ce) { + previous_len = previous_ce->ce_namelen; + if (previous_len < strip_len) + die(_("malformed name field in the index, near path '%s'"), + previous_ce->name); + copy_len = previous_len - strip_len; + } + name = (const char *)cp; + } - if (!previous_name) { - /* v3 and earlier */ - if (len == CE_NAMEMASK) - len = strlen(name); - ce = cache_entry_from_ondisk(mem_pool, ondisk, flags, name, len); + if (len == CE_NAMEMASK) { + len = strlen(name); + if (expand_name_field) + len += copy_len; + } - *ent_size = ondisk_ce_size(ce); - } else { - unsigned long consumed; - consumed = expand_name_field(previous_name, name); - ce = cache_entry_from_ondisk(mem_pool, ondisk, flags, - previous_name->buf, - previous_name->len); + ce = mem_pool__ce_alloc(ce_mem_pool, len); - *ent_size = (name - ((char *)ondisk)) + consumed; + ce->ce_stat_data.sd_ctime.sec = get_be32(&ondisk->ctime.sec); + ce->ce_stat_data.sd_mtime.sec = get_be32(&ondisk->mtime.sec); + ce->ce_stat_data.sd_ctime.nsec = get_be32(&ondisk->ctime.nsec); + ce->ce_stat_data.sd_mtime.nsec = get_be32(&ondisk->mtime.nsec); + ce->ce_stat_data.sd_dev = get_be32(&ondisk->dev); + ce->ce_stat_data.sd_ino = get_be32(&ondisk->ino); + ce->ce_mode = get_be32(&ondisk->mode); + ce->ce_stat_data.sd_uid = get_be32(&ondisk->uid); + ce->ce_stat_data.sd_gid = get_be32(&ondisk->gid); + 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->oid.hash, ondisk->data); + memcpy(ce->name, name, len); + ce->name[len] = '\0'; + + if (expand_name_field) { + if (copy_len) + memcpy(ce->name, previous_ce->name, copy_len); + memcpy(ce->name + copy_len, name, len + 1 - copy_len); + *ent_size = (name - ((char *)ondisk)) + len + 1 - copy_len; + } else { + memcpy(ce->name, name, len + 1); + *ent_size = ondisk_ce_size(ce); } return ce; } @@ -1821,13 +1856,13 @@ static void check_ce_order(struct index_state *istate) int name_compare = strcmp(ce->name, next_ce->name); if (0 < name_compare) - die("unordered stage entries in index"); + die(_("unordered stage entries in index")); if (!name_compare) { if (!ce_stage(ce)) - die("multiple stage entries for merged file '%s'", + die(_("multiple stage entries for merged file '%s'"), ce->name); if (ce_stage(ce) > ce_stage(next_ce)) - die("unordered stage entries for '%s'", + die(_("unordered stage entries for '%s'"), ce->name); } } @@ -1835,18 +1870,17 @@ static void check_ce_order(struct index_state *istate) static void tweak_untracked_cache(struct index_state *istate) { - switch (git_config_get_untracked_cache()) { - case -1: /* keep: do nothing */ - break; - case 0: /* false */ + struct repository *r = the_repository; + + prepare_repo_settings(r); + + if (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE) { remove_untracked_cache(istate); - break; - case 1: /* true */ - add_untracked_cache(istate); - break; - default: /* unknown value: do nothing */ - break; + return; } + + if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE) + add_untracked_cache(istate); } static void tweak_split_index(struct index_state *istate) @@ -1885,20 +1919,235 @@ static size_t estimate_cache_size(size_t ondisk_size, unsigned int entries) /* * Account for potential alignment differences. */ - per_entry += align_padding_size(sizeof(struct cache_entry), -sizeof(struct ondisk_cache_entry)); + per_entry += align_padding_size(per_entry, 0); return ondisk_size + entries * per_entry; } +struct index_entry_offset +{ + /* starting byte offset into index file, count of index entries in this block */ + int offset, nr; +}; + +struct index_entry_offset_table +{ + int nr; + struct index_entry_offset entries[FLEX_ARRAY]; +}; + +static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset); +static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot); + +static size_t read_eoie_extension(const char *mmap, size_t mmap_size); +static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, size_t offset); + +struct load_index_extensions +{ + pthread_t pthread; + struct index_state *istate; + const char *mmap; + size_t mmap_size; + unsigned long src_offset; +}; + +static void *load_index_extensions(void *_data) +{ + struct load_index_extensions *p = _data; + unsigned long src_offset = p->src_offset; + + while (src_offset <= p->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 + * extension name (4-byte) and section length + * in 4-byte network byte order. + */ + uint32_t extsize = get_be32(p->mmap + src_offset + 4); + if (read_index_extension(p->istate, + p->mmap + src_offset, + p->mmap + src_offset + 8, + extsize) < 0) { + munmap((void *)p->mmap, p->mmap_size); + die(_("index file corrupt")); + } + src_offset += 8; + src_offset += extsize; + } + + return NULL; +} + +/* + * A helper function that will load the specified range of cache entries + * from the memory mapped file and add them to the given index. + */ +static unsigned long load_cache_entry_block(struct index_state *istate, + struct mem_pool *ce_mem_pool, int offset, int nr, const char *mmap, + unsigned long start_offset, const struct cache_entry *previous_ce) +{ + int i; + unsigned long src_offset = start_offset; + + for (i = offset; i < offset + nr; i++) { + struct ondisk_cache_entry *disk_ce; + struct cache_entry *ce; + unsigned long consumed; + + disk_ce = (struct ondisk_cache_entry *)(mmap + src_offset); + ce = create_from_disk(ce_mem_pool, istate->version, disk_ce, &consumed, previous_ce); + set_index_entry(istate, i, ce); + + src_offset += consumed; + previous_ce = ce; + } + return src_offset - start_offset; +} + +static unsigned long load_all_cache_entries(struct index_state *istate, + const char *mmap, size_t mmap_size, unsigned long src_offset) +{ + unsigned long consumed; + + istate->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool)); + if (istate->version == 4) { + mem_pool_init(istate->ce_mem_pool, + estimate_cache_size_from_compressed(istate->cache_nr)); + } else { + mem_pool_init(istate->ce_mem_pool, + estimate_cache_size(mmap_size, istate->cache_nr)); + } + + consumed = load_cache_entry_block(istate, istate->ce_mem_pool, + 0, istate->cache_nr, mmap, src_offset, NULL); + return consumed; +} + +/* + * Mostly randomly chosen maximum thread counts: we + * cap the parallelism to online_cpus() threads, and we want + * to have at least 10000 cache entries per thread for it to + * be worth starting a thread. + */ + +#define THREAD_COST (10000) + +struct load_cache_entries_thread_data +{ + pthread_t pthread; + struct index_state *istate; + struct mem_pool *ce_mem_pool; + int offset; + const char *mmap; + struct index_entry_offset_table *ieot; + int ieot_start; /* starting index into the ieot array */ + int ieot_blocks; /* count of ieot entries to process */ + unsigned long consumed; /* return # of bytes in index file processed */ +}; + +/* + * A thread proc to run the load_cache_entries() computation + * across multiple background threads. + */ +static void *load_cache_entries_thread(void *_data) +{ + struct load_cache_entries_thread_data *p = _data; + int i; + + /* iterate across all ieot blocks assigned to this thread */ + for (i = p->ieot_start; i < p->ieot_start + p->ieot_blocks; i++) { + p->consumed += load_cache_entry_block(p->istate, p->ce_mem_pool, + p->offset, p->ieot->entries[i].nr, p->mmap, p->ieot->entries[i].offset, NULL); + p->offset += p->ieot->entries[i].nr; + } + return NULL; +} + +static unsigned long load_cache_entries_threaded(struct index_state *istate, const char *mmap, size_t mmap_size, + int nr_threads, struct index_entry_offset_table *ieot) +{ + int i, offset, ieot_blocks, ieot_start, err; + struct load_cache_entries_thread_data *data; + unsigned long consumed = 0; + + /* a little sanity checking */ + if (istate->name_hash_initialized) + BUG("the name hash isn't thread safe"); + + istate->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool)); + mem_pool_init(istate->ce_mem_pool, 0); + + /* ensure we have no more threads than we have blocks to process */ + if (nr_threads > ieot->nr) + nr_threads = ieot->nr; + data = xcalloc(nr_threads, sizeof(*data)); + + offset = ieot_start = 0; + ieot_blocks = DIV_ROUND_UP(ieot->nr, nr_threads); + for (i = 0; i < nr_threads; i++) { + struct load_cache_entries_thread_data *p = &data[i]; + int nr, j; + + if (ieot_start + ieot_blocks > ieot->nr) + ieot_blocks = ieot->nr - ieot_start; + + p->istate = istate; + p->offset = offset; + p->mmap = mmap; + p->ieot = ieot; + p->ieot_start = ieot_start; + p->ieot_blocks = ieot_blocks; + + /* create a mem_pool for each thread */ + nr = 0; + for (j = p->ieot_start; j < p->ieot_start + p->ieot_blocks; j++) + nr += p->ieot->entries[j].nr; + p->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool)); + if (istate->version == 4) { + mem_pool_init(p->ce_mem_pool, + estimate_cache_size_from_compressed(nr)); + } else { + mem_pool_init(p->ce_mem_pool, + estimate_cache_size(mmap_size, nr)); + } + + err = pthread_create(&p->pthread, NULL, load_cache_entries_thread, p); + if (err) + die(_("unable to create load_cache_entries thread: %s"), strerror(err)); + + /* increment by the number of cache entries in the ieot block being processed */ + for (j = 0; j < ieot_blocks; j++) + offset += ieot->entries[ieot_start + j].nr; + ieot_start += ieot_blocks; + } + + for (i = 0; i < nr_threads; i++) { + struct load_cache_entries_thread_data *p = &data[i]; + + err = pthread_join(p->pthread, NULL); + if (err) + die(_("unable to join load_cache_entries thread: %s"), strerror(err)); + mem_pool_combine(istate->ce_mem_pool, p->ce_mem_pool); + consumed += p->consumed; + } + + free(data); + + return consumed; +} + /* remember to discard_cache() before reading a different cache! */ int do_read_index(struct index_state *istate, const char *path, int must_exist) { - int fd, i; + int fd; struct stat st; unsigned long src_offset; - struct cache_header *hdr; - void *mmap; + const struct cache_header *hdr; + const char *mmap; size_t mmap_size; - struct strbuf previous_name_buf = STRBUF_INIT, *previous_name; + struct load_index_extensions p; + size_t extension_offset = 0; + int nr_threads, cpus; + struct index_entry_offset_table *ieot = NULL; if (istate->initialized) return istate->cache_nr; @@ -1909,22 +2158,22 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) if (fd < 0) { if (!must_exist && errno == ENOENT) return 0; - die_errno("%s: index file open failed", path); + die_errno(_("%s: index file open failed"), path); } if (fstat(fd, &st)) - die_errno("cannot stat the open index"); + die_errno(_("%s: cannot stat the open index"), path); mmap_size = xsize_t(st.st_size); if (mmap_size < sizeof(struct cache_header) + the_hash_algo->rawsz) - die("index file smaller than expected"); + die(_("%s: index file smaller than expected"), path); - mmap = xmmap(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0); + mmap = xmmap_gently(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0); if (mmap == MAP_FAILED) - die_errno("unable to map index file"); + die_errno(_("%s: unable to map index file"), path); close(fd); - hdr = mmap; + hdr = (const struct cache_header *)mmap; if (verify_hdr(hdr, mmap_size) < 0) goto unmap; @@ -1935,56 +2184,82 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) istate->cache = xcalloc(istate->cache_alloc, sizeof(*istate->cache)); istate->initialized = 1; - if (istate->version == 4) { - previous_name = &previous_name_buf; - 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)); - } + p.istate = istate; + p.mmap = mmap; + p.mmap_size = mmap_size; src_offset = sizeof(*hdr); - for (i = 0; i < istate->cache_nr; i++) { - struct ondisk_cache_entry *disk_ce; - struct cache_entry *ce; - unsigned long consumed; - disk_ce = (struct ondisk_cache_entry *)((char *)mmap + src_offset); - ce = create_from_disk(istate->ce_mem_pool, disk_ce, &consumed, previous_name); - set_index_entry(istate, i, ce); + if (git_config_get_index_threads(&nr_threads)) + nr_threads = 1; - src_offset += consumed; + /* TODO: does creating more threads than cores help? */ + if (!nr_threads) { + nr_threads = istate->cache_nr / THREAD_COST; + cpus = online_cpus(); + if (nr_threads > cpus) + nr_threads = cpus; } - strbuf_release(&previous_name_buf); + + if (!HAVE_THREADS) + nr_threads = 1; + + if (nr_threads > 1) { + extension_offset = read_eoie_extension(mmap, mmap_size); + if (extension_offset) { + int err; + + p.src_offset = extension_offset; + err = pthread_create(&p.pthread, NULL, load_index_extensions, &p); + if (err) + die(_("unable to create load_index_extensions thread: %s"), strerror(err)); + + nr_threads--; + } + } + + /* + * Locate and read the index entry offset table so that we can use it + * to multi-thread the reading of the cache entries. + */ + if (extension_offset && nr_threads > 1) + ieot = read_ieot_extension(mmap, mmap_size, extension_offset); + + if (ieot) { + src_offset += load_cache_entries_threaded(istate, mmap, mmap_size, nr_threads, ieot); + free(ieot); + } else { + src_offset += load_all_cache_entries(istate, mmap, mmap_size, src_offset); + } + istate->timestamp.sec = st.st_mtime; istate->timestamp.nsec = ST_MTIME_NSEC(st); - 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 - * extension name (4-byte) and section length - * in 4-byte network byte order. - */ - uint32_t extsize; - memcpy(&extsize, (char *)mmap + src_offset + 4, 4); - extsize = ntohl(extsize); - if (read_index_extension(istate, - (const char *) mmap + src_offset, - (char *) mmap + src_offset + 8, - extsize) < 0) - goto unmap; - src_offset += 8; - src_offset += extsize; + /* if we created a thread, join it otherwise load the extensions on the primary thread */ + if (extension_offset) { + int ret = pthread_join(p.pthread, NULL); + if (ret) + die(_("unable to join load_index_extensions thread: %s"), strerror(ret)); + } else { + p.src_offset = src_offset; + load_index_extensions(&p); } - munmap(mmap, mmap_size); + munmap((void *)mmap, mmap_size); + + /* + * TODO trace2: replace "the_repository" with the actual repo instance + * that is associated with the given "istate". + */ + trace2_data_intmax("index", the_repository, "read/version", + istate->version); + trace2_data_intmax("index", the_repository, "read/cache_nr", + istate->cache_nr); + return istate->cache_nr; unmap: - munmap(mmap, mmap_size); - die("index file corrupt"); + munmap((void *)mmap, mmap_size); + die(_("index file corrupt")); } /* @@ -1996,13 +2271,12 @@ unmap: 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); + warning(_("could not freshen shared index '%s'"), shared_index); } int read_index_from(struct index_state *istate, const char *path, const char *gitdir) { - uint64_t start = getnanotime(); struct split_index *split_index; int ret; char *base_oid_hex; @@ -2012,8 +2286,17 @@ int read_index_from(struct index_state *istate, const char *path, if (istate->initialized) return istate->cache_nr; + /* + * TODO trace2: replace "the_repository" with the actual repo instance + * that is associated with the given "istate". + */ + trace2_region_enter_printf("index", "do_read_index", the_repository, + "%s", path); + trace_performance_enter(); ret = do_read_index(istate, path, 0); - trace_performance_since(start, "read cache %s", path); + trace_performance_leave("read cache %s", path); + trace2_region_leave_printf("index", "do_read_index", the_repository, + "%s", path); split_index = istate->split_index; if (!split_index || is_null_oid(&split_index->base_oid)) { @@ -2021,6 +2304,7 @@ int read_index_from(struct index_state *istate, const char *path, return ret; } + trace_performance_enter(); if (split_index->base) discard_index(split_index->base); else @@ -2028,16 +2312,20 @@ int read_index_from(struct index_state *istate, const char *path, base_oid_hex = oid_to_hex(&split_index->base_oid); base_path = xstrfmt("%s/sharedindex.%s", gitdir, base_oid_hex); + trace2_region_enter_printf("index", "shared/do_read_index", + the_repository, "%s", base_path); ret = do_read_index(split_index->base, base_path, 1); - if (oidcmp(&split_index->base_oid, &split_index->base->oid)) - die("broken index, expect %s in %s, got %s", + trace2_region_leave_printf("index", "shared/do_read_index", + the_repository, "%s", base_path); + if (!oideq(&split_index->base_oid, &split_index->base->oid)) + die(_("broken index, expect %s in %s, got %s"), 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); - trace_performance_since(start, "read cache %s", base_path); + trace_performance_leave("read cache %s", base_path); free(base_path); return ret; } @@ -2066,6 +2354,7 @@ int discard_index(struct index_state *istate) free_name_hash(istate); cache_tree_free(&(istate->cache_tree)); istate->initialized = 0; + istate->fsmonitor_has_run_once = 0; FREE_AND_NULL(istate->cache); istate->cache_alloc = 0; discard_split_index(istate); @@ -2074,7 +2363,7 @@ int discard_index(struct index_state *istate) if (istate->ce_mem_pool) { mem_pool_discard(istate->ce_mem_pool, should_validate_cache_entries()); - istate->ce_mem_pool = NULL; + FREE_AND_NULL(istate->ce_mem_pool); } return 0; @@ -2096,14 +2385,14 @@ void validate_cache_entries(const struct index_state *istate) for (i = 0; i < istate->cache_nr; i++) { if (!istate) { - die("internal error: cache entry is not allocated from expected memory pool"); + BUG("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"); + BUG("cache entry is not allocated from expected memory pool"); } } } @@ -2122,22 +2411,20 @@ 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) +int repo_index_has_changes(struct repository *repo, + struct tree *tree, + struct strbuf *sb) { + struct index_state *istate = repo->index; 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); + repo_diff_setup(repo, &opt); opt.flags.exit_with_status = 1; if (!sb) opt.flags.quick = 1; @@ -2198,11 +2485,15 @@ static int ce_write(git_hash_ctx *context, int fd, void *data, unsigned int len) return 0; } -static int write_index_ext_header(git_hash_ctx *context, int fd, - unsigned int ext, unsigned int sz) +static int write_index_ext_header(git_hash_ctx *context, git_hash_ctx *eoie_context, + int fd, unsigned int ext, unsigned int sz) { ext = htonl(ext); sz = htonl(sz); + if (eoie_context) { + the_hash_algo->update_fn(eoie_context, &ext, 4); + the_hash_algo->update_fn(eoie_context, &sz, 4); + } return ((ce_write(context, fd, &ext, 4) < 0) || (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0; } @@ -2230,7 +2521,8 @@ static int ce_flush(git_hash_ctx *context, int fd, unsigned char *hash) return (write_in_full(fd, write_buffer, left) < 0) ? -1 : 0; } -static void ce_smudge_racily_clean_entry(struct cache_entry *ce) +static void ce_smudge_racily_clean_entry(struct index_state *istate, + struct cache_entry *ce) { /* * The only thing we care about in this function is to smudge the @@ -2249,7 +2541,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce) return; if (ce_match_stat_basic(ce, &st)) return; - if (ce_modified_check_fs(ce, &st)) { + if (ce_modified_check_fs(istate, ce, &st)) { /* This is "racily clean"; smudge it. Note that this * is a tricky code. At first glance, it may appear * that it can break with this sequence: @@ -2284,6 +2576,8 @@ static void copy_cache_entry_to_ondisk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce) { short flags; + const unsigned hashsz = the_hash_algo->rawsz; + uint16_t *flagsp = (uint16_t *)(ondisk->data + hashsz); ondisk->ctime.sec = htonl(ce->ce_stat_data.sd_ctime.sec); ondisk->mtime.sec = htonl(ce->ce_stat_data.sd_mtime.sec); @@ -2295,15 +2589,13 @@ static void 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->oid.hash); + hashcpy(ondisk->data, ce->oid.hash); flags = ce->ce_flags & ~CE_NAMEMASK; flags |= (ce_namelen(ce) >= CE_NAMEMASK ? CE_NAMEMASK : ce_namelen(ce)); - ondisk->flags = htons(flags); + flagsp[0] = htons(flags); if (ce->ce_flags & CE_EXTENDED) { - struct ondisk_cache_entry_extended *ondisk2; - ondisk2 = (struct ondisk_cache_entry_extended *)ondisk; - ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16); + flagsp[1] = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16); } } @@ -2322,10 +2614,7 @@ static int ce_write_entry(git_hash_ctx *c, int fd, struct cache_entry *ce, 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); + size = offsetof(struct ondisk_cache_entry,data) + ondisk_data_size(ce->ce_flags, 0); if (!previous_name) { int len = ce_namelen(ce); @@ -2395,7 +2684,7 @@ static int verify_index_from(const struct index_state *istate, const char *path) if (n != the_hash_algo->rawsz) goto out; - if (hashcmp(istate->oid.hash, hash)) + if (!hasheq(istate->oid.hash, hash)) goto out; close(fd); @@ -2406,9 +2695,9 @@ out: return 0; } -static int verify_index(const struct index_state *istate) +static int repo_verify_index(struct repository *repo) { - return verify_index_from(istate, get_index_file()); + return verify_index_from(repo->index, repo->index_file); } static int has_racy_timestamp(struct index_state *istate) @@ -2424,15 +2713,47 @@ static int has_racy_timestamp(struct index_state *istate) return 0; } -void update_index_if_able(struct index_state *istate, struct lock_file *lockfile) +void repo_update_index_if_able(struct repository *repo, + struct lock_file *lockfile) { - if ((istate->cache_changed || has_racy_timestamp(istate)) && - verify_index(istate)) - write_locked_index(istate, lockfile, COMMIT_LOCK); + if ((repo->index->cache_changed || + has_racy_timestamp(repo->index)) && + repo_verify_index(repo)) + write_locked_index(repo->index, lockfile, COMMIT_LOCK); else rollback_lock_file(lockfile); } +static int record_eoie(void) +{ + int val; + + if (!git_config_get_bool("index.recordendofindexentries", &val)) + return val; + + /* + * As a convenience, the end of index entries extension + * used for threading is written by default if the user + * explicitly requested threaded index reads. + */ + return !git_config_get_index_threads(&val) && val != 1; +} + +static int record_ieot(void) +{ + int val; + + if (!git_config_get_bool("index.recordoffsettable", &val)) + return val; + + /* + * As a convenience, the offset table used for threading is + * written by default if the user explicitly requested + * threaded index reads. + */ + return !git_config_get_index_threads(&val) && val != 1; +} + /* * On success, `tempfile` is closed. If it is the temporary file * of a `struct lock_file`, we will therefore effectively perform @@ -2445,15 +2766,19 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, { uint64_t start = getnanotime(); int newfd = tempfile->fd; - git_hash_ctx c; + git_hash_ctx c, eoie_c; struct cache_header hdr; 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 ondisk_cache_entry ondisk; struct strbuf previous_name_buf = STRBUF_INIT, *previous_name; int drop_cache_tree = istate->drop_cache_tree; + off_t offset; + int ieot_entries = 1; + struct index_entry_offset_table *ieot = NULL; + int nr, nr_threads; for (i = removed = extended = 0; i < entries; i++) { if (cache[i]->ce_flags & CE_REMOVE) @@ -2468,7 +2793,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, } if (!istate->version) { - istate->version = get_index_format_default(); + istate->version = get_index_format_default(the_repository); if (git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) init_split_index(istate); } @@ -2487,6 +2812,46 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0) return -1; + if (!HAVE_THREADS || git_config_get_index_threads(&nr_threads)) + nr_threads = 1; + + if (nr_threads != 1 && record_ieot()) { + int ieot_blocks, cpus; + + /* + * ensure default number of ieot blocks maps evenly to the + * default number of threads that will process them leaving + * room for the thread to load the index extensions. + */ + if (!nr_threads) { + ieot_blocks = istate->cache_nr / THREAD_COST; + cpus = online_cpus(); + if (ieot_blocks > cpus - 1) + ieot_blocks = cpus - 1; + } else { + ieot_blocks = nr_threads; + if (ieot_blocks > istate->cache_nr) + ieot_blocks = istate->cache_nr; + } + + /* + * no reason to write out the IEOT extension if we don't + * have enough blocks to utilize multi-threading + */ + if (ieot_blocks > 1) { + ieot = xcalloc(1, sizeof(struct index_entry_offset_table) + + (ieot_blocks * sizeof(struct index_entry_offset))); + ieot_entries = DIV_ROUND_UP(entries, ieot_blocks); + } + } + + offset = lseek(newfd, 0, SEEK_CUR); + if (offset < 0) { + free(ieot); + return -1; + } + offset += write_buffer_len; + nr = 0; previous_name = (hdr_version == 4) ? &previous_name_buf : NULL; for (i = 0; i < entries; i++) { @@ -2494,7 +2859,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (ce->ce_flags & CE_REMOVE) continue; if (!ce_uptodate(ce) && is_racy_timestamp(istate, ce)) - ce_smudge_racily_clean_entry(ce); + ce_smudge_racily_clean_entry(istate, ce); if (is_null_oid(&ce->oid)) { static const char msg[] = "cache entry has null sha1: %s"; static int allow = -1; @@ -2508,23 +2873,78 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, drop_cache_tree = 1; } + if (ieot && i && (i % ieot_entries == 0)) { + ieot->entries[ieot->nr].nr = nr; + ieot->entries[ieot->nr].offset = offset; + ieot->nr++; + /* + * If we have a V4 index, set the first byte to an invalid + * character to ensure there is nothing common with the previous + * entry + */ + if (previous_name) + previous_name->buf[0] = 0; + nr = 0; + offset = lseek(newfd, 0, SEEK_CUR); + if (offset < 0) { + free(ieot); + return -1; + } + offset += write_buffer_len; + } if (ce_write_entry(&c, newfd, ce, previous_name, (struct ondisk_cache_entry *)&ondisk) < 0) err = -1; if (err) break; + nr++; + } + if (ieot && nr) { + ieot->entries[ieot->nr].nr = nr; + ieot->entries[ieot->nr].offset = offset; + ieot->nr++; } strbuf_release(&previous_name_buf); - if (err) + if (err) { + free(ieot); return err; + } /* Write extension data here */ - if (!strip_extensions && istate->split_index) { + offset = lseek(newfd, 0, SEEK_CUR); + if (offset < 0) { + free(ieot); + return -1; + } + offset += write_buffer_len; + the_hash_algo->init_fn(&eoie_c); + + /* + * Lets write out CACHE_EXT_INDEXENTRYOFFSETTABLE first so that we + * can minimize the number of extensions we have to scan through to + * find it during load. Write it out regardless of the + * strip_extensions parameter as we need it when loading the shared + * index. + */ + if (ieot) { + struct strbuf sb = STRBUF_INIT; + + write_ieot_extension(&sb, ieot); + err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_INDEXENTRYOFFSETTABLE, sb.len) < 0 + || ce_write(&c, newfd, sb.buf, sb.len) < 0; + strbuf_release(&sb); + free(ieot); + if (err) + return -1; + } + + if (!strip_extensions && istate->split_index && + !is_null_oid(&istate->split_index->base_oid)) { struct strbuf sb = STRBUF_INIT; err = write_link_extension(&sb, istate) < 0 || - write_index_ext_header(&c, newfd, CACHE_EXT_LINK, + write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_LINK, sb.len) < 0 || ce_write(&c, newfd, sb.buf, sb.len) < 0; strbuf_release(&sb); @@ -2535,7 +2955,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, struct strbuf sb = STRBUF_INIT; cache_tree_write(&sb, istate->cache_tree); - err = write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sb.len) < 0 + err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_TREE, sb.len) < 0 || ce_write(&c, newfd, sb.buf, sb.len) < 0; strbuf_release(&sb); if (err) @@ -2545,7 +2965,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, struct strbuf sb = STRBUF_INIT; resolve_undo_write(&sb, istate->resolve_undo); - err = write_index_ext_header(&c, newfd, CACHE_EXT_RESOLVE_UNDO, + err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_RESOLVE_UNDO, sb.len) < 0 || ce_write(&c, newfd, sb.buf, sb.len) < 0; strbuf_release(&sb); @@ -2556,7 +2976,7 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, struct strbuf sb = STRBUF_INIT; write_untracked_extension(&sb, istate->untracked); - err = write_index_ext_header(&c, newfd, CACHE_EXT_UNTRACKED, + err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_UNTRACKED, sb.len) < 0 || ce_write(&c, newfd, sb.buf, sb.len) < 0; strbuf_release(&sb); @@ -2567,7 +2987,24 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, struct strbuf sb = STRBUF_INIT; write_fsmonitor_extension(&sb, istate); - err = write_index_ext_header(&c, newfd, CACHE_EXT_FSMONITOR, sb.len) < 0 + err = write_index_ext_header(&c, &eoie_c, newfd, CACHE_EXT_FSMONITOR, sb.len) < 0 + || ce_write(&c, newfd, sb.buf, sb.len) < 0; + strbuf_release(&sb); + if (err) + return -1; + } + + /* + * CACHE_EXT_ENDOFINDEXENTRIES must be written as the last entry before the SHA1 + * so that it can be found and processed before all the index entries are + * read. Write it out regardless of the strip_extensions parameter as we need it + * when loading the shared index. + */ + if (offset && record_eoie()) { + struct strbuf sb = STRBUF_INIT; + + write_eoie_extension(&sb, &eoie_c, offset); + err = write_index_ext_header(&c, NULL, newfd, CACHE_EXT_ENDOFINDEXENTRIES, sb.len) < 0 || ce_write(&c, newfd, sb.buf, sb.len) < 0; strbuf_release(&sb); if (err) @@ -2577,14 +3014,24 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, if (ce_flush(&c, newfd, istate->oid.hash)) return -1; if (close_tempfile_gently(tempfile)) { - error(_("could not close '%s'"), tempfile->filename.buf); + error(_("could not close '%s'"), get_tempfile_path(tempfile)); return -1; } - if (stat(tempfile->filename.buf, &st)) + if (stat(get_tempfile_path(tempfile), &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); + + /* + * TODO trace2: replace "the_repository" with the actual repo instance + * that is associated with the given "istate". + */ + trace2_data_intmax("index", the_repository, "write/version", + istate->version); + trace2_data_intmax("index", the_repository, "write/cache_nr", + istate->cache_nr); + return 0; } @@ -2604,12 +3051,32 @@ 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, lock->tempfile, 0); + int ret; + + /* + * TODO trace2: replace "the_repository" with the actual repo instance + * that is associated with the given "istate". + */ + trace2_region_enter_printf("index", "do_write_index", the_repository, + "%s", get_lock_file_path(lock)); + ret = do_write_index(istate, lock->tempfile, 0); + trace2_region_leave_printf("index", "do_write_index", the_repository, + "%s", get_lock_file_path(lock)); + if (ret) return ret; if (flags & COMMIT_LOCK) - return commit_locked_index(lock); - return close_lock_file_gently(lock); + ret = commit_locked_index(lock); + else + ret = close_lock_file_gently(lock); + + run_hook_le(NULL, "post-index-change", + istate->updated_workdir ? "1" : "0", + istate->updated_skipworktree ? "1" : "0", NULL); + istate->updated_workdir = 0; + istate->updated_skipworktree = 0; + + return ret; } static int write_split_index(struct index_state *istate, @@ -2689,12 +3156,18 @@ static int write_shared_index(struct index_state *istate, int ret; move_cache_to_base_index(istate); + + trace2_region_enter_printf("index", "shared/do_write_index", + the_repository, "%s", get_tempfile_path(*temp)); ret = do_write_index(si->base, *temp, 1); + trace2_region_leave_printf("index", "shared/do_write_index", + the_repository, "%s", get_tempfile_path(*temp)); + if (ret) return ret; ret = adjust_shared_perm(get_tempfile_path(*temp)); if (ret) { - error("cannot fix permission bits on %s", get_tempfile_path(*temp)); + error(_("cannot fix permission bits on '%s'"), get_tempfile_path(*temp)); return ret; } ret = rename_tempfile(temp, @@ -2743,6 +3216,9 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, 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(the_repository, istate); + if ((flags & SKIP_IF_UNCHANGED) && !istate->cache_changed) { if (flags & COMMIT_LOCK) rollback_lock_file(lock); @@ -2774,7 +3250,8 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, struct tempfile *temp; int saved_errno; - temp = mks_tempfile(git_path("sharedindex_XXXXXX")); + /* Same initial permissions as the main .git/index file */ + temp = mks_tempfile_sm(git_path("sharedindex_XXXXXX"), 0, 0666); if (!temp) { oidclr(&si->base_oid); ret = do_write_locked_index(istate, lock, flags); @@ -2794,7 +3271,7 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock, ret = write_split_index(istate, lock, flags); /* Freshen the shared index only if the split-index was written */ - if (!ret && !new_shared_index) { + if (!ret && !new_shared_index && !is_null_oid(&si->base_oid)) { const char *shared_index = git_path("sharedindex.%s", oid_to_hex(&si->base_oid)); freshen_shared_index(shared_index, 1); @@ -2816,12 +3293,14 @@ out: * state can call this and check its return value, instead of calling * read_cache(). */ -int read_index_unmerged(struct index_state *istate) +int repo_read_index_unmerged(struct repository *repo) { + struct index_state *istate; int i; int unmerged = 0; - read_index(istate); + repo_read_index(repo); + istate = repo->index; for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce = istate->cache[i]; struct cache_entry *new_ce; @@ -2837,7 +3316,7 @@ int read_index_unmerged(struct index_state *istate) new_ce->ce_namelen = len; new_ce->ce_mode = ce->ce_mode; if (add_index_entry(istate, new_ce, ADD_CACHE_SKIP_DFCHECK)) - return error("%s: cannot drop to stage #0", + return error(_("%s: cannot drop to stage #0"), new_ce->name); } return unmerged; @@ -2939,6 +3418,8 @@ 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, @@ -2978,3 +3459,179 @@ int should_validate_cache_entries(void) return validate_index_cache_entries; } + +#define EOIE_SIZE (4 + GIT_SHA1_RAWSZ) /* <4-byte offset> + <20-byte hash> */ +#define EOIE_SIZE_WITH_HEADER (4 + 4 + EOIE_SIZE) /* <4-byte signature> + <4-byte length> + EOIE_SIZE */ + +static size_t read_eoie_extension(const char *mmap, size_t mmap_size) +{ + /* + * The end of index entries (EOIE) extension is guaranteed to be last + * so that it can be found by scanning backwards from the EOF. + * + * "EOIE" + * <4-byte length> + * <4-byte offset> + * <20-byte hash> + */ + const char *index, *eoie; + uint32_t extsize; + size_t offset, src_offset; + unsigned char hash[GIT_MAX_RAWSZ]; + git_hash_ctx c; + + /* ensure we have an index big enough to contain an EOIE extension */ + if (mmap_size < sizeof(struct cache_header) + EOIE_SIZE_WITH_HEADER + the_hash_algo->rawsz) + return 0; + + /* validate the extension signature */ + index = eoie = mmap + mmap_size - EOIE_SIZE_WITH_HEADER - the_hash_algo->rawsz; + if (CACHE_EXT(index) != CACHE_EXT_ENDOFINDEXENTRIES) + return 0; + index += sizeof(uint32_t); + + /* validate the extension size */ + extsize = get_be32(index); + if (extsize != EOIE_SIZE) + return 0; + index += sizeof(uint32_t); + + /* + * Validate the offset we're going to look for the first extension + * signature is after the index header and before the eoie extension. + */ + offset = get_be32(index); + if (mmap + offset < mmap + sizeof(struct cache_header)) + return 0; + if (mmap + offset >= eoie) + return 0; + index += sizeof(uint32_t); + + /* + * The hash is computed over extension types and their sizes (but not + * their contents). E.g. if we have "TREE" extension that is N-bytes + * long, "REUC" extension that is M-bytes long, followed by "EOIE", + * then the hash would be: + * + * SHA-1("TREE" + + + * "REUC" + ) + */ + src_offset = offset; + the_hash_algo->init_fn(&c); + while (src_offset < mmap_size - the_hash_algo->rawsz - EOIE_SIZE_WITH_HEADER) { + /* After an array of active_nr index entries, + * there can be arbitrary number of extended + * sections, each of which is prefixed with + * extension name (4-byte) and section length + * in 4-byte network byte order. + */ + uint32_t extsize; + memcpy(&extsize, mmap + src_offset + 4, 4); + extsize = ntohl(extsize); + + /* verify the extension size isn't so large it will wrap around */ + if (src_offset + 8 + extsize < src_offset) + return 0; + + the_hash_algo->update_fn(&c, mmap + src_offset, 8); + + src_offset += 8; + src_offset += extsize; + } + the_hash_algo->final_fn(hash, &c); + if (!hasheq(hash, (const unsigned char *)index)) + return 0; + + /* Validate that the extension offsets returned us back to the eoie extension. */ + if (src_offset != mmap_size - the_hash_algo->rawsz - EOIE_SIZE_WITH_HEADER) + return 0; + + return offset; +} + +static void write_eoie_extension(struct strbuf *sb, git_hash_ctx *eoie_context, size_t offset) +{ + uint32_t buffer; + unsigned char hash[GIT_MAX_RAWSZ]; + + /* offset */ + put_be32(&buffer, offset); + strbuf_add(sb, &buffer, sizeof(uint32_t)); + + /* hash */ + the_hash_algo->final_fn(hash, eoie_context); + strbuf_add(sb, hash, the_hash_algo->rawsz); +} + +#define IEOT_VERSION (1) + +static struct index_entry_offset_table *read_ieot_extension(const char *mmap, size_t mmap_size, size_t offset) +{ + const char *index = NULL; + uint32_t extsize, ext_version; + struct index_entry_offset_table *ieot; + int i, nr; + + /* find the IEOT extension */ + if (!offset) + return NULL; + while (offset <= mmap_size - the_hash_algo->rawsz - 8) { + extsize = get_be32(mmap + offset + 4); + if (CACHE_EXT((mmap + offset)) == CACHE_EXT_INDEXENTRYOFFSETTABLE) { + index = mmap + offset + 4 + 4; + break; + } + offset += 8; + offset += extsize; + } + if (!index) + return NULL; + + /* validate the version is IEOT_VERSION */ + ext_version = get_be32(index); + if (ext_version != IEOT_VERSION) { + error("invalid IEOT version %d", ext_version); + return NULL; + } + index += sizeof(uint32_t); + + /* extension size - version bytes / bytes per entry */ + nr = (extsize - sizeof(uint32_t)) / (sizeof(uint32_t) + sizeof(uint32_t)); + if (!nr) { + error("invalid number of IEOT entries %d", nr); + return NULL; + } + ieot = xmalloc(sizeof(struct index_entry_offset_table) + + (nr * sizeof(struct index_entry_offset))); + ieot->nr = nr; + for (i = 0; i < nr; i++) { + ieot->entries[i].offset = get_be32(index); + index += sizeof(uint32_t); + ieot->entries[i].nr = get_be32(index); + index += sizeof(uint32_t); + } + + return ieot; +} + +static void write_ieot_extension(struct strbuf *sb, struct index_entry_offset_table *ieot) +{ + uint32_t buffer; + int i; + + /* version */ + put_be32(&buffer, IEOT_VERSION); + strbuf_add(sb, &buffer, sizeof(uint32_t)); + + /* ieot */ + for (i = 0; i < ieot->nr; i++) { + + /* offset */ + put_be32(&buffer, ieot->entries[i].offset); + strbuf_add(sb, &buffer, sizeof(uint32_t)); + + /* count */ + put_be32(&buffer, ieot->entries[i].nr); + strbuf_add(sb, &buffer, sizeof(uint32_t)); + } +}