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