use crate::error::GitError;
use crate::status::{ChangeStatus, ColumnChange, WorktreeChange};
pub fn parse_porcelain_v1z(text: &str) -> Result<Vec<WorktreeChange>, GitError> {
let mut fields = text.split('\0').filter(|f| !f.is_empty());
let mut changes = Vec::new();
while let Some(record) = fields.next() {
if record.len() < 4 {
return Err(GitError::ParseError {
message: format!("record too short: {record:?}"),
});
}
let (code, path) = record.split_at(3);
let mut code_chars = code.chars();
let (index, worktree) = (
code_chars.next().unwrap_or(' '),
code_chars.next().unwrap_or(' '),
);
let status = classify(index, worktree).ok_or_else(|| GitError::ParseError {
message: format!("unknown status code: {code:?}"),
})?;
let old_path = if has_source_field(status) {
Some(
fields
.next()
.ok_or_else(|| GitError::ParseError {
message: format!("rename record missing source path: {record:?}"),
})?
.replace('\\', "/"),
)
} else {
None
};
changes.push(WorktreeChange {
path: path.replace('\\', "/"),
status,
old_path,
});
}
Ok(changes)
}
const fn classify(index: char, worktree: char) -> Option<ChangeStatus> {
match (index, worktree) {
('?', '?') => Some(ChangeStatus::Untracked),
('!', '!') => Some(ChangeStatus::Ignored),
('U', _) | (_, 'U') | ('A', 'A') | ('D', 'D') => Some(ChangeStatus::Conflicted),
(x, y) => match (column(x), column(y)) {
(Ok(None), Ok(None)) | (Err(()), _) | (_, Err(())) => None,
(Ok(idx), Ok(wt)) => Some(ChangeStatus::Tracked {
index: idx,
worktree: wt,
}),
},
}
}
const fn column(code: char) -> Result<Option<ColumnChange>, ()> {
match code {
' ' => Ok(None),
'A' => Ok(Some(ColumnChange::Added)),
'M' => Ok(Some(ColumnChange::Modified)),
'D' => Ok(Some(ColumnChange::Deleted)),
'R' => Ok(Some(ColumnChange::Renamed)),
'C' => Ok(Some(ColumnChange::Copied)),
'T' => Ok(Some(ColumnChange::TypeChanged)),
_ => Err(()),
}
}
const fn has_source_field(status: ChangeStatus) -> bool {
match status {
ChangeStatus::Tracked { index, worktree } => {
matches!(index, Some(ColumnChange::Renamed | ColumnChange::Copied))
|| matches!(worktree, Some(ColumnChange::Renamed | ColumnChange::Copied))
}
ChangeStatus::Conflicted | ChangeStatus::Untracked | ChangeStatus::Ignored => false,
}
}