Merge branch 'nd/untracked-cache'

Teach the index to optionally remember already seen untracked files
to speed up "git status" in a working tree with tons of cruft.

* nd/untracked-cache: (24 commits)
  git-status.txt: advertisement for untracked cache
  untracked cache: guard and disable on system changes
  mingw32: add uname()
  t7063: tests for untracked cache
  update-index: test the system before enabling untracked cache
  update-index: manually enable or disable untracked cache
  status: enable untracked cache
  untracked-cache: temporarily disable with $GIT_DISABLE_UNTRACKED_CACHE
  untracked cache: mark index dirty if untracked cache is updated
  untracked cache: print stats with $GIT_TRACE_UNTRACKED_STATS
  untracked cache: avoid racy timestamps
  read-cache.c: split racy stat test to a separate function
  untracked cache: invalidate at index addition or removal
  untracked cache: load from UNTR index extension
  untracked cache: save to an index extension
  ewah: add convenient wrapper ewah_serialize_strbuf()
  untracked cache: don't open non-existent .gitignore
  untracked cache: mark what dirs should be recursed/saved
  untracked cache: record/validate dir mtime and reuse cached output
  untracked cache: make a wrapper around {open,read,close}dir()
  ...
This commit is contained in:
Junio C Hamano 2015-05-26 13:24:45 -07:00
commit 38ccaf93bb
21 changed files with 1823 additions and 59 deletions

1
.gitignore vendored
View File

@ -184,6 +184,7 @@
/test-delta
/test-dump-cache-tree
/test-dump-split-index
/test-dump-untracked-cache
/test-scrap-cache-tree
/test-genrandom
/test-hashmap

View File

@ -66,7 +66,10 @@ When `-u` option is not used, untracked files and directories are
shown (i.e. the same as specifying `normal`), to help you avoid
forgetting to add newly created files. Because it takes extra work
to find untracked files in the filesystem, this mode may take some
time in a large working tree. You can use `no` to have `git status`
time in a large working tree.
Consider enabling untracked cache and split index if supported (see
`git update-index --untracked-cache` and `git update-index
--split-index`), Otherwise you can use `no` to have `git status`
return more quickly without showing untracked files.
+
The default can be changed using the status.showUntrackedFiles

View File

@ -170,6 +170,20 @@ may not support it yet.
the shared index file. This mode is designed for very large
indexes that take a significant amount of time to read or write.
--untracked-cache::
--no-untracked-cache::
Enable or disable untracked cache extension. This could speed
up for commands that involve determining untracked files such
as `git status`. The underlying operating system and file
system must change `st_mtime` field of a directory if files
are added or deleted in that directory.
--force-untracked-cache::
For safety, `--untracked-cache` performs tests on the working
directory to make sure untracked cache can be used. These
tests can take a few seconds. `--force-untracked-cache` can be
used to skip the tests.
\--::
Do not interpret any more arguments as options.

View File

@ -233,3 +233,65 @@ Git index format
The remaining index entries after replaced ones will be added to the
final index. These added entries are also sorted by entry name then
stage.
== Untracked cache
Untracked cache saves the untracked file list and necessary data to
verify the cache. The signature for this extension is { 'U', 'N',
'T', 'R' }.
The extension starts with
- A sequence of NUL-terminated strings, preceded by the size of the
sequence in variable width encoding. Each string describes the
environment where the cache can be used.
- Stat data of $GIT_DIR/info/exclude. See "Index entry" section from
ctime field until "file size".
- Stat data of core.excludesfile
- 32-bit dir_flags (see struct dir_struct)
- 160-bit SHA-1 of $GIT_DIR/info/exclude. Null SHA-1 means the file
does not exist.
- 160-bit SHA-1 of core.excludesfile. Null SHA-1 means the file does
not exist.
- NUL-terminated string of per-dir exclude file name. This usually
is ".gitignore".
- The number of following directory blocks, variable width
encoding. If this number is zero, the extension ends here with a
following NUL.
- A number of directory blocks in depth-first-search order, each
consists of
- The number of untracked entries, variable width encoding.
- The number of sub-directory blocks, variable width encoding.
- The directory name terminated by NUL.
- A number of untrached file/dir names terminated by NUL.
The remaining data of each directory block is grouped by type:
- An ewah bitmap, the n-th bit marks whether the n-th directory has
valid untracked cache entries.
- An ewah bitmap, the n-th bit records "check-only" bit of
read_directory_recursive() for the n-th directory.
- An ewah bitmap, the n-th bit indicates whether SHA-1 and stat data
is valid for the n-th directory and exists in the next data.
- An array of stat data. The n-th data corresponds with the n-th
"one" bit in the previous ewah bitmap.
- An array of SHA-1. The n-th SHA-1 corresponds with the n-th "one" bit
in the previous ewah bitmap.
- One NUL.

View File

@ -574,6 +574,7 @@ TEST_PROGRAMS_NEED_X += test-date
TEST_PROGRAMS_NEED_X += test-delta
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
TEST_PROGRAMS_NEED_X += test-dump-split-index
TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
TEST_PROGRAMS_NEED_X += test-genrandom
TEST_PROGRAMS_NEED_X += test-hashmap
TEST_PROGRAMS_NEED_X += test-index-version

View File

@ -1366,13 +1366,14 @@ int cmd_status(int argc, const char **argv, const char *prefix)
refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
fd = hold_locked_index(&index_lock, 0);
if (0 <= fd)
update_index_if_able(&the_index, &index_lock);
s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
s.ignore_submodule_arg = ignore_submodule_arg;
wt_status_collect(&s);
if (0 <= fd)
update_index_if_able(&the_index, &index_lock);
if (s.relative_paths)
s.prefix = prefix;

View File

@ -33,6 +33,7 @@ static int mark_valid_only;
static int mark_skip_worktree_only;
#define MARK_FLAG 1
#define UNMARK_FLAG 2
static struct strbuf mtime_dir = STRBUF_INIT;
__attribute__((format (printf, 1, 2)))
static void report(const char *fmt, ...)
@ -48,6 +49,166 @@ static void report(const char *fmt, ...)
va_end(vp);
}
static void remove_test_directory(void)
{
if (mtime_dir.len)
remove_dir_recursively(&mtime_dir, 0);
}
static const char *get_mtime_path(const char *path)
{
static struct strbuf sb = STRBUF_INIT;
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/%s", mtime_dir.buf, path);
return sb.buf;
}
static void xmkdir(const char *path)
{
path = get_mtime_path(path);
if (mkdir(path, 0700))
die_errno(_("failed to create directory %s"), path);
}
static int xstat_mtime_dir(struct stat *st)
{
if (stat(mtime_dir.buf, st))
die_errno(_("failed to stat %s"), mtime_dir.buf);
return 0;
}
static int create_file(const char *path)
{
int fd;
path = get_mtime_path(path);
fd = open(path, O_CREAT | O_RDWR, 0644);
if (fd < 0)
die_errno(_("failed to create file %s"), path);
return fd;
}
static void xunlink(const char *path)
{
path = get_mtime_path(path);
if (unlink(path))
die_errno(_("failed to delete file %s"), path);
}
static void xrmdir(const char *path)
{
path = get_mtime_path(path);
if (rmdir(path))
die_errno(_("failed to delete directory %s"), path);
}
static void avoid_racy(void)
{
/*
* not use if we could usleep(10) if USE_NSEC is defined. The
* field nsec could be there, but the OS could choose to
* ignore it?
*/
sleep(1);
}
static int test_if_untracked_cache_is_supported(void)
{
struct stat st;
struct stat_data base;
int fd, ret = 0;
strbuf_addstr(&mtime_dir, "mtime-test-XXXXXX");
if (!mkdtemp(mtime_dir.buf))
die_errno("Could not make temporary directory");
fprintf(stderr, _("Testing "));
atexit(remove_test_directory);
xstat_mtime_dir(&st);
fill_stat_data(&base, &st);
fputc('.', stderr);
avoid_racy();
fd = create_file("newfile");
xstat_mtime_dir(&st);
if (!match_stat_data(&base, &st)) {
close(fd);
fputc('\n', stderr);
fprintf_ln(stderr,_("directory stat info does not "
"change after adding a new file"));
goto done;
}
fill_stat_data(&base, &st);
fputc('.', stderr);
avoid_racy();
xmkdir("new-dir");
xstat_mtime_dir(&st);
if (!match_stat_data(&base, &st)) {
close(fd);
fputc('\n', stderr);
fprintf_ln(stderr, _("directory stat info does not change "
"after adding a new directory"));
goto done;
}
fill_stat_data(&base, &st);
fputc('.', stderr);
avoid_racy();
write_or_die(fd, "data", 4);
close(fd);
xstat_mtime_dir(&st);
if (match_stat_data(&base, &st)) {
fputc('\n', stderr);
fprintf_ln(stderr, _("directory stat info changes "
"after updating a file"));
goto done;
}
fputc('.', stderr);
avoid_racy();
close(create_file("new-dir/new"));
xstat_mtime_dir(&st);
if (match_stat_data(&base, &st)) {
fputc('\n', stderr);
fprintf_ln(stderr, _("directory stat info changes after "
"adding a file inside subdirectory"));
goto done;
}
fputc('.', stderr);
avoid_racy();
xunlink("newfile");
xstat_mtime_dir(&st);
if (!match_stat_data(&base, &st)) {
fputc('\n', stderr);
fprintf_ln(stderr, _("directory stat info does not "
"change after deleting a file"));
goto done;
}
fill_stat_data(&base, &st);
fputc('.', stderr);
avoid_racy();
xunlink("new-dir/new");
xrmdir("new-dir");
xstat_mtime_dir(&st);
if (!match_stat_data(&base, &st)) {
fputc('\n', stderr);
fprintf_ln(stderr, _("directory stat info does not "
"change after deleting a directory"));
goto done;
}
if (rmdir(mtime_dir.buf))
die_errno(_("failed to delete directory %s"), mtime_dir.buf);
fprintf_ln(stderr, _(" OK"));
ret = 1;
done:
strbuf_release(&mtime_dir);
return ret;
}
static int mark_ce_flags(const char *path, int flag, int mark)
{
int namelen = strlen(path);
@ -741,6 +902,7 @@ static int reupdate_callback(struct parse_opt_ctx_t *ctx,
int cmd_update_index(int argc, const char **argv, const char *prefix)
{
int newfd, entries, has_errors = 0, line_termination = '\n';
int untracked_cache = -1;
int read_from_stdin = 0;
int prefix_length = prefix ? strlen(prefix) : 0;
int preferred_index_format = 0;
@ -832,6 +994,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
N_("write index in this format")),
OPT_BOOL(0, "split-index", &split_index,
N_("enable or disable split index")),
OPT_BOOL(0, "untracked-cache", &untracked_cache,
N_("enable/disable untracked cache")),
OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
N_("enable untracked cache without testing the filesystem"), 2),
OPT_END()
};
@ -938,6 +1104,28 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
the_index.split_index = NULL;
the_index.cache_changed |= SOMETHING_CHANGED;
}
if (untracked_cache > 0) {
struct untracked_cache *uc;
if (untracked_cache < 2) {
setup_work_tree();
if (!test_if_untracked_cache_is_supported())
return 1;
}
if (!the_index.untracked) {
uc = xcalloc(1, sizeof(*uc));
strbuf_init(&uc->ident, 100);
uc->exclude_per_dir = ".gitignore";
/* should be the same flags used by git-status */
uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
the_index.untracked = uc;
}
add_untracked_ident(the_index.untracked);
the_index.cache_changed |= UNTRACKED_CHANGED;
} else if (!untracked_cache && the_index.untracked) {
the_index.untracked = NULL;
the_index.cache_changed |= UNTRACKED_CHANGED;
}
if (active_cache_changed) {
if (newfd < 0) {

View File

@ -297,8 +297,11 @@ static inline unsigned int canon_mode(unsigned int mode)
#define RESOLVE_UNDO_CHANGED (1 << 4)
#define CACHE_TREE_CHANGED (1 << 5)
#define SPLIT_INDEX_ORDERED (1 << 6)
#define UNTRACKED_CHANGED (1 << 7)
struct split_index;
struct untracked_cache;
struct index_state {
struct cache_entry **cache;
unsigned int version;
@ -312,6 +315,7 @@ struct index_state {
struct hashmap name_hash;
struct hashmap dir_hash;
unsigned char sha1[20];
struct untracked_cache *untracked;
};
extern struct index_state the_index;
@ -563,6 +567,8 @@ extern void fill_stat_data(struct stat_data *sd, struct stat *st);
* INODE_CHANGED, and DATA_CHANGED.
*/
extern int match_stat_data(const struct stat_data *sd, struct stat *st);
extern int match_stat_data_racy(const struct index_state *istate,
const struct stat_data *sd, struct stat *st);
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);

View File

@ -2128,3 +2128,14 @@ void mingw_startup()
/* initialize Unicode console */
winansi_init();
}
int uname(struct utsname *buf)
{
DWORD v = GetVersion();
memset(buf, 0, sizeof(*buf));
strcpy(buf->sysname, "Windows");
sprintf(buf->release, "%u.%u", v & 0xff, (v >> 8) & 0xff);
/* assuming NT variants only.. */
sprintf(buf->version, "%u", (v >> 16) & 0x7fff);
return 0;
}

View File

@ -76,6 +76,14 @@ struct itimerval {
};
#define ITIMER_REAL 0
struct utsname {
char sysname[16];
char nodename[1];
char release[16];
char version[16];
char machine[1];
};
/*
* sanitize preprocessor namespace polluted by Windows headers defining
* macros which collide with git local versions
@ -175,6 +183,7 @@ struct passwd *getpwuid(uid_t uid);
int setitimer(int type, struct itimerval *in, struct itimerval *out);
int sigaction(int sig, struct sigaction *in, struct sigaction *out);
int link(const char *oldpath, const char *newpath);
int uname(struct utsname *buf);
/*
* replacements of existing functions

990
dir.c

File diff suppressed because it is too large Load Diff

82
dir.h
View File

@ -66,6 +66,7 @@ struct exclude_stack {
struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
int baselen;
int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
struct untracked_cache_dir *ucd;
};
struct exclude_list_group {
@ -73,6 +74,73 @@ struct exclude_list_group {
struct exclude_list *el;
};
struct sha1_stat {
struct stat_data stat;
unsigned char sha1[20];
int valid;
};
/*
* Untracked cache
*
* The following inputs are sufficient to determine what files in a
* directory are excluded:
*
* - The list of files and directories of the directory in question
* - The $GIT_DIR/index
* - dir_struct flags
* - The content of $GIT_DIR/info/exclude
* - The content of core.excludesfile
* - The content (or the lack) of .gitignore of all parent directories
* from $GIT_WORK_TREE
* - The check_only flag in read_directory_recursive (for
* DIR_HIDE_EMPTY_DIRECTORIES)
*
* The first input can be checked using directory mtime. In many
* filesystems, directory mtime (stat_data field) is updated when its
* files or direct subdirs are added or removed.
*
* The second one can be hooked from cache_tree_invalidate_path().
* Whenever a file (or a submodule) is added or removed from a
* directory, we invalidate that directory.
*
* The remaining inputs are easy, their SHA-1 could be used to verify
* their contents (exclude_sha1[], info_exclude_sha1[] and
* excludes_file_sha1[])
*/
struct untracked_cache_dir {
struct untracked_cache_dir **dirs;
char **untracked;
struct stat_data stat_data;
unsigned int untracked_alloc, dirs_nr, dirs_alloc;
unsigned int untracked_nr;
unsigned int check_only : 1;
/* all data except 'dirs' in this struct are good */
unsigned int valid : 1;
unsigned int recurse : 1;
/* null SHA-1 means this directory does not have .gitignore */
unsigned char exclude_sha1[20];
char name[FLEX_ARRAY];
};
struct untracked_cache {
struct sha1_stat ss_info_exclude;
struct sha1_stat ss_excludes_file;
const char *exclude_per_dir;
struct strbuf ident;
/*
* dir_struct#flags must match dir_flags or the untracked
* cache is ignored.
*/
unsigned dir_flags;
struct untracked_cache_dir *root;
/* Statistics */
int dir_created;
int gitignore_invalidated;
int dir_invalidated;
int dir_opened;
};
struct dir_struct {
int nr, alloc;
int ignored_nr, ignored_alloc;
@ -120,6 +188,12 @@ struct dir_struct {
struct exclude_stack *exclude_stack;
struct exclude *exclude;
struct strbuf basebuf;
/* Enable untracked file cache if set */
struct untracked_cache *untracked;
struct sha1_stat ss_info_exclude;
struct sha1_stat ss_excludes_file;
unsigned unmanaged_exclude_files;
};
/*
@ -226,4 +300,12 @@ static inline int dir_path_match(const struct dir_entry *ent,
has_trailing_dir);
}
void untracked_cache_invalidate_path(struct index_state *, const char *);
void untracked_cache_remove_from_index(struct index_state *, const char *);
void untracked_cache_add_to_index(struct index_state *, const char *);
void free_untracked_cache(struct untracked_cache *);
struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);
void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);
void add_untracked_ident(struct untracked_cache *);
#endif

View File

@ -19,6 +19,7 @@
*/
#include "git-compat-util.h"
#include "ewok.h"
#include "strbuf.h"
int ewah_serialize_native(struct ewah_bitmap *self, int fd)
{
@ -110,6 +111,18 @@ int ewah_serialize(struct ewah_bitmap *self, int fd)
return ewah_serialize_to(self, write_helper, (void *)(intptr_t)fd);
}
static int write_strbuf(void *user_data, const void *data, size_t len)
{
struct strbuf *sb = user_data;
strbuf_add(sb, data, len);
return len;
}
int ewah_serialize_strbuf(struct ewah_bitmap *self, struct strbuf *sb)
{
return ewah_serialize_to(self, write_strbuf, sb);
}
int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len)
{
const uint8_t *ptr = map;

View File

@ -30,6 +30,7 @@
# define ewah_calloc xcalloc
#endif
struct strbuf;
typedef uint64_t eword_t;
#define BITS_IN_WORD (sizeof(eword_t) * 8)
@ -98,6 +99,7 @@ int ewah_serialize_to(struct ewah_bitmap *self,
void *out);
int ewah_serialize(struct ewah_bitmap *self, int fd);
int ewah_serialize_native(struct ewah_bitmap *self, int fd);
int ewah_serialize_strbuf(struct ewah_bitmap *self, struct strbuf *);
int ewah_deserialize(struct ewah_bitmap *self, int fd);
int ewah_read_mmap(struct ewah_bitmap *self, const void *map, size_t len);

View File

@ -189,6 +189,7 @@
#elif defined(_MSC_VER)
#include "compat/msvc.h"
#else
#include <sys/utsname.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/socket.h>

View File

@ -39,11 +39,12 @@ static struct cache_entry *refresh_cache_entry(struct cache_entry *ce,
#define CACHE_EXT_TREE 0x54524545 /* "TREE" */
#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
#define CACHE_EXT_LINK 0x6c696e6b /* "link" */
#define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */
/* 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)
SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED)
struct index_state the_index;
static const char *alternate_index_output;
@ -79,6 +80,7 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
memcpy(new->name, new_name, namelen + 1);
cache_tree_invalidate_path(istate, old->name);
untracked_cache_remove_from_index(istate, old->name);
remove_index_entry_at(istate, nr);
add_index_entry(istate, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
}
@ -270,20 +272,34 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
return changed;
}
static int is_racy_stat(const struct index_state *istate,
const struct stat_data *sd)
{
return (istate->timestamp.sec &&
#ifdef USE_NSEC
/* nanosecond timestamped files can also be racy! */
(istate->timestamp.sec < sd->sd_mtime.sec ||
(istate->timestamp.sec == sd->sd_mtime.sec &&
istate->timestamp.nsec <= sd->sd_mtime.nsec))
#else
istate->timestamp.sec <= sd->sd_mtime.sec
#endif
);
}
static int is_racy_timestamp(const struct index_state *istate,
const struct cache_entry *ce)
{
return (!S_ISGITLINK(ce->ce_mode) &&
istate->timestamp.sec &&
#ifdef USE_NSEC
/* nanosecond timestamped files can also be racy! */
(istate->timestamp.sec < ce->ce_stat_data.sd_mtime.sec ||
(istate->timestamp.sec == ce->ce_stat_data.sd_mtime.sec &&
istate->timestamp.nsec <= ce->ce_stat_data.sd_mtime.nsec))
#else
istate->timestamp.sec <= ce->ce_stat_data.sd_mtime.sec
#endif
);
is_racy_stat(istate, &ce->ce_stat_data));
}
int match_stat_data_racy(const struct index_state *istate,
const struct stat_data *sd, struct stat *st)
{
if (is_racy_stat(istate, sd))
return MTIME_CHANGED;
return match_stat_data(sd, st);
}
int ie_match_stat(const struct index_state *istate,
@ -538,6 +554,7 @@ int remove_file_from_index(struct index_state *istate, const char *path)
if (pos < 0)
pos = -pos-1;
cache_tree_invalidate_path(istate, path);
untracked_cache_remove_from_index(istate, path);
while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
remove_index_entry_at(istate, pos);
return 0;
@ -982,6 +999,8 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
}
pos = -pos-1;
untracked_cache_add_to_index(istate, ce->name);
/*
* Inserting a merged entry ("stage 0") into the index
* will always replace all non-merged entries..
@ -1372,6 +1391,9 @@ static int read_index_extension(struct index_state *istate,
if (read_link_extension(istate, data, sz))
return -1;
break;
case CACHE_EXT_UNTRACKED:
istate->untracked = read_untracked_extension(data, sz);
break;
default:
if (*ext < 'A' || 'Z' < *ext)
return error("index uses %.4s extension, which we do not understand",
@ -1667,6 +1689,8 @@ int discard_index(struct index_state *istate)
istate->cache = NULL;
istate->cache_alloc = 0;
discard_split_index(istate);
free_untracked_cache(istate->untracked);
istate->untracked = NULL;
return 0;
}
@ -2053,6 +2077,17 @@ static int do_write_index(struct index_state *istate, int newfd,
if (err)
return -1;
}
if (!strip_extensions && istate->untracked) {
struct strbuf sb = STRBUF_INIT;
write_untracked_extension(&sb, istate->untracked);
err = write_index_ext_header(&c, newfd, CACHE_EXT_UNTRACKED,
sb.len) < 0 ||
ce_write(&c, newfd, sb.buf, sb.len) < 0;
strbuf_release(&sb);
if (err)
return -1;
}
if (ce_flush(&c, newfd, istate->sha1) || fstat(newfd, &st))
return -1;

View File

@ -41,13 +41,6 @@ int read_link_extension(struct index_state *istate,
return 0;
}
static int write_strbuf(void *user_data, const void *data, size_t len)
{
struct strbuf *sb = user_data;
strbuf_add(sb, data, len);
return len;
}
int write_link_extension(struct strbuf *sb,
struct index_state *istate)
{
@ -55,8 +48,8 @@ int write_link_extension(struct strbuf *sb,
strbuf_add(sb, si->base_sha1, 20);
if (!si->delete_bitmap && !si->replace_bitmap)
return 0;
ewah_serialize_to(si->delete_bitmap, write_strbuf, sb);
ewah_serialize_to(si->replace_bitmap, write_strbuf, sb);
ewah_serialize_strbuf(si->delete_bitmap, sb);
ewah_serialize_strbuf(si->replace_bitmap, sb);
return 0;
}

353
t/t7063-status-untracked-cache.sh Executable file
View File

@ -0,0 +1,353 @@
#!/bin/sh
test_description='test untracked cache'
. ./test-lib.sh
avoid_racy() {
sleep 1
}
git update-index --untracked-cache
# It's fine if git update-index returns an error code other than one,
# it'll be caught in the first test.
if test $? -eq 1; then
skip_all='This system does not support untracked cache'
test_done
fi
test_expect_success 'setup' '
git init worktree &&
cd worktree &&
mkdir done dtwo dthree &&
touch one two three done/one dtwo/two dthree/three &&
git add one two done/one &&
: >.git/info/exclude &&
git update-index --untracked-cache
'
test_expect_success 'untracked cache is empty' '
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 0000000000000000000000000000000000000000
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
EOF
test_cmp ../expect ../actual
'
cat >../status.expect <<EOF &&
A done/one
A one
A two
?? dthree/
?? dtwo/
?? three
EOF
cat >../dump.expect <<EOF &&
info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
/ 0000000000000000000000000000000000000000 recurse valid
dthree/
dtwo/
three
/done/ 0000000000000000000000000000000000000000 recurse valid
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
three
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
two
EOF
test_expect_success 'status first time (empty cache)' '
avoid_racy &&
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 3
gitignore invalidation: 1
directory invalidation: 0
opendir: 4
EOF
test_cmp ../trace.expect ../trace
'
test_expect_success 'untracked cache after first status' '
test-dump-untracked-cache >../actual &&
test_cmp ../dump.expect ../actual
'
test_expect_success 'status second time (fully populated cache)' '
avoid_racy &&
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
gitignore invalidation: 0
directory invalidation: 0
opendir: 0
EOF
test_cmp ../trace.expect ../trace
'
test_expect_success 'untracked cache after second status' '
test-dump-untracked-cache >../actual &&
test_cmp ../dump.expect ../actual
'
test_expect_success 'modify in root directory, one dir invalidation' '
avoid_racy &&
: >four &&
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
cat >../status.expect <<EOF &&
A done/one
A one
A two
?? dthree/
?? dtwo/
?? four
?? three
EOF
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
gitignore invalidation: 0
directory invalidation: 1
opendir: 1
EOF
test_cmp ../trace.expect ../trace
'
test_expect_success 'verify untracked cache dump' '
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
/ 0000000000000000000000000000000000000000 recurse valid
dthree/
dtwo/
four
three
/done/ 0000000000000000000000000000000000000000 recurse valid
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
three
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
two
EOF
test_cmp ../expect ../actual
'
test_expect_success 'new .gitignore invalidates recursively' '
avoid_racy &&
echo four >.gitignore &&
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
cat >../status.expect <<EOF &&
A done/one
A one
A two
?? .gitignore
?? dthree/
?? dtwo/
?? three
EOF
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
gitignore invalidation: 1
directory invalidation: 1
opendir: 4
EOF
test_cmp ../trace.expect ../trace
'
test_expect_success 'verify untracked cache dump' '
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
.gitignore
dthree/
dtwo/
three
/done/ 0000000000000000000000000000000000000000 recurse valid
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
three
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
two
EOF
test_cmp ../expect ../actual
'
test_expect_success 'new info/exclude invalidates everything' '
avoid_racy &&
echo three >>.git/info/exclude &&
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
cat >../status.expect <<EOF &&
A done/one
A one
A two
?? .gitignore
?? dtwo/
EOF
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
gitignore invalidation: 1
directory invalidation: 0
opendir: 4
EOF
test_cmp ../trace.expect ../trace
'
test_expect_success 'verify untracked cache dump' '
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
.gitignore
dtwo/
/done/ 0000000000000000000000000000000000000000 recurse valid
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
two
EOF
test_cmp ../expect ../actual
'
test_expect_success 'move two from tracked to untracked' '
git rm --cached two &&
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse
/done/ 0000000000000000000000000000000000000000 recurse valid
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
two
EOF
test_cmp ../expect ../actual
'
test_expect_success 'status after the move' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
cat >../status.expect <<EOF &&
A done/one
A one
?? .gitignore
?? dtwo/
?? two
EOF
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
gitignore invalidation: 0
directory invalidation: 0
opendir: 1
EOF
test_cmp ../trace.expect ../trace
'
test_expect_success 'verify untracked cache dump' '
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
.gitignore
dtwo/
two
/done/ 0000000000000000000000000000000000000000 recurse valid
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
two
EOF
test_cmp ../expect ../actual
'
test_expect_success 'move two from untracked to tracked' '
git add two &&
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse
/done/ 0000000000000000000000000000000000000000 recurse valid
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
two
EOF
test_cmp ../expect ../actual
'
test_expect_success 'status after the move' '
: >../trace &&
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
git status --porcelain >../actual &&
cat >../status.expect <<EOF &&
A done/one
A one
A two
?? .gitignore
?? dtwo/
EOF
test_cmp ../status.expect ../actual &&
cat >../trace.expect <<EOF &&
node creation: 0
gitignore invalidation: 0
directory invalidation: 0
opendir: 1
EOF
test_cmp ../trace.expect ../trace
'
test_expect_success 'verify untracked cache dump' '
test-dump-untracked-cache >../actual &&
cat >../expect <<EOF &&
info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0
core.excludesfile 0000000000000000000000000000000000000000
exclude_per_dir .gitignore
flags 00000006
/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid
.gitignore
dtwo/
/done/ 0000000000000000000000000000000000000000 recurse valid
/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid
/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid
two
EOF
test_cmp ../expect ../actual
'
test_done

View File

@ -0,0 +1,62 @@
#include "cache.h"
#include "dir.h"
static int compare_untracked(const void *a_, const void *b_)
{
const char *const *a = a_;
const char *const *b = b_;
return strcmp(*a, *b);
}
static int compare_dir(const void *a_, const void *b_)
{
const struct untracked_cache_dir *const *a = a_;
const struct untracked_cache_dir *const *b = b_;
return strcmp((*a)->name, (*b)->name);
}
static void dump(struct untracked_cache_dir *ucd, struct strbuf *base)
{
int i, len;
qsort(ucd->untracked, ucd->untracked_nr, sizeof(*ucd->untracked),
compare_untracked);
qsort(ucd->dirs, ucd->dirs_nr, sizeof(*ucd->dirs),
compare_dir);
len = base->len;
strbuf_addf(base, "%s/", ucd->name);
printf("%s %s", base->buf,
sha1_to_hex(ucd->exclude_sha1));
if (ucd->recurse)
fputs(" recurse", stdout);
if (ucd->check_only)
fputs(" check_only", stdout);
if (ucd->valid)
fputs(" valid", stdout);
printf("\n");
for (i = 0; i < ucd->untracked_nr; i++)
printf("%s\n", ucd->untracked[i]);
for (i = 0; i < ucd->dirs_nr; i++)
dump(ucd->dirs[i], base);
strbuf_setlen(base, len);
}
int main(int ac, char **av)
{
struct untracked_cache *uc;
struct strbuf base = STRBUF_INIT;
setup_git_directory();
if (read_cache() < 0)
die("unable to read index file");
uc = the_index.untracked;
if (!uc) {
printf("no untracked cache\n");
return 0;
}
printf("info/exclude %s\n", sha1_to_hex(uc->ss_info_exclude.sha1));
printf("core.excludesfile %s\n", sha1_to_hex(uc->ss_excludes_file.sha1));
printf("exclude_per_dir %s\n", uc->exclude_per_dir);
printf("flags %08x\n", uc->dir_flags);
if (uc->root)
dump(uc->root, &base);
return 0;
}

View File

@ -9,6 +9,7 @@
#include "refs.h"
#include "attr.h"
#include "split-index.h"
#include "dir.h"
/*
* Error messages expected by scripts out of plumbing commands such as
@ -1259,8 +1260,10 @@ static int verify_uptodate_sparse(const struct cache_entry *ce,
static void invalidate_ce_path(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
if (ce)
cache_tree_invalidate_path(o->src_index, ce->name);
if (!ce)
return;
cache_tree_invalidate_path(o->src_index, ce->name);
untracked_cache_invalidate_path(o->src_index, ce->name);
}
/*

View File

@ -585,6 +585,8 @@ static void wt_status_collect_untracked(struct wt_status *s)
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
if (s->show_ignored_files)
dir.flags |= DIR_SHOW_IGNORED_TOO;
else
dir.untracked = the_index.untracked;
setup_standard_excludes(&dir);
fill_directory(&dir, &s->pathspec);