1 use std
::path
::PathBuf
;
3 use bstr
::{BString, ByteSlice}
;
4 use gix_glob
::pattern
::Case
;
6 use crate::{cache::State, PathIdMapping}
;
8 type AttributeMatchGroup
= gix_attributes
::Search
;
9 type IgnoreMatchGroup
= gix_ignore
::Search
;
11 /// State related to attributes associated with files in the repository.
12 #[derive(Default, Clone)]
13 pub struct Attributes
{
14 /// Attribute patterns which aren't tied to the repository root, hence are global, they contribute first.
15 globals
: AttributeMatchGroup
,
16 /// Attribute patterns that match the currently set directory (in the stack).
18 /// Note that the root-level file is always loaded, if present, followed by, the `$GIT_DIR/info/attributes`, if present, based
19 /// on the location of the `info_attributes` file.
20 stack
: AttributeMatchGroup
,
21 /// The first time we push the root, we have to load additional information from this file if it exists along with the root attributes
22 /// file if possible, and keep them there throughout.
23 info_attributes
: Option
<PathBuf
>,
24 /// A lookup table to accelerate searches.
25 collection
: gix_attributes
::search
::MetadataCollection
,
26 /// Where to read `.gitattributes` data from.
27 source
: attributes
::Source
,
30 /// State related to the exclusion of files, supporting static overrides and globals, along with a stack of dynamically read
31 /// ignore files from disk or from the index each time the directory changes.
32 #[derive(Default, Clone)]
35 /// Ignore patterns passed as overrides to everything else, typically passed on the command-line and the first patterns to
37 overrides
: IgnoreMatchGroup
,
38 /// Ignore patterns that match the currently set director (in the stack), which is pushed and popped as needed.
39 stack
: IgnoreMatchGroup
,
40 /// Ignore patterns which aren't tied to the repository root, hence are global. They are consulted last.
41 globals
: IgnoreMatchGroup
,
42 /// A matching stack of pattern indices which is empty if we have just been initialized to indicate that the
43 /// currently set directory had a pattern matched. Note that this one could be negated.
44 /// (index into match groups, index into list of pattern lists, index into pattern list)
45 matched_directory_patterns_stack
: Vec
<Option
<(usize, usize, usize)>>,
46 /// The name of the file to look for in directories.
47 pub(crate) exclude_file_name_for_directories
: BString
,
48 /// Where to read ignore files from
49 source
: ignore
::Source
,
59 /// Configure a state to be suitable for checking out files, which only needs access to attribute files read from the index.
60 pub fn for_checkout(unlink_on_collision
: bool
, attributes
: Attributes
) -> Self {
61 State
::CreateDirectoryAndAttributesStack
{
67 /// Configure a state for adding files, with support for ignore files and attribute files.
68 pub fn for_add(attributes
: Attributes
, ignore
: Ignore
) -> Self {
69 State
::AttributesAndIgnoreStack { attributes, ignore }
72 /// Configure a state for status retrieval, which needs access to ignore files only.
73 pub fn for_status(ignore
: Ignore
) -> Self {
74 State
::IgnoreStack(ignore
)
80 /// Returns a vec of tuples of relative index paths along with the best usable blob OID for
81 /// either *ignore* or *attribute* files or both. This allows files to be accessed directly from
82 /// the object database without the need for a worktree checkout.
84 /// Note that this method…
85 /// - ignores entries which aren't blobs.
86 /// - ignores ignore entries which are not skip-worktree.
87 /// - within merges, picks 'our' stage both for *ignore* and *attribute* files.
89 /// * `index` is where we look for suitable files by path in order to obtain their blob hash.
90 /// * `paths` is the indices storage backend for paths.
91 /// * `case` determines if the search for files should be case-sensitive or not.
92 pub fn id_mappings_from_index(
94 index
: &gix_index
::State
,
95 paths
: &gix_index
::PathStorageRef
,
96 ignore_source
: ignore
::Source
,
98 ) -> Vec
<PathIdMapping
> {
101 let names
= match self {
102 State
::IgnoreStack(v
) => {
103 a1_backing
= [(v
.exclude_file_name_for_directories
.as_bytes().as_bstr(), true)];
106 State
::AttributesAndIgnoreStack { ignore, .. }
=> {
108 (ignore
.exclude_file_name_for_directories
.as_bytes().as_bstr(), true),
109 (".gitattributes".into(), false),
113 State
::CreateDirectoryAndAttributesStack { .. }
=> {
114 a1_backing
= [(".gitattributes".into(), true)];
122 .filter_map(move |entry
| {
123 let path
= entry
.path_in(paths
);
125 // Stage 0 means there is no merge going on, stage 2 means it's 'our' side of the merge, but then
126 // there won't be a stage 0.
127 if entry
.mode
== gix_index
::entry
::Mode
::FILE
&& (entry
.stage() == 0 || entry
.stage() == 2) {
130 .map(|pos
| path
[pos
+ 1..].as_bstr())
132 let is_ignore
= names
.iter().find_map(|t
| {
134 Case
::Sensitive
=> basename
== t
.0,
135 Case
::Fold
=> basename
.eq_ignore_ascii_case(t
.0),
140 match ignore_source
{
141 ignore
::Source
::IdMapping
=> {}
142 ignore
::Source
::WorktreeThenIdMappingIfNotSkipped
=> {
143 // See https://github.com/git/git/blob/master/dir.c#L912:L912
144 if !entry
.flags
.contains(gix_index
::entry
::Flags
::SKIP_WORKTREE
) {
150 Some((path
.to_owned(), entry
.id
))
158 pub(crate) fn ignore_or_panic(&self) -> &Ignore
{
160 State
::IgnoreStack(v
) => v
,
161 State
::AttributesAndIgnoreStack { ignore, .. }
=> ignore
,
162 State
::CreateDirectoryAndAttributesStack { .. }
=> {
163 unreachable
!("BUG: must not try to check excludes without it being setup")
168 pub(crate) fn attributes_or_panic(&self) -> &Attributes
{
170 State
::AttributesAndIgnoreStack { attributes, .. }
171 | State
::CreateDirectoryAndAttributesStack { attributes, .. }
=> attributes
,
172 State
::IgnoreStack(_
) => {
173 unreachable
!("BUG: must not try to check excludes without it being setup")