Skip to main content

AttributeResolver

Struct AttributeResolver 

Source
pub struct AttributeResolver { /* private fields */ }
Expand description

Answers “would git run our filter for this path, and would git convert its line endings”, the way git answers both.

Resolving rather than naming, since 2026-08-04. The previous build listed every attribute source carrying a filter line and left the reader to run git check-attr. That was the last route to a green report on a repository that stores plaintext: a line below the managed section, or a .gitattributes in a subdirectory, silently outranks the catch-all, and a note does not fail a CI gate. Naming also cannot tell an ordinary *.psd filter=lfs from a line that reaches a secret, so it either cried wolf or said nothing useful.

text joined filter on 2026-08-04, for the same reason and at the same severity. The managed section writes -text on every encrypted path, and a line below it saying secrets/** text puts the conversion back — measured on git 2.55, with sync freshly run so nothing else in this command had a complaint: 34 CR bytes eaten out of a 2 MB ciphertext, git add and git commit both exit 0, and the checkout fails the authentication tag and leaves no file at all. status printed VERDICT: no findings. over it. An unresolved filter costs a plaintext secret; this costs the file outright, and both answer the same question with “your declaration is not enforced”.

The stack reproduced here is git’s, in git’s precedence order — lowest first, because gix_attributes::Search matches its lists in reverse:

  1. the built-in [attr]binary macro;
  2. core.attributesFile, the global file;
  3. the working tree’s .gitattributes, root first and each directory after it, so the file closest to the path wins;
  4. $GIT_DIR/info/attributes, which outranks everything.

Macros ([attr]name …) are honoured only where git honours them: the root file, the global file and info/attributes. A [attr] line in a subdirectory is not a macro definition to git and is not one here.

A source that cannot be read is skipped, exactly as git skips it.

Discovery is lazy since 2026-08-07; assembly is not. The tree’s .gitattributes are probed only in the directories on the ancestor chain of each path Self::resolve is asked about — git reads the file from nowhere else, so everything beyond the ancestors was inert for the answer and cost a read_dir per directory and a file_type() per entry. Measured on a tree with 5281 directories and 480 000 ignored files (a build directory): git add of one declared file took 220 ms instead of 10 ms, scaling linearly with the number of directory entries — the only measured cost in the product that grew with untracked files. When a probe finds a file the whole gix_attributes::Search is rebuilt from scratch by Self::assemble, the same code that built it at construction, so precedence is decided by one sort in one place and lazy discovery cannot reorder anything: patterns from <dir>/.gitattributes reach only paths under <dir>, which makes the order of discovery across disjoint branches irrelevant, and the order within a chain is re-sorted on every rebuild. Rebuilds are bounded by the number of attribute files on the queried chains — typically zero to two — never by the number of directories.

Implementations§

Source§

impl AttributeResolver

Source

pub fn new( work_tree: &Path, common_dir: &Path, global: Option<&Path>, ignore_case: bool, staged: Vec<StagedAttributes>, ) -> Self

Loads every attribute source git would consult under work_tree.

global is core.attributesFile; ignore_case is core.ignorecase, which git applies to attribute matching as well as to path lookup.

common_dir, not this checkout’s git directory: info/ is on git’s common list, so every linked worktree reads the same info/attributes. Measured on git 2.55 with one linked worktree and secrets/** -filter in the main .git/info/attributes: git check-attr filter answers unset in both checkouts, and a file dropped in .git/worktrees/side/info/attributes is ignored in both. Reading the worktree’s own directory therefore got it wrong twice over — it missed the source that decides and would have read one git never consults.

staged is what staged_fallbacks found: the .gitattributes files git reads from the index because their working-tree file is gone. They take part in the ordinary precedence, by the directory they would occupy — git treats the fallback copy exactly as it would treat the file.

Source

pub fn resolve(&mut self, relative_path: &[u8]) -> Resolution

What git resolves for a repository-relative path, on both axes.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.