Skip to main content

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}