1use crate::cmd::{get_cmd_stdout_utf8, run_cmd, RunCommandError};
12use std::ffi::OsStr;
13use std::fmt::{self, Display, Formatter};
14use std::path::{Path, PathBuf};
15use std::process::Command;
16use std::{env, io};
17
18#[derive(Debug)]
20pub enum RepoOpenError {
21 CurrentDir(io::Error),
23
24 GitDirMissing(PathBuf),
26}
27
28impl Display for RepoOpenError {
29 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
30 write!(f, "failed to open git repo: ")?;
31 match self {
32 Self::CurrentDir(err) => {
33 write!(f, "failed to get current dir: {err}")
34 }
35 Self::GitDirMissing(path) => {
36 write!(f, "{} does not exist", path.display())
37 }
38 }
39 }
40}
41
42impl std::error::Error for RepoOpenError {}
43
44#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
46pub struct Repo(PathBuf);
47
48impl Repo {
49 pub fn open() -> Result<Self, RepoOpenError> {
54 let path = env::current_dir().map_err(RepoOpenError::CurrentDir)?;
55 Self::open_path(path)
56 }
57
58 pub fn open_path<P>(path: P) -> Result<Self, RepoOpenError>
63 where
64 P: Into<PathBuf>,
65 {
66 let path = path.into();
67
68 let git_dir = path.join(".git");
77 if !git_dir.exists() {
78 return Err(RepoOpenError::GitDirMissing(git_dir));
79 }
80
81 Ok(Self(path))
82 }
83
84 pub fn path(&self) -> &Path {
86 &self.0
87 }
88
89 fn get_git_command<I, S>(&self, args: I) -> Command
91 where
92 I: IntoIterator<Item = S>,
93 S: AsRef<OsStr>,
94 {
95 let mut cmd = Command::new("git");
96 cmd.arg("-C");
97 cmd.arg(self.path());
98 cmd.args(args);
99 cmd
100 }
101
102 pub fn get_commit_message_body(
104 &self,
105 commit_sha: &str,
106 ) -> Result<String, RunCommandError> {
107 let cmd = self.get_git_command([
108 "log",
109 "-1",
110 "--format=format:%b",
112 commit_sha,
113 ]);
114 let output = get_cmd_stdout_utf8(cmd)?;
115 Ok(output)
116 }
117
118 pub fn get_commit_message_subject(
120 &self,
121 commit_sha: &str,
122 ) -> Result<String, RunCommandError> {
123 let cmd = self.get_git_command([
124 "log",
125 "-1",
126 "--format=format:%s",
128 commit_sha,
129 ]);
130 let output = get_cmd_stdout_utf8(cmd)?;
131 Ok(output)
132 }
133
134 pub fn fetch_git_tags(&self) -> Result<(), RunCommandError> {
136 let cmd = self.get_git_command(["fetch", "--tags"]);
137 run_cmd(cmd)?;
138 Ok(())
139 }
140
141 pub fn does_git_tag_exist(
146 &self,
147 tag: &str,
148 ) -> Result<bool, RunCommandError> {
149 let cmd = self.get_git_command(["tag", "--list", tag]);
150 let output = get_cmd_stdout_utf8(cmd)?;
151
152 Ok(output.lines().any(|line| line == tag))
153 }
154
155 pub fn make_and_push_git_tag(
157 &self,
158 tag: &str,
159 commit_sha: &str,
160 ) -> Result<(), RunCommandError> {
161 let cmd = self.get_git_command(["tag", tag, commit_sha]);
163 run_cmd(cmd)?;
164
165 let cmd = self.get_git_command(["push", "--tags"]);
167 run_cmd(cmd)?;
168
169 Ok(())
170 }
171}