Parse `atomic git push` trailers from a commit message. Only matches when the trailers form the message's final paragraph — the shape `atomic git push` itself produces. Trailer lines embedded mid-body (e.g. a GitHub squash-merge message quoting the original commit) do NOT match: those commits may carry conflict resolutions and must be imported.
(message: &str)
| 4221 | /// (e.g. a GitHub squash-merge message quoting the original commit) do NOT |
| 4222 | /// match: those commits may carry conflict resolutions and must be imported. |
| 4223 | fn parse_push_trailer(message: &str) -> Option<PushTrailer> { |
| 4224 | let last_paragraph = message.trim_end().rsplit("\n\n").next()?; |
| 4225 | |
| 4226 | let mut view = None; |
| 4227 | let mut state = None; |
| 4228 | for line in last_paragraph.lines() { |
| 4229 | let line = line.trim(); |
| 4230 | if let Some(value) = line.strip_prefix("Atomic-View:") { |
| 4231 | view = Some(value.trim().to_string()); |
| 4232 | } else if let Some(value) = line.strip_prefix("Atomic-State:") { |
| 4233 | state = Merkle::from_base32(value.trim().as_bytes()); |
| 4234 | } else if line.starts_with("Atomic-Changes:") { |
| 4235 | // Optional trailer; not needed for self-push detection (we only |
| 4236 | // care about view + state here). The authoritative collection of |
| 4237 | // these hashes for ReviewGate provenance happens in |
| 4238 | // `parse_atomic_changes_trailer`, which scans the whole message. |
| 4239 | } else { |
| 4240 | // Non-trailer content in the final paragraph — not a commit |
| 4241 | // produced by `atomic git push`. |
| 4242 | return None; |
| 4243 | } |
| 4244 | } |
| 4245 | |
| 4246 | Some(PushTrailer { |
| 4247 | view: view?, |
| 4248 | state: state?, |
| 4249 | }) |
| 4250 | } |
| 4251 | |
| 4252 | /// Extract metadata from a git commit. |
| 4253 | fn extract_commit_metadata(commit: &git2::Commit) -> CliResult<CommitMetadata> { |