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#[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 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 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 #[instrument(level = "trace")]
185 pub fn reset(&self) -> miette::Result<()> {
186 self.command().arg("reset").output_checked_utf8()?;
187 Ok(())
188 }
189}