git_proc/lib.rs
1#![doc = include_str!("../README.md")]
2
3/// Generate a pair of flag methods: unconditional and conditional.
4///
5/// The unconditional method calls the conditional one with `true`.
6///
7/// # Example
8///
9/// ```ignore
10/// flag_methods! {
11/// /// Enable foo mode.
12/// ///
13/// /// Corresponds to `--foo`.
14/// pub fn foo / foo_if, foo_field, "Conditionally enable foo mode."
15/// }
16/// ```
17///
18/// Generates:
19/// - `pub fn foo(self) -> Self` - calls `foo_if(true)`
20/// - `pub fn foo_if(mut self, value: bool) -> Self` - sets `self.foo_field = value`
21#[doc(hidden)]
22#[macro_export]
23macro_rules! flag_methods {
24 (
25 $(#[$attr:meta])*
26 $vis:vis fn $name:ident / $name_if:ident, $field:ident, $doc_if:literal
27 ) => {
28 $(#[$attr])*
29 #[must_use]
30 $vis fn $name(self) -> Self {
31 self.$name_if(true)
32 }
33
34 #[doc = $doc_if]
35 #[must_use]
36 $vis fn $name_if(mut self, value: bool) -> Self {
37 self.$field = value;
38 self
39 }
40 };
41}
42
43/// Generate an inherent `repo_path` method and a `RepoPath` trait implementation.
44///
45/// The inherent method sets the `repo_path` field to `Some(path)`.
46/// The trait implementation delegates to the inherent method.
47///
48/// This ensures callers can use `.repo_path()` without importing the `RepoPath` trait,
49/// while the trait remains available for generic bounds.
50#[doc(hidden)]
51#[macro_export]
52macro_rules! impl_repo_path {
53 ($ty:ident) => {
54 impl<'a> $ty<'a> {
55 /// Set the repository path (`-C <path>`).
56 #[must_use]
57 pub fn repo_path(mut self, path: &'a std::path::Path) -> Self {
58 self.repo_path = Some(path);
59 self
60 }
61 }
62
63 impl<'a> $crate::RepoPath<'a> for $ty<'a> {
64 fn repo_path(self, path: &'a std::path::Path) -> Self {
65 self.repo_path(path)
66 }
67 }
68 };
69}
70
71/// Generate a `Cow<'static, str>` tuple newtype with validation, plus a
72/// type-distinct error wrapper.
73///
74/// Invocation: `pub struct $name, $error($inner), "$invalid"`.
75///
76/// Generated:
77/// - `$vis struct $name(Cow<'static, str>)` with `as_str` / `from_static_or_panic`
78/// - `Display`, `AsRef<OsStr>`, `FromStr`, `TryFrom<String>`
79/// - `serde::Serialize` and `serde::Deserialize` (validating via `try_from = "String"`)
80/// - `$vis struct $error(pub $inner)` — a transparent `thiserror::Error`
81/// wrapper, so distinct newtypes can share an inner validation enum
82/// (e.g. `RefFormatError`) yet remain type-distinct
83///
84/// The user is expected to define
85/// `impl $name { const fn validate(input: &str) -> Result<(), $error> { ... } }`
86/// in the same module.
87#[doc(hidden)]
88#[macro_export]
89macro_rules! cow_str_newtype {
90 (
91 $(#[$attr:meta])*
92 $vis:vis struct $name:ident, $error:ident($inner:ty), $invalid:literal
93 ) => {
94 $(#[$attr])*
95 #[derive(Clone, Debug, Eq, PartialEq, ::serde::Deserialize)]
96 #[serde(try_from = "String")]
97 $vis struct $name(::std::borrow::Cow<'static, str>);
98
99 #[doc = concat!("Error returned when parsing an invalid `", stringify!($name), "`.")]
100 #[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
101 #[error(transparent)]
102 $vis struct $error(pub $inner);
103
104 impl $name {
105 /// Returns the inner string slice.
106 #[must_use]
107 pub fn as_str(&self) -> &str {
108 &self.0
109 }
110
111 /// Constructs from a static string, panicking if invalid.
112 #[must_use]
113 pub const fn from_static_or_panic(input: &'static str) -> Self {
114 assert!(Self::validate(input).is_ok(), $invalid);
115 Self(::std::borrow::Cow::Borrowed(input))
116 }
117 }
118
119 impl ::std::fmt::Display for $name {
120 fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
121 write!(formatter, "{}", self.0)
122 }
123 }
124
125 impl ::std::convert::AsRef<::std::ffi::OsStr> for $name {
126 fn as_ref(&self) -> &::std::ffi::OsStr {
127 self.as_str().as_ref()
128 }
129 }
130
131 impl ::std::str::FromStr for $name {
132 type Err = $error;
133
134 fn from_str(input: &str) -> ::std::result::Result<Self, Self::Err> {
135 Self::validate(input)?;
136 Ok(Self(::std::borrow::Cow::Owned(input.to_string())))
137 }
138 }
139
140 impl ::std::convert::TryFrom<String> for $name {
141 type Error = $error;
142
143 fn try_from(value: String) -> ::std::result::Result<Self, Self::Error> {
144 value.parse()
145 }
146 }
147
148 impl ::serde::Serialize for $name {
149 fn serialize<S: ::serde::Serializer>(
150 &self,
151 serializer: S,
152 ) -> ::std::result::Result<S::Ok, S::Error> {
153 serializer.serialize_str(self.as_str())
154 }
155 }
156 };
157}
158
159pub mod add;
160pub mod branch;
161pub mod checkout;
162pub mod clean;
163pub mod clone;
164pub mod commit;
165pub mod commit_id;
166pub mod commit_ish;
167pub mod config;
168pub mod diff;
169pub mod fetch;
170pub mod init;
171pub mod ls_remote;
172pub mod push;
173pub mod ref_format;
174pub mod remote;
175pub mod repository;
176pub mod reset;
177pub mod rev_list;
178pub mod rev_parse;
179pub mod show;
180pub mod show_ref;
181pub mod status;
182pub mod tag;
183pub mod worktree;
184
185use std::path::Path;
186
187pub use cmd_proc::CommandError;
188
189/// Trait for git command builders that support porcelain output.
190///
191/// Provides the `porcelain` and `porcelain_if` methods to set `--porcelain`.
192pub trait Porcelain: Sized {
193 /// Conditionally enable porcelain output (`--porcelain`).
194 fn porcelain_if(self, value: bool) -> Self;
195
196 /// Enable porcelain output (`--porcelain`).
197 fn porcelain(self) -> Self {
198 self.porcelain_if(true)
199 }
200}
201
202/// Generate inherent `porcelain` / `porcelain_if` methods and a `Porcelain` trait implementation.
203///
204/// The inherent methods set the `porcelain` field.
205/// The trait implementation delegates to the inherent methods.
206///
207/// This ensures callers can use `.porcelain()` without importing the `Porcelain` trait,
208/// while the trait remains available for generic bounds.
209#[doc(hidden)]
210#[macro_export]
211macro_rules! impl_porcelain {
212 ($ty:ident) => {
213 impl<'a> $ty<'a> {
214 /// Give output in machine-parseable format.
215 ///
216 /// Corresponds to `--porcelain`.
217 #[must_use]
218 pub fn porcelain(self) -> Self {
219 self.porcelain_if(true)
220 }
221
222 /// Conditionally enable porcelain output.
223 #[must_use]
224 pub fn porcelain_if(mut self, value: bool) -> Self {
225 self.porcelain = value;
226 self
227 }
228 }
229
230 impl<'a> $crate::Porcelain for $ty<'a> {
231 fn porcelain_if(self, value: bool) -> Self {
232 self.porcelain_if(value)
233 }
234 }
235 };
236}
237
238/// Trait for git command builders that support a repository path.
239///
240/// Provides the `repo_path` method to set `-C <path>`.
241#[must_use = "repo_path returns a modified builder; the return value must be used"]
242pub trait RepoPath<'a>: Sized {
243 /// Set the repository path (`-C <path>`).
244 fn repo_path(self, path: &'a Path) -> Self;
245}
246
247/// Trait for building a command without executing it.
248///
249/// All git command builders implement this trait, allowing you to
250/// access the underlying `cmd_proc::Command` for custom execution.
251///
252/// # Example
253///
254/// ```ignore
255/// use git_proc::Build;
256///
257/// git_proc::fetch::new()
258/// .all()
259/// .build()
260/// .stderr_null()
261/// .status()
262/// .await?;
263/// ```
264pub trait Build {
265 /// Build the command without executing it.
266 fn build(self) -> cmd_proc::Command;
267}
268
269/// Create a command builder with optional repository path.
270///
271/// If `repo_path` is `Some`, adds `-C <path>` to the command.
272/// If `repo_path` is `None`, uses current working directory.
273fn base_command(repo_path: Option<&Path>) -> cmd_proc::Command {
274 cmd_proc::Command::new("git").optional_option("-C", repo_path)
275}