@@ -582,6 +582,22 @@ static struct untracked_cache_dir *lookup_untracked(struct untracked_cache *uc,
582
582
return d ;
583
583
}
584
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
+
585
601
/*
586
602
* Given a file with name "fname", read it (either from disk, or from
587
603
* the index if "check_index" is non-zero), parse it and store the
@@ -698,13 +714,21 @@ static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
698
714
struct sha1_stat * sha1_stat )
699
715
{
700
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 ++ ;
701
724
el = add_exclude_list (dir , EXC_FILE , fname );
702
725
if (add_excludes (fname , "" , 0 , el , 0 , sha1_stat ) < 0 )
703
726
die ("cannot use %s as an exclude file" , fname );
704
727
}
705
728
706
729
void add_excludes_from_file (struct dir_struct * dir , const char * fname )
707
730
{
731
+ dir -> unmanaged_exclude_files ++ ; /* see validate_untracked_cache() */
708
732
add_excludes_from_file_1 (dir , fname , NULL );
709
733
}
710
734
@@ -1573,9 +1597,87 @@ static int treat_leading_path(struct dir_struct *dir,
1573
1597
return rc ;
1574
1598
}
1575
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
+
1576
1677
int read_directory (struct dir_struct * dir , const char * path , int len , const struct pathspec * pathspec )
1577
1678
{
1578
1679
struct path_simplify * simplify ;
1680
+ struct untracked_cache_dir * untracked ;
1579
1681
1580
1682
/*
1581
1683
* Check out create_simplify()
@@ -1599,10 +1701,15 @@ int read_directory(struct dir_struct *dir, const char *path, int len, const stru
1599
1701
* create_simplify().
1600
1702
*/
1601
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 ;
1602
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 );
1606
1713
free_simplify (simplify );
1607
1714
qsort (dir -> entries , dir -> nr , sizeof (struct dir_entry * ), cmp_name );
1608
1715
qsort (dir -> ignored , dir -> ignored_nr , sizeof (struct dir_entry * ), cmp_name );
0 commit comments