1use crate::core::find_repo_root;
2use crate::network::transport::RemoteRepo;
3use crate::network::AirgapValidator;
4use crate::response::FetchResponse;
5use crate::storage::ObjectStore;
6
7pub fn execute(
8 remote: String,
9 branch: Option<String>,
10) -> Result<FetchResponse, crate::errors::LitError> {
11 let repo_root = find_repo_root()?;
12 let remote_url = get_remote_url(&repo_root, &remote)?;
13
14 let validator = AirgapValidator::new()?;
15 validator.validate_transport(&remote_url)?;
16
17 let remote_repo = RemoteRepo::open(&remote_url)?;
18 let local_store = ObjectStore::new(&repo_root);
19
20 let branches_to_fetch: Vec<(String, String)> = if let Some(ref b) = branch {
22 let hash = remote_repo.read_branch_ref(b)?;
23 vec![(b.clone(), hash)]
24 } else {
25 remote_repo.list_branches()?
26 };
27
28 if branches_to_fetch.is_empty() {
29 return Ok(FetchResponse {
30 remote: remote.clone(),
31 branches_updated: vec![],
32 objects_transferred: 0,
33 message: format!("No branches found on remote '{}'", remote),
34 });
35 }
36
37 let wants: Vec<String> = branches_to_fetch.iter().map(|(_, h)| h.clone()).collect();
39 let needed = remote_repo.negotiate_download(&local_store, &wants)?;
40 let total_transferred = remote_repo.download_objects(&local_store, &needed)?;
41
42 let mut updated_branches = Vec::new();
44 for (branch_name, hash) in &branches_to_fetch {
45 crate::network::transport::update_remote_tracking_ref(
46 &repo_root,
47 &remote,
48 branch_name,
49 hash,
50 )?;
51 updated_branches.push(format!(
52 "{} -> {}/{}",
53 &hash[..16.min(hash.len())],
54 remote,
55 branch_name
56 ));
57 }
58
59 let message = if total_transferred > 0 {
60 format!(
61 "From {}\n {} objects transferred, {} branches updated",
62 remote_url,
63 total_transferred,
64 updated_branches.len()
65 )
66 } else {
67 format!("From {}\n Already up to date", remote_url)
68 };
69
70 Ok(FetchResponse {
71 remote,
72 branches_updated: updated_branches,
73 objects_transferred: total_transferred,
74 message,
75 })
76}
77
78fn get_remote_url(
79 repo_root: &std::path::Path,
80 remote_name: &str,
81) -> Result<String, crate::errors::LitError> {
82 use serde::{Deserialize, Serialize};
83 use std::collections::HashMap;
84 use std::fs;
85
86 #[derive(Debug, Deserialize, Serialize)]
87 struct Remote {
88 url: String,
89 }
90
91 #[derive(Debug, Deserialize, Serialize)]
92 struct RemoteConfig {
93 remotes: HashMap<String, Remote>,
94 }
95
96 let config_path = repo_root.join(".lit").join("remotes");
97
98 if !config_path.exists() {
99 return Err(format!(
100 "No remotes configured. Use 'lit remote add {} <url>'",
101 remote_name
102 )
103 .into());
104 }
105
106 let content = fs::read_to_string(&config_path)
107 .map_err(|e| format!("Failed to read remotes config: {}", e))?;
108
109 let config: RemoteConfig = serde_json::from_str(&content)
110 .map_err(|e| format!("Failed to parse remotes config: {}", e))?;
111
112 config
113 .remotes
114 .get(remote_name)
115 .map(|r| r.url.clone())
116 .ok_or_else(|| format!("Remote '{}' not found", remote_name).into())
117}