git_bot_feedback/client/
local.rs1use super::RestApiClient;
2use crate::{OutputVariable, RestClientError as ClientError, ReviewOptions, ThreadCommentOptions};
3
4#[cfg(feature = "file-changes")]
5use crate::{FileDiffLines, FileFilter, LinesChangedOnly, parse_diff};
6#[cfg(feature = "file-changes")]
7use std::{collections::HashMap, process::Command};
8
9#[derive(Debug, Clone, PartialEq, Eq, Default)]
29pub struct LocalClient;
30
31#[cfg(feature = "file-changes")]
33fn git_rev_parse(base: &str) -> Result<String, ClientError> {
34 match Command::new("git").args(["rev-parse", base]).output() {
35 Err(e) => Err(ClientError::Io {
36 task: format!("invoke `git rev-parse {base}` to validate reference"),
37 source: e,
38 }),
39 Ok(output) => {
40 if output.status.success() {
41 Ok(String::from_utf8_lossy(output.stdout.trim_ascii()).to_string())
42 } else if base.chars().all(|c| c.is_ascii_digit()) {
43 git_rev_parse(format!("HEAD~{base}").as_str())
48 } else {
49 let err_msg = String::from_utf8_lossy(&output.stderr).to_string();
50 Err(ClientError::GitCommand(err_msg))
52 }
53 }
54 }
55}
56
57#[async_trait::async_trait]
58impl RestApiClient for LocalClient {
59 #[cfg(feature = "file-changes")]
60 async fn get_list_of_changed_files(
61 &self,
62 file_filter: &FileFilter,
63 lines_changed_only: &LinesChangedOnly,
64 base_diff: Option<String>,
65 ignore_index: bool,
66 ) -> Result<HashMap<String, FileDiffLines>, ClientError> {
67 let git_status = if ignore_index {
68 0
69 } else {
70 match Command::new("git").args(["status", "--short"]).output() {
71 Err(e) => {
72 return Err(ClientError::io("invoke `git status`", e));
73 }
74 Ok(output) => {
75 if output.status.success() {
76 String::from_utf8_lossy(&output.stdout)
77 .to_string()
78 .trim_end_matches('\n')
80 .lines()
81 .filter(|l| !l.starts_with(' '))
83 .count()
84 } else {
85 let err_msg = String::from_utf8_lossy(&output.stderr).to_string();
86 return Err(ClientError::GitCommand(err_msg));
87 }
88 }
89 }
90 };
91 let mut diff_args = vec![];
92 let mut git_sub_cmd = vec!["--no-pager"];
93 if git_status != 0 {
94 diff_args.push("--staged".to_string());
97 }
98 if let Some(base) = base_diff {
99 let resolved_base = git_rev_parse(&base)?;
100 diff_args.push(resolved_base);
101 } else if git_status == 0 {
102 let resolved_head = git_rev_parse("HEAD~1")?;
105 diff_args.push(resolved_head);
106 }
107 if ignore_index {
108 diff_args.push("--format=%b".to_string());
111 git_sub_cmd.push("show");
112 } else {
113 git_sub_cmd.push("diff");
114 };
115 log::debug!(
116 "Getting diff with `git {} {}`",
117 git_sub_cmd.join(" "),
118 diff_args.join(" ")
119 );
120 match Command::new("git")
121 .args(&git_sub_cmd)
122 .args(&diff_args)
123 .output()
124 {
125 Err(e) => Err(ClientError::Io {
126 task: format!(
127 "invoke `git {} {}`",
128 git_sub_cmd.join(" "),
129 diff_args.join(" ")
130 ),
131 source: e,
132 }),
133 Ok(output) => {
134 if output.status.success() {
135 let diff_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
136 let files = parse_diff(&diff_str, file_filter, lines_changed_only)?;
137 Ok(files)
138 } else {
139 let err_msg = String::from_utf8_lossy(&output.stderr).to_string();
140 Err(ClientError::GitCommand(err_msg))
141 }
142 }
143 }
144 }
145
146 fn is_pr_event(&self) -> bool {
147 false
148 }
149
150 fn set_user_agent(&mut self, _user_agent: &str) -> Result<(), ClientError> {
151 Ok(())
152 }
153
154 async fn post_thread_comment(&self, _options: ThreadCommentOptions) -> Result<(), ClientError> {
155 Ok(())
156 }
157
158 async fn cull_pr_reviews(&mut self, _options: &mut ReviewOptions) -> Result<(), ClientError> {
159 Ok(())
160 }
161
162 async fn post_pr_review(&mut self, _options: &ReviewOptions) -> Result<(), ClientError> {
163 Ok(())
164 }
165
166 fn write_output_variables(&self, vars: &[OutputVariable]) -> Result<(), ClientError> {
167 for var in vars {
168 log::info!("{}: {}", var.name, var.value);
169 }
170 Ok(())
171 }
172}