{"record":{"id":"ffb245509c9ab017","repo":"GitoxideLabs/gitoxide","slug":"attr-itself","errorCode":null,"errorMessage":"attr itself","messagePattern":"attr itself","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"gix-attributes/src/parse.rs","lineNumber":54,"sourceCode":"    line_no: usize,\n}\n\n/// An iterator over attribute assignments in a single line.\npub struct Iter<'a> {\n    attrs: std::slice::Split<'a, u8, fn(&u8) -> bool>,\n}\n\nimpl<'a> Iter<'a> {\n    /// Create a new instance to parse attribute assignments from `input`.\n    pub fn new(input: &'a BStr) -> Self {\n        Iter {\n            attrs: input.split(is_blank as fn(&u8) -> bool),\n        }\n    }\n\n    fn parse_attr(&self, attr: &'a [u8]) -> Result<AssignmentRef<'a>, name::Error> {\n        let mut tokens = attr.splitn(2, |b| *b == b'=');\n        let attr = tokens.next().expect(\"attr itself\").as_bstr();\n        let possibly_value = tokens.next();\n        let (attr, state) = if attr.first() == Some(&b'-') {\n            (&attr[1..], StateRef::Unset)\n        } else if attr.first() == Some(&b'!') {\n            (&attr[1..], StateRef::Unspecified)\n        } else {\n            (attr, possibly_value.map_or(StateRef::Set, StateRef::from_bytes))\n        };\n        Ok(AssignmentRef::new(check_attr(attr)?, state))\n    }\n}\n\nfn check_attr(attr: &BStr) -> Result<NameRef<'_>, name::Error> {\n    NameRef::try_from(attr).and_then(|name| {\n        (!name.as_str().starts_with(\"builtin_\"))\n            .then_some(name)\n            .ok_or_else(|| name::Error { attribute: attr.into() })\n    })","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-attributes/src/parse.rs#L36-L72","documentation":"`parse_attr` splits an attribute assignment at the first `=` with `splitn(2, ...)`. The first element (the attribute name) always exists for any non-empty input, so `tokens.next()` is expected to be `Some`; the `.expect(\"attr itself\")` panics only on empty input reaching this function, indicating a caller bug.","triggerScenarios":"Calling `parse_attr` with an empty attribute slice, or a regression where `attrs` (produced by `input.split(is_blank)`) yields empty segments that reach this function.","commonSituations":"Malformed `.gitattributes` lines with stray blank tokens (e.g. double spaces or trailing whitespace) if upstream filtering changes; contributors refactoring the parser.","solutions":["Filter out empty attribute segments before calling `parse_attr`.","Update gitoxide so blank-token filtering upstream prevents empty attrs from reaching `parse_attr`.","Replace the `.expect()` with an early `return Err` on empty input for a clean error."],"exampleFix":"// before\nlet attr = tokens.next().expect(\"attr itself\").as_bstr();\n\n// after\nlet Some(attr) = tokens.next() else {\n    return Err(name::Error { attribute: attr.into() });\n};\nlet attr = attr.as_bstr();","handlingStrategy":"validation","validationCode":"// skip empty segments before parse_attr\nif attr.is_empty() {\n    continue; // or return a parse error\n}","typeGuard":null,"tryCatchPattern":"match parse_attr(attr) {\n    Ok(assignment) => push(assignment),\n    Err(e) => return Err(e),\n}","preventionTips":["Filter empty tokens produced by splitting on blanks before parsing","Validate .gitattributes lines (no stray whitespace-only tokens)","Prefer explicit errors over .expect() in parser code paths"],"tags":["gitattributes","parsing","panic","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}