kodegen_tools_git/operations/
remote.rs1use crate::{GitError, GitResult, RepoHandle};
4use gix::bstr::ByteSlice;
5use gix::config::parse::section::ValueName;
6use std::borrow::Cow;
7
8#[derive(Debug, Clone)]
10pub struct RemoteAddOpts {
11 pub name: String,
13 pub url: String,
15 pub force: bool,
17}
18
19pub async fn add_remote(repo: RepoHandle, opts: RemoteAddOpts) -> GitResult<()> {
21 let mut repo_clone = repo.clone_inner();
22
23 tokio::task::spawn_blocking(move || {
24 if !is_valid_git_url(&opts.url) {
26 return Err(GitError::InvalidInput(format!(
27 "Invalid Git URL format: {}",
28 opts.url
29 )));
30 }
31
32 if !opts.force
34 && repo_clone
35 .find_remote(opts.name.as_bytes().as_bstr())
36 .is_ok()
37 {
38 return Err(GitError::InvalidInput(format!(
39 "Remote '{}' already exists",
40 opts.name
41 )));
42 }
43
44 let mut config = repo_clone.config_snapshot_mut();
46
47 let mut section = config
49 .new_section("remote", Some(Cow::Owned(opts.name.clone().into())))
50 .map_err(|e| GitError::Gix(Box::new(e)))?;
51
52 let url_key = ValueName::try_from("url").map_err(|e| GitError::Gix(Box::new(e)))?;
54 section.push(url_key, Some(opts.url.as_bytes().as_bstr()));
55
56 let fetch_key = ValueName::try_from("fetch").map_err(|e| GitError::Gix(Box::new(e)))?;
58 let refspec = format!("+refs/heads/*:refs/remotes/{}/*", opts.name);
59 section.push(fetch_key, Some(refspec.as_bytes().as_bstr()));
60
61 drop(section);
63 config
64 .commit()
65 .map_err(|e| GitError::Gix(Box::new(e)))?;
66
67 Ok(())
68 })
69 .await
70 .map_err(|e| GitError::Gix(Box::new(e)))??;
71
72 Ok(())
73}
74
75pub async fn remove_remote(repo: RepoHandle, name: &str) -> GitResult<()> {
77 let mut repo_clone = repo.clone_inner();
78 let name = name.to_string();
79
80 tokio::task::spawn_blocking(move || {
81 if repo_clone
83 .find_remote(name.as_bytes().as_bstr())
84 .is_err()
85 {
86 return Err(GitError::InvalidInput(format!(
87 "Remote '{}' does not exist",
88 name
89 )));
90 }
91
92 let mut config = repo_clone.config_snapshot_mut();
94
95 let section_name = format!("remote.{}", name);
96
97 if config.remove_section(§ion_name, None).is_none() {
99 return Err(GitError::InvalidInput(format!(
100 "Remote '{}' not found in configuration",
101 name
102 )));
103 }
104
105 config
107 .commit()
108 .map_err(|e| GitError::Gix(Box::new(e)))?;
109
110 Ok(())
111 })
112 .await
113 .map_err(|e| GitError::Gix(Box::new(e)))??;
114
115 Ok(())
116}
117
118fn is_valid_git_url(url: &str) -> bool {
120 url.starts_with("https://")
121 || url.starts_with("http://")
122 || url.starts_with("git://")
123 || url.starts_with("ssh://")
124 || url.starts_with("file://")
125 || (url.contains('@') && url.contains(':')) }