mirror of
https://github.com/git/git.git
synced 2024-11-23 18:05:29 +08:00
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:
commit
38ccaf93bb
1
.gitignore
vendored
1
.gitignore
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
1
Makefile
1
Makefile
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
|
6
cache.h
6
cache.h
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
82
dir.h
82
dir.h
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
57
read-cache.c
57
read-cache.c
@ -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;
|
||||
|
@ -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
353
t/t7063-status-untracked-cache.sh
Executable 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
|
62
test-dump-untracked-cache.c
Normal file
62
test-dump-untracked-cache.c
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user