use crate::addr::access_ctrl::serv::NetAccessCtrl;
use crate::addr::{AddrReason, AddrResult, Address, GitRepository};
use crate::update::UploadOptions;
use crate::{
prelude::*,
tools::get_repo_name,
types::{ResourceDownloader, ResourceUploader},
update::DownloadOptions,
};
use async_trait::async_trait;
use fs_extra::dir::CopyOptions;
use getset::{Getters, Setters, WithSetters};
use git2::{
BranchType, FetchOptions, MergeOptions, RemoteUpdateFlags, Repository, ResetType,
build::{CheckoutBuilder, RepoBuilder},
};
use home::home_dir;
use orion_error::{ContextRecord, ToStructError, UvsFrom};
use orion_infra::path::ensure_path;
#[derive(Clone, Debug, Default, Getters, Setters, WithSetters)]
#[getset(get = "pub", set = "pub")]
pub struct GitAccessor {
#[getset(set_with = "pub")]
ctrl: Option<NetAccessCtrl>,
}
impl GitAccessor {
fn build_remote_callbacks(&self, addr: &GitRepository) -> git2::RemoteCallbacks<'_> {
let mut callbacks = git2::RemoteCallbacks::new();
let ssh_key = addr.ssh_key().clone();
let ssh_passphrase = addr.ssh_passphrase().clone();
let token = addr.token().clone();
let username = addr.username().clone();
callbacks.credentials(move |url, username_from_url, allowed_types| {
let is_https = url.starts_with("https://");
if is_https {
if allowed_types.contains(git2::CredentialType::USER_PASS_PLAINTEXT) {
let final_token = token.clone().or_else(|| {
if let Some(credentials) = GitRepository::read_git_credentials() {
credentials
.iter()
.find(|(cred_url, _, _)| url.contains(cred_url))
.map(|(_, _, token)| token.clone())
} else {
None
}
});
let final_username = username
.clone()
.or_else(|| {
if let Some(credentials) = GitRepository::read_git_credentials() {
credentials
.iter()
.find(|(cred_url, _, _)| url.contains(cred_url))
.map(|(_, username, _)| username.clone())
} else {
None
}
})
.unwrap_or_else(|| username.clone().unwrap_or_else(|| "git".to_string()));
if let Some(token) = final_token {
let actual_username = if final_username == "oauth2" {
"oauth2"
} else if final_username == "x-token-auth" {
"x-token-auth"
} else {
&final_username
};
git2::Cred::userpass_plaintext(actual_username, &token)
} else {
Err(git2::Error::from_str("需要Token认证但未提供token"))
}
} else {
Err(git2::Error::from_str("HTTPS协议不支持所需的认证类型"))
}
} else {
if allowed_types.contains(git2::CredentialType::SSH_KEY) {
let username = username_from_url.unwrap_or("git");
let key_path = if let Some(custom_key) = &ssh_key {
PathBuf::from(custom_key)
} else {
find_default_ssh_key()
.ok_or_else(|| git2::Error::from_str("无法找到默认SSH密钥"))?
};
git2::Cred::ssh_key(
username,
None, &key_path,
ssh_passphrase.as_deref(), )
} else {
Err(git2::Error::from_str("SSH协议不支持所需的认证类型"))
}
}
});
callbacks
}
fn update_repo(&self, addr: &GitRepository, repo: &Repository) -> AddrResult<()> {
if !self.is_workdir_clean(repo)? {
return Err(AddrReason::from_biz().to_err().want("工作区有未提交的更改"));
}
self.fetch_updates(addr, repo)?;
self.checkout_target(addr, repo)?;
self.pull_updates(addr, repo)
}
fn pull_updates(&self, _addr: &GitRepository, repo: &Repository) -> AddrResult<()> {
let head = repo.head().owe_data().want("get repo head")?;
let branch_name = match head.shorthand() {
Some(name) => name,
None => return Ok(()), };
let upstream_branch = format!("origin/{branch_name}");
let upstream_ref = match repo.find_reference(&upstream_branch) {
Ok(r) => r,
Err(_) => return Ok(()), };
let current_commit = head
.peel_to_commit()
.owe_data()
.want("current peel to commit")?;
let upstream_commit = upstream_ref
.peel_to_commit()
.owe_data()
.want("upstream peel to commit ")?;
if current_commit.id() == upstream_commit.id() {
return Ok(());
}
let annotated_commit = repo
.find_annotated_commit(upstream_commit.id())
.owe_data()
.want("find annotated commit")?;
let analysis = repo
.merge_analysis(&[&annotated_commit])
.owe_data()
.want("merge analysis")?;
if analysis.0.is_up_to_date() {
Ok(())
} else if analysis.0.is_fast_forward() {
self.fast_forward_merge(repo, &upstream_commit)
} else {
self.merge_upstream(repo, &upstream_commit)
}
}
fn fast_forward_merge(
&self,
repo: &Repository,
upstream_commit: &git2::Commit,
) -> AddrResult<()> {
let refname = match repo.head().owe_data()?.name() {
Some(name) => name.to_string(),
None => return AddrReason::from_biz().err_result().want("无法获取分支名称"),
};
repo.reference(&refname, upstream_commit.id(), true, "Fast-forward")
.owe_data()
.want("reference update")?;
repo.reset(upstream_commit.as_object(), ResetType::Hard, None)
.owe_data()
.want("reset")?;
Ok(())
}
fn merge_upstream(&self, repo: &Repository, upstream_commit: &git2::Commit) -> AddrResult<()> {
let annotated_commit = repo
.find_annotated_commit(upstream_commit.id())
.owe_data()
.want("find annotated commit")?;
repo.merge(&[&annotated_commit], Some(&mut MergeOptions::new()), None)
.owe_data()
.want("merge")?;
if repo.index().owe_data().want("repo index")?.has_conflicts() {
return AddrReason::from_biz()
.err_result()
.want("合并冲突:需要手动解决");
}
let head_commit = repo
.head()
.owe_data()
.want("head")?
.peel_to_commit()
.owe_data()?;
let mut index = repo.index().owe_data()?;
let tree_oid = index.write_tree().owe_data()?;
let tree = repo.find_tree(tree_oid).owe_data()?;
repo.commit(
Some("HEAD"),
&head_commit.author(),
&head_commit.committer(),
"合并远程变更",
&tree,
&[&head_commit, upstream_commit],
)
.owe_data()?;
repo.cleanup_state().owe_data()?;
Ok(())
}
fn is_workdir_clean(&self, repo: &Repository) -> AddrResult<bool> {
let mut options = git2::StatusOptions::new();
options.include_untracked(true);
options.include_ignored(false);
let statuses = repo.statuses(Some(&mut options)).owe_data()?;
Ok(statuses.is_empty())
}
fn get_local_repo_name(&self, addr: &GitRepository) -> String {
let mut name = get_repo_name(addr.repo().as_str()).unwrap_or("unknow".into());
if let Some(postfix) = addr
.rev()
.as_ref()
.or(addr.tag().as_ref())
.or(addr.branch().as_ref())
{
name = format!("{name}_{postfix}");
}
name
}
}
#[async_trait]
impl ResourceDownloader for GitAccessor {
async fn download_to_local(
&self,
addr: &Address,
path: &Path,
options: &DownloadOptions,
) -> AddrResult<UpdateUnit> {
let mut ctx = OperationContext::want("download local")
.with_mod_path("addr/git")
.with_auto_log();
let addr = match addr {
Address::Git(x) => x,
_ => {
return AddrReason::Brief(format!("bad format for git {addr}"))
.err_result()
.with(&ctx);
}
};
let name = self.get_local_repo_name(addr);
let cache_local = home_dir()
.ok_or(AddrReason::from_res().to_err().want("unget home"))?
.join(".cache/galaxy");
ensure_path(&cache_local).owe_logic().with(&ctx)?;
let mut git_local = cache_local.join(name.clone());
ctx.record("repo", addr.repo().as_str());
ctx.record("path", &git_local);
debug!( target : "addr/git", "update options {:?} where :{} ", options, git_local.display() );
if git_local.exists() && options.clean_git_cache() {
std::fs::remove_dir_all(&git_local).owe_logic().with(&ctx)?;
std::fs::create_dir_all(&git_local).owe_logic().with(&ctx)?;
ctx.warn("remove cache ");
} else {
debug!( target : "addr/git", "git_local:{} , clean : {} ", git_local.exists(), options.clean_git_cache() );
}
match git2::Repository::open(&git_local) {
Ok(_re) => {
debug!(target :"spec", " use repo : {}", git_local.display());
}
Err(_) => {
debug!(target :"spec", "clone repo : {}", git_local.display());
self.clone_repo(addr, &git_local).owe_data().with(&ctx)?;
}
}
let mut real_path = path.to_path_buf();
if let Some(sub) = addr.path() {
git_local = git_local.join(sub);
if let Some(sub_path) = PathBuf::from(sub).iter().next_back() {
real_path = real_path.join(sub_path);
}
} else {
real_path = real_path.join(name);
}
if real_path.exists() {
std::fs::remove_dir_all(&real_path).owe_res().with(&ctx)?;
}
std::fs::create_dir_all(&real_path).owe_res().with(&ctx)?;
let options = CopyOptions::new();
debug!(target:"spec", "src-path:{}", git_local.display() );
debug!(target:"spec", "dst-path:{}", path.display() );
ctx.record("src-path", &git_local);
ctx.record("dst-path", &real_path);
fs_extra::copy_items(&[&git_local], path, &options)
.owe_res()
.with(&ctx)?;
ctx.mark_suc();
Ok(UpdateUnit::from(real_path))
}
}
#[async_trait]
impl ResourceUploader for GitAccessor {
async fn upload_from_local(
&self,
addr: &Address,
path: &Path,
_options: &UploadOptions,
) -> AddrResult<UpdateUnit> {
let mut ctx = OperationContext::want("upload to repository")
.with_auto_log()
.with_mod_path("addr/git");
ctx.record("target", path.display().to_string());
if !path.exists() {
return Err(AddrReason::from_res().to_err().want("path not exist"));
}
let temp_path = home_dir().unwrap_or(PathBuf::from("~/")).join(".temp");
ensure_path(&temp_path).owe_logic()?;
let target_repo = self
.download_to_local(addr, &temp_path, &DownloadOptions::default())
.await?;
let addr = match addr {
Address::Git(x) => x,
_ => return AddrReason::Brief(format!("bad format for git {addr}")).err_result(),
};
let target_repo_in_local_path = &target_repo.position;
ctx.record(
"target_repo",
target_repo_in_local_path.display().to_string(),
);
if path.is_file() {
let filename = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("UNKONW");
ctx.record("upload_type", "file");
ctx.record("filename", filename);
std::fs::copy(path, target_repo_in_local_path.join(filename)).owe_res()?;
std::fs::remove_file(path).owe_res()?;
} else {
let copy_options = CopyOptions::new().overwrite(true).copy_inside(true);
ctx.record("upload_type", "directory");
fs_extra::copy_items(&[path], target_repo_in_local_path, ©_options).owe_res()?;
std::fs::remove_dir_all(path).owe_res()?;
}
match Repository::open(target_repo_in_local_path) {
Ok(_repo) => {
ctx.record("repo_open", "success");
}
Err(e) => {
ctx.record("repo_open", format!("failed: {e}"));
debug!(target :"spec", "Open Local repo : {} is failed! error: {}", addr.repo(), e)
}
}
let name = self.get_local_repo_name(addr);
ctx.record("cleanup_temp", name.clone());
std::fs::remove_dir_all(temp_path.join(name)).owe_res()?;
ctx.mark_suc();
Ok(UpdateUnit::from(path.to_path_buf()))
}
}
impl GitAccessor {
pub fn sync_repo(&self, addr: &GitRepository, target_dir: &Path) -> AddrResult<()> {
match Repository::open(target_dir) {
Ok(repo) => self.update_repo(addr, &repo),
Err(_) => self.clone_repo(addr, target_dir),
}
}
fn clone_repo(&self, addr: &GitRepository, target_dir: &Path) -> AddrResult<()> {
let repo_addr = if let Some(director) = &self.ctrl {
director.direct_git_addr(addr.clone())
} else {
addr.clone()
};
let mut ctx = OperationContext::want("clone repository")
.with_auto_log()
.with_mod_path("addr/git");
ctx.record("repo", repo_addr.repo().as_str());
ctx.record("target", target_dir.display().to_string());
let callbacks = self.build_remote_callbacks(&repo_addr);
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(callbacks);
if let Some(proxy_config) = self.ctrl.as_ref().and_then(|x| x.proxy_git(addr)) {
let mut proxy_options = git2::ProxyOptions::new();
proxy_options.url(proxy_config.url().as_str());
fetch_options.proxy_options(proxy_options);
}
let mut builder = RepoBuilder::new();
builder.fetch_options(fetch_options);
println!("clone repo from {}", repo_addr.repo());
let repo = builder.clone(repo_addr.repo(), target_dir).owe_data()?;
ctx.mark_suc();
self.checkout_target(&repo_addr, &repo)
}
fn fetch_updates(&self, addr: &GitRepository, repo: &Repository) -> AddrResult<()> {
let mut remote = repo.find_remote("origin").owe_data()?;
let callbacks = self.build_remote_callbacks(addr); let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(callbacks);
if let Some(proxy_config) = self.ctrl.as_ref().and_then(|x| x.proxy_git(addr)) {
let mut proxy_options = git2::ProxyOptions::new();
proxy_options.url(proxy_config.url().as_str());
fetch_options.proxy_options(proxy_options);
}
remote
.fetch(&[] as &[&str], Some(&mut fetch_options), None)
.owe_data()?;
remote
.update_tips(
None,
RemoteUpdateFlags::UPDATE_FETCHHEAD,
git2::AutotagOption::All,
None,
)
.owe_data()?;
Ok(())
}
fn checkout_target(&self, addr: &GitRepository, repo: &Repository) -> AddrResult<()> {
if let Some(rev) = addr.rev() {
self.checkout_revision(addr, repo, rev)
} else if let Some(tag) = addr.tag() {
self.checkout_tag(addr, repo, tag).owe_data()
} else if let Some(branch) = addr.branch() {
self.checkout_branch(addr, repo, branch)
} else {
let head = repo.head().owe_data()?;
let _name = head
.name()
.ok_or_else(|| AddrReason::from_data().to_err().want("无法获取 HEAD 名称"))?;
repo.checkout_head(Some(&mut CheckoutBuilder::new().force()))
.owe_data()?;
Ok(())
}
}
fn checkout_revision(
&self,
_addr: &GitRepository,
repo: &Repository,
rev: &str,
) -> AddrResult<()> {
let obj = repo.revparse_single(rev).owe_data()?;
repo.checkout_tree(&obj, Some(&mut CheckoutBuilder::new().force()))
.owe_data()?;
repo.set_head_detached(obj.id()).owe_data()?;
Ok(())
}
fn checkout_tag(
&self,
_addr: &GitRepository,
repo: &Repository,
tag: &str,
) -> Result<(), git2::Error> {
let refname = format!("refs/tags/{tag}");
let obj = repo.revparse_single(&refname)?;
repo.checkout_tree(&obj, Some(&mut CheckoutBuilder::new().force()))?;
repo.set_head_detached(obj.id())?;
Ok(())
}
fn checkout_branch(
&self,
_addr: &GitRepository,
repo: &Repository,
branch: &str,
) -> AddrResult<()> {
if let Ok(b) = repo.find_branch(branch, BranchType::Local) {
let refname = b
.get()
.name()
.ok_or_else(|| AddrReason::from_biz().to_err().want("无效的分支名称"))?;
repo.set_head(refname).owe_data()?;
repo.checkout_head(Some(&mut CheckoutBuilder::new().force()))
.owe_data()?;
return Ok(());
}
let remote_branch_name = format!("origin/{branch}");
if let Ok(b) = repo.find_branch(&remote_branch_name, BranchType::Remote) {
let commit = b.get().peel_to_commit().owe_data()?;
let mut new_branch = repo.branch(branch, &commit, false).owe_data()?;
new_branch
.set_upstream(Some(&format!("origin/{branch}")))
.owe_data()?;
let refname = format!("refs/heads/{branch}");
repo.set_head(&refname).owe_data()?;
repo.checkout_head(Some(&mut CheckoutBuilder::new().force()))
.owe_data()?;
return Ok(());
}
AddrReason::from_biz()
.err_result()
.want(format!("分支 '{branch}' 不存在"))
}
}
fn find_default_ssh_key() -> Option<PathBuf> {
let home = home_dir()?;
let ssh_dir = home.join(".ssh");
let key_files = [
"id_ed25519", "id_rsa", "id_ecdsa", "identity", ];
for key_file in &key_files {
let key_path = ssh_dir.join(key_file);
if key_path.exists() {
return Some(key_path);
}
}
None
}
#[cfg(test)]
mod tests {
use crate::addr::access_ctrl::{AuthConfig, Rule};
use crate::{addr::AddrResult, tools::test_init};
use super::*;
use orion_error::{ErrorOwe, TestAssert};
use tempfile::tempdir;
#[ignore = "need more time"]
#[tokio::test]
async fn test_git_addr_update_local() -> AddrResult<()> {
let temp_dir = tempdir().owe_res()?;
let dest_path = temp_dir.path().to_path_buf();
let git_addr = GitRepository::from("https://github.com/galaxy-sec/hello-word.git")
.with_branch("master");
let accessor = GitAccessor::default();
let cloned_v = accessor
.download_to_local(
&Address::from(git_addr),
&dest_path,
&DownloadOptions::default(),
)
.await?;
assert!(cloned_v.position().exists());
assert!(cloned_v.position().join(".git").exists());
let repo = git2::Repository::open(cloned_v.position()).owe_res()?;
let head = repo.head().owe_res()?;
assert!(head.is_branch() || head.is_tag());
Ok(())
}
#[tokio::test]
async fn test_git_addr_update_local_sub() -> AddrResult<()> {
test_init();
let dest_path = PathBuf::from("./tests/temp/git");
if dest_path.exists() {
std::fs::remove_dir_all(&dest_path).assert();
}
std::fs::create_dir_all(&dest_path).assert();
let git_addr = GitRepository::from("https://github.com/galaxy-sec/hello-word.git")
.with_branch("main")
.with_path("x86");
let addr_type = Address::Git(git_addr.clone());
let accessor = GitAccessor::default();
let git_up = accessor
.download_to_local(&addr_type, &dest_path, &DownloadOptions::default())
.await
.assert();
assert_eq!(git_up.position(), &dest_path.join("x86"));
Ok(())
}
#[tokio::test]
async fn test_git_addr_pull_2() -> AddrResult<()> {
test_init();
let dest_path = PathBuf::from("./tests/temp/git2");
if dest_path.exists() {
std::fs::remove_dir_all(&dest_path).assert();
}
std::fs::create_dir_all(&dest_path).assert();
let git_addr =
GitRepository::from("https://github.com/galaxy-sec/hello-word.git").with_branch("main");
let accessor = GitAccessor::default();
let git_up = accessor
.download_to_local(
&Address::from(git_addr),
&dest_path,
&DownloadOptions::default(),
)
.await
.assert();
assert_eq!(git_up.position(), &dest_path.join("hello-word.git_main"));
Ok(())
}
#[tokio::test]
async fn test_git_addr_pull_redirect() -> AddrResult<()> {
test_init();
let dest_path = PathBuf::from("./tests/temp/git3");
if dest_path.exists() {
std::fs::remove_dir_all(&dest_path).assert();
}
std::fs::create_dir_all(&dest_path).assert();
let redirect = NetAccessCtrl::from_rule(
Rule::new(
"https://github.com/galaxy-sec/hello-none*",
"https://github.com/galaxy-sec/hello-word",
),
Some(AuthConfig::new(
"generic-1747535977632",
"5b2c9e9b7f111af52f0375c1fd9d35cd4d0dabc3",
)),
None,
);
let git_addr =
GitRepository::from("https://github.com/galaxy-sec/hello-none.git").with_branch("main");
let accessor = GitAccessor::default().with_ctrl(Some(redirect));
let git_up = accessor
.download_to_local(
&Address::from(git_addr),
&dest_path,
&DownloadOptions::default(),
)
.await
.assert();
assert_eq!(git_up.position(), &dest_path.join("hello-none.git_main"));
Ok(())
}
#[tokio::test]
async fn test_checkout_specific_branch() -> AddrResult<()> {
test_init();
let dest_path = PathBuf::from("./tests/temp/git_branch_test");
if dest_path.exists() {
std::fs::remove_dir_all(&dest_path).unwrap();
}
let git_addr = GitRepository::from("https://github.com/galaxy-sec/hello-word.git")
.with_branch("develop");
let addr_type = Address::Git(git_addr.clone());
let accessor = GitAccessor::default();
let git_up = accessor
.download_to_local(&addr_type, &dest_path, &DownloadOptions::default())
.await?;
let repo = git2::Repository::open(git_up.position().clone()).assert();
let head = repo.head().assert();
assert!(head.shorthand().unwrap_or("").contains("develop"));
Ok(())
}
use crate::types::{ResourceDownloader, ResourceUploader};
use crate::{addr::GitRepository, update::DownloadOptions};
#[ignore = "no run in ci"]
#[tokio::test]
async fn test_dir_upload_to_remote_repo() -> AddrResult<()> {
let temp_dir = tempdir().assert();
let dir = temp_dir.path().join("version_1");
let file = dir.join("test.txt");
std::fs::create_dir_all(&dir).assert();
std::fs::write(&file, "spec upload local dir to git repo.").assert();
let git_addr =
GitRepository::from("git@github.com:galaxy-sec/spec_test.git").with_branch("main");
let addr_type = Address::Git(git_addr.clone());
let accessor = GitAccessor::default();
let git_up = accessor
.upload_from_local(&addr_type, &dir, &UploadOptions::new())
.await?;
println!("{:?}", git_up.position);
Ok(())
}
#[ignore = "no run in ci"]
#[tokio::test]
async fn test_file_upload_to_remote_repo() -> AddrResult<()> {
let temp_dir = tempdir().assert();
let file = temp_dir.path().join("test.txt");
std::fs::write(&file, "spec upload local file to git repo.").assert();
let git_addr =
GitRepository::from("git@github.com:galaxy-sec/spec_test.git").with_branch("main");
let addr_type = Address::Git(git_addr.clone());
let accessor = GitAccessor::default();
let git_up = accessor
.upload_from_local(&addr_type, &file, &UploadOptions::new())
.await?;
println!("{:?}", git_up.position);
Ok(())
}
#[test]
fn test_git_addr_env_token() {
let addr = GitRepository::from("https://github.com/user/repo.git");
let addr = addr.with_env_token("NON_EXISTENT_VAR");
assert!(addr.token().is_none()); }
#[test]
fn test_git_credentials_parsing() {
let _result = GitRepository::read_git_credentials();
}
#[test]
fn test_git_addr_with_git_credentials() {
let addr = GitRepository::from("https://github.com/user/repo.git");
let _addr = addr.with_git_credentials();
}
#[ignore = "need cnb.cool access"]
#[tokio::test]
async fn test_git_addr_cnb_cool_clone() -> AddrResult<()> {
test_init();
let dest_path = PathBuf::from("./tests/temp/cnb_cool_test");
if dest_path.exists() {
std::fs::remove_dir_all(&dest_path).unwrap();
}
std::fs::create_dir_all(&dest_path).unwrap();
let git_addr =
GitRepository::from("https://cnb.cool/dy-sec/ops/sys-operators/mac-devkit.git")
.with_branch("main");
let addr_type = Address::Git(git_addr.clone());
let git_up = GitAccessor::default()
.download_to_local(&addr_type, &dest_path, &DownloadOptions::default())
.await?;
assert!(git_up.position().exists());
assert!(git_up.position().join(".git").exists());
let repo = git2::Repository::open(git_up.position()).owe_res()?;
let head = repo.head().owe_res()?;
assert!(head.is_branch());
Ok(())
}
#[ignore = "need cnb.cool token access"]
#[tokio::test]
async fn test_git_addr_cnb_cool_with_token() -> AddrResult<()> {
test_init();
let dest_path = PathBuf::from("./tests/temp/cnb_cool_token_test");
if dest_path.exists() {
std::fs::remove_dir_all(&dest_path).unwrap();
}
std::fs::create_dir_all(&dest_path).unwrap();
let git_addr = GitRepository::from("https://cnb.cool/dy-sec/ops/mechanism/gxl-dayu.git")
.with_branch("main");
let git_up = GitAccessor::default()
.download_to_local(
&Address::from(git_addr),
&dest_path,
&DownloadOptions::default(),
)
.await?;
assert!(git_up.position().exists());
assert!(git_up.position().join(".git").exists());
Ok(())
}
#[test]
fn test_git_accessor_authentication_modes() {
use crate::addr::access_ctrl::serv::NetAccessCtrl;
let git_addr_https = GitRepository::from("https://github.com/user/repo.git")
.with_token("ghp_test_token123".to_string());
let git_addr_ssh = GitRepository::from("git@github.com:user/repo.git").with_ssh_key(
"-----BEGIN OPENSSH PRIVATE KEY-----\ntest_key_data\n-----END OPENSSH PRIVATE KEY-----"
.to_string(),
);
let https_accessor = GitAccessor::default();
let _https_callbacks = https_accessor.build_remote_callbacks(&git_addr_https);
assert_eq!(git_addr_https.token().clone().unwrap(), "ghp_test_token123");
let ssh_accessor = GitAccessor::default();
let _ssh_callbacks = ssh_accessor.build_remote_callbacks(&git_addr_ssh);
assert_eq!(
git_addr_ssh.ssh_key().clone().unwrap(),
"-----BEGIN OPENSSH PRIVATE KEY-----\ntest_key_data\n-----END OPENSSH PRIVATE KEY-----"
);
let net_ctrl = NetAccessCtrl::new(vec![], false);
let controlled_accessor = GitAccessor::default().with_ctrl(Some(net_ctrl));
assert!(controlled_accessor.ctrl().is_some());
}
#[tokio::test]
async fn test_git_accessor_error_handling() -> AddrResult<()> {
use tempfile::tempdir;
test_init();
let invalid_git_addr = GitRepository::from("invalid://not-a-git-url.com/repo.git");
let invalid_accessor = GitAccessor::default();
let temp_dir = tempdir().owe_res()?;
let result = invalid_accessor
.download_to_local(
&Address::Git(invalid_git_addr),
temp_dir.path(),
&DownloadOptions::default(),
)
.await;
assert!(result.is_err());
let non_existent_addr =
GitRepository::from("nonexistent-protocol://invalid-server.com/repo.git");
let non_existent_result = invalid_accessor
.download_to_local(
&Address::Git(non_existent_addr),
temp_dir.path(),
&DownloadOptions::default(),
)
.await;
assert!(non_existent_result.is_err());
let readonly_path = PathBuf::from("/readonly");
if !readonly_path.exists() {
std::fs::create_dir_all(&readonly_path).ok();
}
if readonly_path.exists() {
let result = invalid_accessor
.download_to_local(
&Address::Git(GitRepository::from(
"https://github.com/galaxy-sec/hello-word.git",
)),
&readonly_path,
&DownloadOptions::default(),
)
.await;
assert!(result.is_err());
}
Ok(())
}
}