untracked cache: initial untracked cache validation

Make sure the starting conditions and all global exclude files are good to go. If not, either disable untracked cache completely, or wipe out the cache and start fresh. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Mar 8, 2015 at 17:12 UTC ccad261f07900b55029f3fd42a9ec8f17229808f
2 files changed +114 -3
dir.c
+110 -3
@@ -582,6 +582,22 @@ static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,
582 return d;
583 }
584
585 +static void do_invalidate_gitignore(struct untracked_cache_dir *dir)
586 +{
587 + int i;
588 + dir->valid = 0;
589 + dir->untracked_nr = 0;
590 + for (i = 0; i < dir->dirs_nr; i++)
591 + do_invalidate_gitignore(dir->dirs[i]);
592 +}
593 +
594 +static void invalidate_gitignore(struct untracked_cache *uc,
595 + struct untracked_cache_dir *dir)
596 +{
597 + uc->gitignore_invalidated++;
598 + do_invalidate_gitignore(dir);
599 +}
600 +
601 /*
602 * Given a file with name "fname", read it (either from disk, or from
603 * the index if "check_index" is non-zero), parse it and store the
@@ -698,6 +714,13 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
714 struct sha1_stat *sha1_stat)
715 {
716 struct exclude_list *el;
717 + /*
718 + * catch setup_standard_excludes() that's called before
719 + * dir->untracked is assigned. That function behaves
720 + * differently when dir->untracked is non-NULL.
721 + */
722 + if (!dir->untracked)
723 + dir->unmanaged_exclude_files++;
724 el = add_exclude_list(dir, EXC_FILE, fname);
725 if (add_excludes(fname, "", 0, el, 0, sha1_stat) < 0)
726 die("cannot use %s as an exclude file", fname);
@@ -705,6 +728,7 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
728
729 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
730 {
731 + dir->unmanaged_exclude_files++; /* see validate_untracked_cache() */
732 add_excludes_from_file_1(dir, fname, NULL);
733 }
734
@@ -1573,9 +1597,87 @@ static int treat_leading_path(struct dir_struct *dir,
1597 return rc;
1598 }
1599
1600 +static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
1601 + int base_len,
1602 + const struct pathspec *pathspec)
1603 +{
1604 + struct untracked_cache_dir *root;
1605 +
1606 + if (!dir->untracked)
1607 + return NULL;
1608 +
1609 + /*
1610 + * We only support $GIT_DIR/info/exclude and core.excludesfile
1611 + * as the global ignore rule files. Any other additions
1612 + * (e.g. from command line) invalidate the cache. This
1613 + * condition also catches running setup_standard_excludes()
1614 + * before setting dir->untracked!
1615 + */
1616 + if (dir->unmanaged_exclude_files)
1617 + return NULL;
1618 +
1619 + /*
1620 + * Optimize for the main use case only: whole-tree git
1621 + * status. More work involved in treat_leading_path() if we
1622 + * use cache on just a subset of the worktree. pathspec
1623 + * support could make the matter even worse.
1624 + */
1625 + if (base_len || (pathspec && pathspec->nr))
1626 + return NULL;
1627 +
1628 + /* Different set of flags may produce different results */
1629 + if (dir->flags != dir->untracked->dir_flags ||
1630 + /*
1631 + * See treat_directory(), case index_nonexistent. Without
1632 + * this flag, we may need to also cache .git file content
1633 + * for the resolve_gitlink_ref() call, which we don't.
1634 + */
1635 + !(dir->flags & DIR_SHOW_OTHER_DIRECTORIES) ||
1636 + /* We don't support collecting ignore files */
1637 + (dir->flags & (DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO |
1638 + DIR_COLLECT_IGNORED)))
1639 + return NULL;
1640 +
1641 + /*
1642 + * If we use .gitignore in the cache and now you change it to
1643 + * .gitexclude, everything will go wrong.
1644 + */
1645 + if (dir->exclude_per_dir != dir->untracked->exclude_per_dir &&
1646 + strcmp(dir->exclude_per_dir, dir->untracked->exclude_per_dir))
1647 + return NULL;
1648 +
1649 + /*
1650 + * EXC_CMDL is not considered in the cache. If people set it,
1651 + * skip the cache.
1652 + */
1653 + if (dir->exclude_list_group[EXC_CMDL].nr)
1654 + return NULL;
1655 +
1656 + if (!dir->untracked->root) {
1657 + const int len = sizeof(*dir->untracked->root);
1658 + dir->untracked->root = xmalloc(len);
1659 + memset(dir->untracked->root, 0, len);
1660 + }
1661 +
1662 + /* Validate $GIT_DIR/info/exclude and core.excludesfile */
1663 + root = dir->untracked->root;
1664 + if (hashcmp(dir->ss_info_exclude.sha1,
1665 + dir->untracked->ss_info_exclude.sha1)) {
1666 + invalidate_gitignore(dir->untracked, root);
1667 + dir->untracked->ss_info_exclude = dir->ss_info_exclude;
1668 + }
1669 + if (hashcmp(dir->ss_excludes_file.sha1,
1670 + dir->untracked->ss_excludes_file.sha1)) {
1671 + invalidate_gitignore(dir->untracked, root);
1672 + dir->untracked->ss_excludes_file = dir->ss_excludes_file;
1673 + }
1674 + return root;
1675 +}
1676 +
1677 int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
1678 {
1679 struct path_simplify *simplify;
1680 + struct untracked_cache_dir *untracked;
1681
1682 /*
1683 * Check out create_simplify()
@@ -1599,10 +1701,15 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru
1701 * create_simplify().
1702 */
1703 simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
1704 + untracked = validate_untracked_cache(dir, len, pathspec);
1705 + if (!untracked)
1706 + /*
1707 + * make sure untracked cache code path is disabled,
1708 + * e.g. prep_exclude()
1709 + */
1710 + dir->untracked = NULL;
1711 if (!len || treat_leading_path(dir, path, len, simplify))
1603 - read_directory_recursive(dir, path, len,
1604 - dir->untracked ? dir->untracked->root : NULL,
1605 - 0, simplify);
1712 + read_directory_recursive(dir, path, len, untracked, 0, simplify);
1713 free_simplify(simplify);
1714 qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
1715 qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
dir.h
+4
@@ -115,6 +115,8 @@ struct untracked_cache_dir {
115 unsigned int untracked_alloc, dirs_nr, dirs_alloc;
116 unsigned int untracked_nr;
117 unsigned int check_only : 1;
118 + /* all data in this struct are good */
119 + unsigned int valid : 1;
120 /* null SHA-1 means this directory does not have .gitignore */
121 unsigned char exclude_sha1[20];
122 char name[FLEX_ARRAY];
@@ -132,6 +134,7 @@ struct untracked_cache {
134 struct untracked_cache_dir *root;
135 /* Statistics */
136 int dir_created;
137 + int gitignore_invalidated;
138 };
139
140 struct dir_struct {
@@ -186,6 +189,7 @@ struct dir_struct {
189 struct untracked_cache *untracked;
190 struct sha1_stat ss_info_exclude;
191 struct sha1_stat ss_excludes_file;
192 + unsigned unmanaged_exclude_files;
193 };
194
195 /*