1use crate::core::find_repo_root;
2use crate::core::ObjectHash;
3use crate::network::transport::RemoteRepo;
4use crate::network::AirgapValidator;
5use crate::response::PushResponse;
6use crate::storage::ObjectStore;
7use std::collections::HashSet;
8
9pub fn execute(
10 remote: String,
11 branch: String,
12 force: bool,
13) -> Result<PushResponse, crate::errors::LitError> {
14 let repo_root = find_repo_root()?;
15 let remote_url = get_remote_url(&repo_root, &remote)?;
16
17 let validator = AirgapValidator::new()?;
18 validator.validate_transport(&remote_url)?;
19
20 let remote_repo = RemoteRepo::open(&remote_url)?;
21
22 let local_hash = crate::core::refs::read_ref(&repo_root, &format!("heads/{}", branch))
24 .map_err(|_| format!("Branch '{}' not found locally", branch))?;
25
26 let local_store = ObjectStore::new(&repo_root);
27
28 let remote_has_branch = remote_repo.read_branch_ref(&branch).ok();
30
31 if let Some(ref remote_hash) = remote_has_branch {
33 if remote_hash == &local_hash {
34 return Ok(PushResponse {
35 remote: remote.clone(),
36 branch: branch.clone(),
37 objects_transferred: 0,
38 updated: false,
39 message: "Everything up-to-date".to_string(),
40 });
41 }
42
43 if !force {
44 let remote_obj = ObjectHash::from_hex(remote_hash.clone());
45 let local_obj = ObjectHash::from_hex(local_hash.clone());
46 let is_ff = remote_repo.check_fast_forward(&local_store, &local_obj, &remote_obj)?;
47 if !is_ff {
48 return Err(
49 "Push rejected: non-fast-forward update. Use --force to override.".into(),
50 );
51 }
52 }
53 }
54
55 let remote_known: HashSet<String> = if let Some(ref rh) = remote_has_branch {
57 let mut set = HashSet::new();
58 set.insert(rh.clone());
59 set
60 } else {
61 HashSet::new()
62 };
63
64 let needed = remote_repo.negotiate_upload(
65 &local_store,
66 std::slice::from_ref(&local_hash),
67 &remote_known,
68 )?;
69
70 let transferred = remote_repo.upload_objects(&local_store, &needed)?;
72
73 remote_repo.update_branch_ref(&branch, &local_hash, force)?;
75
76 crate::network::transport::update_remote_tracking_ref(
78 &repo_root,
79 &remote,
80 &branch,
81 &local_hash,
82 )?;
83
84 let range = if let Some(old) = remote_has_branch {
85 format!(
86 "{}..{}",
87 &old[..16.min(old.len())],
88 &local_hash[..16.min(local_hash.len())]
89 )
90 } else {
91 format!("[new branch] -> {}", branch)
92 };
93
94 Ok(PushResponse {
95 remote: remote.clone(),
96 branch: branch.clone(),
97 objects_transferred: transferred,
98 updated: true,
99 message: format!(
100 "To {}\n {} {} objects transferred",
101 remote_url, range, transferred
102 ),
103 })
104}
105
106fn get_remote_url(
107 repo_root: &std::path::Path,
108 remote_name: &str,
109) -> Result<String, crate::errors::LitError> {
110 use serde::{Deserialize, Serialize};
111 use std::collections::HashMap;
112 use std::fs;
113
114 #[derive(Debug, Deserialize, Serialize)]
115 struct Remote {
116 url: String,
117 }
118
119 #[derive(Debug, Deserialize, Serialize)]
120 struct RemoteConfig {
121 remotes: HashMap<String, Remote>,
122 }
123
124 let config_path = repo_root.join(".lit").join("remotes");
125
126 if !config_path.exists() {
127 return Err(format!(
128 "No remotes configured. Use 'lit remote add {} <url>'",
129 remote_name
130 )
131 .into());
132 }
133
134 let content = fs::read_to_string(&config_path)
135 .map_err(|e| format!("Failed to read remotes config: {}", e))?;
136
137 let config: RemoteConfig = serde_json::from_str(&content)
138 .map_err(|e| format!("Failed to parse remotes config: {}", e))?;
139
140 config
141 .remotes
142 .get(remote_name)
143 .map(|r| r.url.clone())
144 .ok_or_else(|| format!("Remote '{}' not found", remote_name).into())
145}