Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Documentation/git-update-index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,25 @@ you will need to handle the situation manually.
Like `--refresh`, but checks stat information unconditionally,
without regard to the "assume unchanged" setting.

--refresh-stat-only::
Like `--refresh`, but updates only the stat information
in the index, without rehashing the file contents. This is
useful for large repositories after a working tree has been
produced or restored by means other than a normal checkout --
for example, a CI cache restore, container provisioning, or
copying a working tree from another machine -- when the file
contents are known to be correct but the cached stat
information no longer matches. Some backup and syncing tools
preserve mtimes, but inode numbers, device identifiers, and
other filesystem-specific stat fields generally cannot be
preserved across machines or even across mounts on the same
machine. Like `--really-refresh`, this option disregards the
"assume unchanged" setting so that stale stat data on those
entries is still updated. Use with care: if the worktree
content does not actually match what the index records, the
affected entries will appear clean while the recorded object
ID remains stale.

--skip-worktree::
--no-skip-worktree::
When one of these flags is specified, the object names recorded
Expand Down
12 changes: 12 additions & 0 deletions builtin/update-index.c
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,14 @@ static int really_refresh_callback(const struct option *opt,
return refresh(opt->value, REFRESH_REALLY);
}

static int refresh_stat_only_callback(const struct option *opt,
const char *arg, int unset)
{
BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg);
return refresh(opt->value, REFRESH_STAT_ONLY);
}

static int chmod_callback(const struct option *opt,
const char *arg, int unset)
{
Expand Down Expand Up @@ -957,6 +965,10 @@ int cmd_update_index(int argc,
N_("like --refresh, but ignore assume-unchanged setting"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
really_refresh_callback),
OPT_CALLBACK_F(0, "refresh-stat-only", &refresh_args, NULL,
N_("refresh stat information without checking content"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG,
refresh_stat_only_callback),
{
.type = OPTION_LOWLEVEL_CALLBACK,
.long_name = "cacheinfo",
Expand Down
7 changes: 6 additions & 1 deletion preload-index.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ struct thread_data {
struct progress_data *progress;
int offset, nr;
int t2_nr_lstat;
unsigned int refresh_flags;
};

static void *preload_thread(void *_data)
Expand All @@ -60,6 +61,7 @@ static void *preload_thread(void *_data)
do {
struct cache_entry *ce = *cep++;
struct stat st;
unsigned int ce_option = CE_MATCH_RACY_IS_DIRTY | CE_MATCH_IGNORE_FSMONITOR;

if (ce_stage(ce))
continue;
Expand Down Expand Up @@ -87,7 +89,9 @@ static void *preload_thread(void *_data)
p->t2_nr_lstat++;
if (lstat(ce->name, &st))
continue;
if (ie_match_stat(index, ce, &st, CE_MATCH_RACY_IS_DIRTY|CE_MATCH_IGNORE_FSMONITOR))
if (p->refresh_flags & (REFRESH_REALLY | REFRESH_STAT_ONLY))
ce_option |= CE_MATCH_IGNORE_VALID;
if (ie_match_stat(index, ce, &st, ce_option))
continue;
ce_mark_uptodate(ce);
mark_fsmonitor_valid(index, ce);
Expand Down Expand Up @@ -150,6 +154,7 @@ void preload_index(struct index_state *index,
copy_pathspec(&p->pathspec, pathspec);
p->offset = offset;
p->nr = work;
p->refresh_flags = refresh_flags;
if (pd.progress)
p->progress = &pd;
offset += work;
Expand Down
3 changes: 3 additions & 0 deletions read-cache-ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ void *read_blob_data_from_index(struct index_state *, const char *, unsigned lon
#define CE_MATCH_REFRESH 0x10
/* don't refresh_fsmonitor state or do stat comparison even if CE_FSMONITOR_VALID is true */
#define CE_MATCH_IGNORE_FSMONITOR 0X20
/* update stat info without checking content */
#define CE_MATCH_STAT_ONLY 0x40
int is_racy_timestamp(const struct index_state *istate,
const struct cache_entry *ce);
int has_racy_timestamp(struct index_state *istate);
Expand Down Expand Up @@ -452,6 +454,7 @@ int fake_lstat(const struct cache_entry *ce, struct stat *st);
#define REFRESH_IN_PORCELAIN (1 << 5) /* user friendly output, not "needs update" */
#define REFRESH_PROGRESS (1 << 6) /* show progress bar if stderr is tty */
#define REFRESH_IGNORE_SKIP_WORKTREE (1 << 7) /* ignore skip_worktree entries */
#define REFRESH_STAT_ONLY (1 << 8) /* update stat info without checking content */
int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
/*
* Refresh the index and write it to disk.
Expand Down
24 changes: 15 additions & 9 deletions read-cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
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;
int stat_only = options & CE_MATCH_STAT_ONLY;

if (!refresh || ce_uptodate(ce))
return ce;
Expand Down Expand Up @@ -1420,12 +1421,14 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
}
}

if (t2_did_scan)
*t2_did_scan = 1;
if (ie_modified(istate, ce, &st, options)) {
if (err)
*err = EINVAL;
return NULL;
if (!stat_only) {
if (t2_did_scan)
*t2_did_scan = 1;
if (ie_modified(istate, ce, &st, options)) {
if (err)
*err = EINVAL;
return NULL;
}
}

updated = make_empty_cache_entry(istate, ce_namelen(ce));
Expand Down Expand Up @@ -1490,11 +1493,14 @@ int refresh_index(struct index_state *istate, unsigned int flags,
int not_new = (flags & REFRESH_IGNORE_MISSING) != 0;
int ignore_submodules = (flags & REFRESH_IGNORE_SUBMODULES) != 0;
int ignore_skip_worktree = (flags & REFRESH_IGNORE_SKIP_WORKTREE) != 0;
int stat_only = (flags & REFRESH_STAT_ONLY) != 0;
int first = 1;
int in_porcelain = (flags & REFRESH_IN_PORCELAIN);
unsigned int options = (CE_MATCH_REFRESH |
(really ? CE_MATCH_IGNORE_VALID : 0) |
(not_new ? CE_MATCH_IGNORE_MISSING : 0));
((really || stat_only) ? CE_MATCH_IGNORE_VALID : 0) |
(not_new ? CE_MATCH_IGNORE_MISSING : 0) |
(stat_only ? (CE_MATCH_STAT_ONLY |
CE_MATCH_RACY_IS_DIRTY) : 0));
const char *modified_fmt;
const char *deleted_fmt;
const char *typechange_fmt;
Expand All @@ -1520,7 +1526,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
* 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);
preload_index(istate, pathspec, flags & (REFRESH_REALLY | REFRESH_STAT_ONLY));
trace2_region_enter("index", "refresh", NULL);

for (i = 0; i < istate->cache_nr; i++) {
Expand Down
1 change: 1 addition & 0 deletions t/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ integration_tests = [
't2106-update-index-assume-unchanged.sh',
't2107-update-index-basic.sh',
't2108-update-index-refresh-racy.sh',
't2109-update-index-refresh-stat-only.sh',
't2200-add-update.sh',
't2201-add-update-typechange.sh',
't2202-add-addremove.sh',
Expand Down
11 changes: 11 additions & 0 deletions t/t2106-update-index-assume-unchanged.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,15 @@ test_expect_success 'do not switch branches with dirty file' '
test_grep overwritten err
'

test_expect_success '--really-refresh overrides assume-unchanged under preload' '
git reset --hard &&
test_commit really-refresh really-refresh original &&
git update-index --assume-unchanged really-refresh &&
printf "modified\n" >really-refresh &&
test-tool chmtime -100000 really-refresh &&
test_must_fail env GIT_TEST_PRELOAD_INDEX=1 \
git update-index --really-refresh >out 2>err &&
test_grep "needs update" out
'

test_done
59 changes: 59 additions & 0 deletions t/t2109-update-index-refresh-stat-only.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/bin/sh

test_description='git update-index --refresh-stat-only'

. ./test-lib.sh

test_expect_success 'setup' '
test_commit initial base-file base
'

test_expect_success '--refresh-stat-only updates stat info without rehashing' '
test_commit refresh-stat refresh-stat original &&
git ls-files --stage -- refresh-stat >expect &&
git ls-files --debug refresh-stat | grep mtime >before &&
printf "modified\n" >refresh-stat &&
test-tool chmtime -100000 refresh-stat &&
test_must_fail git diff-files --quiet -- refresh-stat &&
git update-index --refresh-stat-only &&
git ls-files --debug refresh-stat | grep mtime >after &&
! test_cmp before after &&
git ls-files --stage -- refresh-stat >actual &&
test_cmp expect actual &&
git diff-files --quiet -- refresh-stat
'

test_expect_success '--refresh-stat-only ignores assume-unchanged' '
test_commit assume-unchanged assume-unchanged old &&
git update-index --assume-unchanged assume-unchanged &&
printf "new\n" >assume-unchanged &&
test-tool chmtime -100000 assume-unchanged &&
GIT_TEST_PRELOAD_INDEX=1 git update-index --refresh-stat-only &&
git update-index --no-assume-unchanged assume-unchanged &&
git diff-files --quiet -- assume-unchanged
'

test_expect_success '--refresh-stat-only with missing file and --ignore-missing' '
test_commit missing-ignore missing-ignore content &&
rm missing-ignore &&
git update-index --ignore-missing --refresh-stat-only &&
git checkout -- missing-ignore
'

test_expect_success '--refresh-stat-only reports error on missing file without --ignore-missing' '
test_commit missing-error missing-error content &&
rm missing-error &&
test_must_fail git update-index --refresh-stat-only >out 2>err &&
test_grep "needs update" out &&
git checkout -- missing-error
'

test_expect_success '--refresh-stat-only with -q is quiet' '
test_commit missing-quiet missing-quiet content &&
rm missing-quiet &&
git update-index -q --ignore-missing --refresh-stat-only >out 2>err &&
test_must_be_empty out &&
test_must_be_empty err
'

test_done
Loading