Skip to main content

lit/commands/
fetch.rs

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    // Determine which branches to fetch
21    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    // Negotiate and download objects
38    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    // Update remote-tracking refs
43    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}