Skip to main content

grit_lib/
fetch_head.rs

1//! Parsing [`FETCH_HEAD`](https://git-scm.com/docs/git-fetch#_discussion) lines.
2
3use crate::objects::ObjectId;
4
5/// Collect 40-character hex object IDs from `FETCH_HEAD` content for lines that are **for merge**.
6///
7/// Git marks merge candidates with either:
8/// - `<oid>` + tab + tab + description (empty middle field; typical `branch '…' of …` lines), or
9/// - `<oid>` + tab + description when there is no `not-for-merge` marker.
10///
11/// Lines containing `not-for-merge` after the first tab are skipped.
12///
13/// This matches the rules used by [`crate::fmt_merge_msg`] and fixes incorrect handling where
14/// splitting on tab treated the empty middle field as a separate token.
15#[must_use]
16pub fn merge_object_ids_hex(input: &str) -> Vec<String> {
17    let mut out = Vec::new();
18    for line in input.lines() {
19        if line.trim().is_empty() {
20            continue;
21        }
22        let Some(first_tab) = line.find('\t') else {
23            continue;
24        };
25        let oid = &line[..first_tab];
26        if !ObjectId::is_full_hex(oid) {
27            continue;
28        }
29        let rest = &line[first_tab + 1..];
30        if rest.starts_with("not-for-merge") {
31            continue;
32        }
33        let desc = rest.strip_prefix('\t').unwrap_or(rest);
34        if desc.is_empty() {
35            continue;
36        }
37        out.push(oid.to_owned());
38    }
39    out
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn double_tab_for_merge_branch() {
48        let input = "a".repeat(40);
49        let line = format!("{input}\t\tbranch 'main' of ../repo2\n");
50        let oids = merge_object_ids_hex(&line);
51        assert_eq!(oids, vec![input]);
52    }
53
54    #[test]
55    fn not_for_merge_skipped() {
56        let input = format!(
57            "{}\tnot-for-merge\tbranch 'other' of ../x\n",
58            "b".repeat(40)
59        );
60        assert!(merge_object_ids_hex(&input).is_empty());
61    }
62
63    #[test]
64    fn bare_url_line_for_merge() {
65        let oid = "c".repeat(40);
66        let line = format!("{oid}\t\t../repo2\n");
67        assert_eq!(merge_object_ids_hex(&line), vec![oid]);
68    }
69}