1pub mod backend_id;
4pub mod models;
5
6pub use crate::backend_id::BackendId;
7
8#[doc(hidden)]
9pub use log as __log;
10
11#[cfg(feature = "backend-registry")]
12pub mod backend_descriptor;
13
14#[cfg(feature = "plugin-protocol")]
15pub mod plugin_protocol;
16
17#[cfg(feature = "plugin-protocol")]
18pub mod plugin_stdio;
19
20#[cfg(feature = "plugin-protocol")]
21pub mod host;
22
23#[cfg(feature = "plugin-protocol")]
24pub mod plugin_runtime;
25
26#[cfg(feature = "plugin-protocol")]
27pub mod events;
28
29#[doc(hidden)]
30pub fn __plugin_log_to_client(level: log::Level, target: &str, args: std::fmt::Arguments<'_>) {
31 #[cfg(feature = "plugin-protocol")]
32 {
33 let msg = format!("[{level}] {target}: {args}");
34 if let Ok(out) = crate::host::stdout() {
35 crate::plugin_stdio::send_message_shared(
36 out,
37 &crate::plugin_protocol::PluginMessage::Event {
38 event: match level {
39 log::Level::Error => crate::models::VcsEvent::Error { msg },
40 log::Level::Warn => crate::models::VcsEvent::Warning { msg },
41 log::Level::Info | log::Level::Debug | log::Level::Trace => {
42 crate::models::VcsEvent::Info { msg }
43 }
44 },
45 },
46 );
47 return;
48 }
49 }
50
51 let _ = (level, target, args);
52}
53
54#[macro_export]
55macro_rules! trace {
56 (target: $target:expr, $($arg:tt)+) => {{
57 $crate::__plugin_log_to_client($crate::__log::Level::Trace, $target, format_args!($($arg)+));
58 $crate::__log::trace!(target: $target, $($arg)+);
59 }};
60 ($($arg:tt)+) => {{
61 $crate::__plugin_log_to_client($crate::__log::Level::Trace, module_path!(), format_args!($($arg)+));
62 $crate::__log::trace!($($arg)+);
63 }};
64}
65
66#[macro_export]
67macro_rules! debug {
68 (target: $target:expr, $($arg:tt)+) => {{
69 $crate::__plugin_log_to_client($crate::__log::Level::Debug, $target, format_args!($($arg)+));
70 $crate::__log::debug!(target: $target, $($arg)+);
71 }};
72 ($($arg:tt)+) => {{
73 $crate::__plugin_log_to_client($crate::__log::Level::Debug, module_path!(), format_args!($($arg)+));
74 $crate::__log::debug!($($arg)+);
75 }};
76}
77
78#[macro_export]
79macro_rules! info {
80 (target: $target:expr, $($arg:tt)+) => {{
81 $crate::__plugin_log_to_client($crate::__log::Level::Info, $target, format_args!($($arg)+));
82 $crate::__log::info!(target: $target, $($arg)+);
83 }};
84 ($($arg:tt)+) => {{
85 $crate::__plugin_log_to_client($crate::__log::Level::Info, module_path!(), format_args!($($arg)+));
86 $crate::__log::info!($($arg)+);
87 }};
88}
89
90#[macro_export]
91macro_rules! warn {
92 (target: $target:expr, $($arg:tt)+) => {{
93 $crate::__plugin_log_to_client($crate::__log::Level::Warn, $target, format_args!($($arg)+));
94 $crate::__log::warn!(target: $target, $($arg)+);
95 }};
96 ($($arg:tt)+) => {{
97 $crate::__plugin_log_to_client($crate::__log::Level::Warn, module_path!(), format_args!($($arg)+));
98 $crate::__log::warn!($($arg)+);
99 }};
100}
101
102#[macro_export]
103macro_rules! error {
104 (target: $target:expr, $($arg:tt)+) => {{
105 $crate::__plugin_log_to_client($crate::__log::Level::Error, $target, format_args!($($arg)+));
106 $crate::__log::error!(target: $target, $($arg)+);
107 }};
108 ($($arg:tt)+) => {{
109 $crate::__plugin_log_to_client($crate::__log::Level::Error, module_path!(), format_args!($($arg)+));
110 $crate::__log::error!($($arg)+);
111 }};
112}
113
114#[cfg(feature = "plugin-protocol")]
115pub use crate::plugin_protocol::{PluginMessage, RpcRequest, RpcResponse};
116
117#[cfg(feature = "vcs")]
118pub use crate::models::{Capabilities, FetchOptions, OnEvent};
119
120#[cfg(feature = "vcs")]
121use std::path::{Path, PathBuf};
122
123#[cfg(feature = "vcs")]
124#[derive(thiserror::Error, Debug)]
125pub enum VcsError {
126 #[error("not a repository: {0}")]
127 NotARepo(String),
128 #[error("branch not found: {0}")]
129 NoSuchBranch(String),
130 #[error("no upstream configured")]
131 NoUpstream,
132 #[error("nothing to commit")]
133 NothingToCommit,
134 #[error("non-fast-forward; merge or rebase required")]
135 NonFastForward,
136 #[error("unsupported backend: {0}")]
137 Unsupported(BackendId),
138 #[error("io: {0}")]
139 Io(#[from] std::io::Error),
140 #[error("{backend}: {msg}")]
141 Backend { backend: BackendId, msg: String },
142}
143
144#[cfg(feature = "vcs")]
145pub type Result<T> = std::result::Result<T, VcsError>;
146
147#[cfg(feature = "vcs")]
149pub trait Vcs: Send + Sync {
150 fn id(&self) -> BackendId;
151 fn caps(&self) -> models::Capabilities;
152
153 fn open(path: &Path) -> Result<Self>
155 where
156 Self: Sized;
157 fn clone(url: &str, dest: &Path, on: Option<models::OnEvent>) -> Result<Self>
158 where
159 Self: Sized;
160
161 fn workdir(&self) -> &Path;
163
164 fn current_branch(&self) -> Result<Option<String>>;
166 fn branches(&self) -> Result<Vec<models::BranchItem>>;
167
168 #[deprecated(
169 since = "0.1.0",
170 note = "This function is being replaced by `branches`."
171 )]
172 fn local_branches(&self) -> Result<Vec<String>>;
173 fn create_branch(&self, name: &str, checkout: bool) -> Result<()>;
174 fn checkout_branch(&self, name: &str) -> Result<()>;
175
176 fn ensure_remote(&self, name: &str, url: &str) -> Result<()>;
178 fn list_remotes(&self) -> Result<Vec<(String, String)>>;
179 fn remove_remote(&self, name: &str) -> Result<()>;
180 fn fetch(&self, remote: &str, refspec: &str, on: Option<models::OnEvent>) -> Result<()>;
181 fn fetch_with_options(
182 &self,
183 remote: &str,
184 refspec: &str,
185 _opts: models::FetchOptions,
186 on: Option<models::OnEvent>,
187 ) -> Result<()> {
188 self.fetch(remote, refspec, on)
189 }
190 fn push(&self, remote: &str, refspec: &str, on: Option<models::OnEvent>) -> Result<()>;
191 fn pull_ff_only(&self, remote: &str, branch: &str, on: Option<models::OnEvent>) -> Result<()>;
192
193 fn commit(&self, message: &str, name: &str, email: &str, paths: &[PathBuf]) -> Result<String>;
195 fn commit_index(&self, message: &str, name: &str, email: &str) -> Result<String>;
196 fn status_summary(&self) -> Result<models::StatusSummary>;
197 fn status_payload(&self) -> Result<models::StatusPayload>;
198 fn log_commits(&self, query: &models::LogQuery) -> Result<Vec<models::CommitItem>>;
199 fn diff_file(&self, path: &Path) -> Result<Vec<String>>;
200 fn diff_commit(&self, rev: &str) -> Result<Vec<String>>;
201
202 fn conflict_details(&self, _path: &Path) -> Result<models::ConflictDetails> {
204 Err(VcsError::Unsupported(self.id()))
205 }
206 fn checkout_conflict_side(&self, _path: &Path, _side: models::ConflictSide) -> Result<()> {
207 Err(VcsError::Unsupported(self.id()))
208 }
209 fn write_merge_result(&self, _path: &Path, _content: &[u8]) -> Result<()> {
210 Err(VcsError::Unsupported(self.id()))
211 }
212
213 fn stage_patch(&self, patch: &str) -> Result<()>;
214 fn discard_paths(&self, paths: &[PathBuf]) -> Result<()>;
215 fn apply_reverse_patch(&self, patch: &str) -> Result<()>;
216
217 fn delete_branch(&self, name: &str, force: bool) -> Result<()>;
219 fn rename_branch(&self, old: &str, new: &str) -> Result<()>;
220 fn merge_into_current(&self, name: &str) -> Result<()>;
221 fn merge_into_current_with_message(&self, name: &str, message: Option<&str>) -> Result<()> {
222 let _ = message;
223 self.merge_into_current(name)
224 }
225 fn merge_abort(&self) -> Result<()> {
226 Err(VcsError::Unsupported(self.id()))
227 }
228 fn merge_continue(&self) -> Result<()> {
229 Err(VcsError::Unsupported(self.id()))
230 }
231 fn merge_in_progress(&self) -> Result<bool> {
232 Ok(false)
233 }
234 fn set_branch_upstream(&self, _branch: &str, _upstream: &str) -> Result<()> {
235 Err(VcsError::Unsupported(self.id()))
236 }
237 fn branch_upstream(&self, _branch: &str) -> Result<Option<String>> {
238 Err(VcsError::Unsupported(self.id()))
239 }
240
241 fn hard_reset_head(&self) -> Result<()>;
243 fn reset_soft_to(&self, _rev: &str) -> Result<()> {
244 Err(VcsError::Unsupported(self.id()))
245 }
246
247 fn get_identity(&self) -> Result<Option<(String, String)>>;
249 fn set_identity_local(&self, name: &str, email: &str) -> Result<()>;
250
251 fn stash_list(&self) -> Result<Vec<models::StashItem>> {
253 Err(VcsError::Unsupported(self.id()))
254 }
255 fn stash_push(
256 &self,
257 _message: &str,
258 _include_untracked: bool,
259 _paths: &[PathBuf],
260 ) -> Result<()> {
261 Err(VcsError::Unsupported(self.id()))
262 }
263 fn stash_apply(&self, _selector: &str) -> Result<()> {
264 Err(VcsError::Unsupported(self.id()))
265 }
266 fn stash_pop(&self, _selector: &str) -> Result<()> {
267 Err(VcsError::Unsupported(self.id()))
268 }
269 fn stash_drop(&self, _selector: &str) -> Result<()> {
270 Err(VcsError::Unsupported(self.id()))
271 }
272 fn stash_show(&self, _selector: &str) -> Result<Vec<String>> {
273 Err(VcsError::Unsupported(self.id()))
274 }
275
276 fn lfs_fetch(&self) -> Result<()> {
278 Err(VcsError::Unsupported(self.id()))
279 }
280 fn lfs_pull(&self) -> Result<()> {
281 Err(VcsError::Unsupported(self.id()))
282 }
283 fn lfs_prune(&self) -> Result<()> {
284 Err(VcsError::Unsupported(self.id()))
285 }
286 fn lfs_track(&self, _paths: &[PathBuf]) -> Result<()> {
287 Err(VcsError::Unsupported(self.id()))
288 }
289 fn lfs_untrack(&self, _paths: &[PathBuf]) -> Result<()> {
290 Err(VcsError::Unsupported(self.id()))
291 }
292 fn lfs_is_tracked(&self, _path: &Path) -> Result<bool> {
293 Err(VcsError::Unsupported(self.id()))
294 }
295
296 fn cherry_pick(&self, _rev: &str) -> Result<()> {
298 Err(VcsError::Unsupported(self.id()))
299 }
300 fn revert_commit(&self, _rev: &str, _no_edit: bool) -> Result<()> {
301 Err(VcsError::Unsupported(self.id()))
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 #[test]
308 fn log_macros_are_callable() {
309 crate::trace!("trace");
310 crate::debug!("debug");
311 crate::info!("info");
312 crate::warn!("warn");
313 crate::error!("error");
314 }
315
316 #[cfg(feature = "vcs")]
317 #[test]
318 fn vcs_error_formats_useful_messages() {
319 let e = crate::VcsError::Unsupported(crate::BackendId::from("git"));
320 assert!(e.to_string().contains("unsupported backend"));
321
322 let e = crate::VcsError::Backend {
323 backend: crate::BackendId::from("git"),
324 msg: "boom".into(),
325 };
326 assert_eq!(e.to_string(), "git: boom");
327 }
328}