thoughts_tool/git/
remote_refs.rs1use anyhow::Context;
2use anyhow::Result;
3use gix_protocol::handshake::Ref as HandshakeRef;
4use schemars::JsonSchema;
5use serde::Deserialize;
6use serde::Serialize;
7use std::path::Path;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
10pub struct RemoteRef {
11 pub name: String,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub oid: Option<String>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub peeled: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub target: Option<String>,
18}
19
20pub fn discover_remote_refs(repo_root: &Path, url: &str) -> Result<Vec<RemoteRef>> {
21 let repo = gix::open(repo_root).with_context(|| {
22 format!(
23 "Failed to open repository context for remote ref discovery: {}",
24 repo_root.display()
25 )
26 })?;
27 let remote = repo
28 .remote_at(url)
29 .with_context(|| format!("Failed to create remote for URL: {url}"))?;
30 let connection = remote
31 .connect(gix::remote::Direction::Fetch)
32 .with_context(|| format!("Failed to connect to remote: {url}"))?;
33 let options = gix::remote::ref_map::Options {
39 prefix_from_spec_as_filter_on_remote: false,
40 ..Default::default()
41 };
42
43 let (ref_map, _handshake) = connection
44 .ref_map(gix::progress::Discard, options)
45 .with_context(|| format!("Failed to list remote refs for: {url}"))?;
46
47 Ok(ref_map
48 .remote_refs
49 .into_iter()
50 .map(|remote_ref| match remote_ref {
51 HandshakeRef::Direct {
52 full_ref_name,
53 object,
54 } => RemoteRef {
55 name: bytes_to_string(full_ref_name.as_ref()),
56 oid: Some(object.to_string()),
57 peeled: None,
58 target: None,
59 },
60 HandshakeRef::Peeled {
61 full_ref_name,
62 tag,
63 object,
64 } => RemoteRef {
65 name: bytes_to_string(full_ref_name.as_ref()),
66 oid: Some(tag.to_string()),
67 peeled: Some(object.to_string()),
68 target: None,
69 },
70 HandshakeRef::Symbolic {
71 full_ref_name,
72 target,
73 tag,
74 object,
75 } => RemoteRef {
76 name: bytes_to_string(full_ref_name.as_ref()),
77 oid: Some(tag.unwrap_or(object).to_string()),
78 peeled: tag.map(|_| object.to_string()),
79 target: Some(bytes_to_string(target.as_ref())),
80 },
81 HandshakeRef::Unborn {
82 full_ref_name,
83 target,
84 } => RemoteRef {
85 name: bytes_to_string(full_ref_name.as_ref()),
86 oid: None,
87 peeled: None,
88 target: Some(bytes_to_string(target.as_ref())),
89 },
90 })
91 .collect())
92}
93
94fn bytes_to_string(bytes: &[u8]) -> String {
95 String::from_utf8_lossy(bytes).into_owned()
96}