git_prole/git/
mod.rs

1use std::fmt::Debug;
2use std::process::Command;
3
4use camino::Utf8Path;
5use camino::Utf8PathBuf;
6use command_error::CommandExt;
7use tracing::instrument;
8
9mod branch;
10mod commit_hash;
11mod commitish;
12mod config;
13mod git_like;
14mod head_state;
15mod path;
16mod refs;
17mod remote;
18mod repository_url_destination;
19mod status;
20mod worktree;
21
22pub use branch::GitBranch;
23pub use commit_hash::CommitHash;
24pub use commitish::ResolvedCommitish;
25pub use config::GitConfig;
26pub use git_like::GitLike;
27pub use head_state::HeadKind;
28pub use path::GitPath;
29pub use refs::BranchRef;
30pub use refs::GitRefs;
31pub use refs::LocalBranchRef;
32pub use refs::Ref;
33pub use refs::RemoteBranchRef;
34pub use remote::GitRemote;
35pub use repository_url_destination::repository_url_destination;
36pub use status::GitStatus;
37pub use status::Status;
38pub use status::StatusCode;
39pub use status::StatusEntry;
40pub use worktree::AddWorktreeOpts;
41pub use worktree::GitWorktree;
42pub use worktree::RenamedWorktree;
43pub use worktree::ResolveUniqueNameOpts;
44pub use worktree::Worktree;
45pub use worktree::WorktreeHead;
46pub use worktree::Worktrees;
47
48use crate::app_git::AppGit;
49use crate::config::Config;
50use crate::current_dir::current_dir_utf8;
51
52/// `git` CLI wrapper.
53#[derive(Clone)]
54pub struct Git<C> {
55    current_dir: C,
56    env_variables: Vec<(String, String)>,
57    args: Vec<String>,
58}
59
60impl<C> Debug for Git<C>
61where
62    C: AsRef<Utf8Path>,
63{
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.debug_tuple("Git")
66            .field(&self.current_dir.as_ref())
67            .finish()
68    }
69}
70
71impl<C> AsRef<Utf8Path> for Git<C>
72where
73    C: AsRef<Utf8Path>,
74{
75    fn as_ref(&self) -> &Utf8Path {
76        self.current_dir.as_ref()
77    }
78}
79
80impl<C> AsRef<Git<C>> for Git<C> {
81    fn as_ref(&self) -> &Self {
82        self
83    }
84}
85
86impl<C> GitLike for Git<C>
87where
88    C: AsRef<Utf8Path>,
89{
90    type CurrentDir = C;
91
92    fn as_git(&self) -> &Git<Self::CurrentDir> {
93        self
94    }
95
96    fn get_current_dir(&self) -> &Self::CurrentDir {
97        &self.current_dir
98    }
99}
100
101impl Git<Utf8PathBuf> {
102    pub fn from_current_dir() -> miette::Result<Self> {
103        Ok(Self::from_path(current_dir_utf8()?))
104    }
105}
106
107impl<C> Git<C>
108where
109    C: AsRef<Utf8Path>,
110{
111    pub fn from_path(current_dir: C) -> Self {
112        Self {
113            current_dir,
114            env_variables: Vec::new(),
115            args: Vec::new(),
116        }
117    }
118
119    pub fn with_config(self, config: &Config) -> AppGit<'_, C> {
120        AppGit { git: self, config }
121    }
122
123    /// Get a `git` command.
124    pub fn command(&self) -> Command {
125        let mut command = Command::new("git");
126        command.current_dir(self.current_dir.as_ref());
127        command.envs(self.env_variables.iter().map(|(key, value)| (key, value)));
128        command.args(&self.args);
129        command
130    }
131
132    /// Set the current working directory for `git` commands to be run in.
133    pub fn set_current_dir(&mut self, path: C) {
134        self.current_dir = path;
135    }
136
137    pub fn with_current_dir<C2>(&self, path: C2) -> Git<C2> {
138        Git {
139            current_dir: path,
140            env_variables: self.env_variables.clone(),
141            args: self.args.clone(),
142        }
143    }
144
145    pub fn env(&mut self, key: String, value: String) {
146        self.env_variables.push((key, value));
147    }
148
149    pub fn envs(&mut self, iter: impl IntoIterator<Item = (String, String)>) {
150        self.env_variables.extend(iter);
151    }
152
153    pub fn arg(&mut self, arg: String) {
154        self.args.push(arg);
155    }
156
157    pub fn args(&mut self, iter: impl IntoIterator<Item = String>) {
158        self.args.extend(iter);
159    }
160
161    pub(crate) fn rev_parse_command(&self) -> Command {
162        let mut command = self.command();
163        command.args(["rev-parse", "--path-format=absolute"]);
164        command
165    }
166
167    #[instrument(level = "trace")]
168    pub fn clone_repository(
169        &self,
170        repository: &str,
171        destination: Option<&Utf8Path>,
172        args: &[String],
173    ) -> miette::Result<()> {
174        let mut command = self.command();
175        command.arg("clone").args(args).arg(repository);
176        if let Some(destination) = destination {
177            command.arg(destination);
178        }
179        command.status_checked()?;
180        Ok(())
181    }
182
183    /// `git reset`.
184    #[instrument(level = "trace")]
185    pub fn reset(&self) -> miette::Result<()> {
186        self.command().arg("reset").output_checked_utf8()?;
187        Ok(())
188    }
189}