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#[async_trait::async_trait]
32impl RestApiClient for LocalClient {
33 #[cfg(feature = "file-changes")]
34 async fn get_list_of_changed_files(
35 &self,
36 file_filter: &FileFilter,
37 lines_changed_only: &LinesChangedOnly,
38 base_diff: Option<String>,
39 ignore_index: bool,
40 ) -> Result<HashMap<String, FileDiffLines>, ClientError> {
41 let git_status = if ignore_index {
42 0
43 } else {
44 match Command::new("git").args(["status", "--short"]).output() {
45 Err(e) => {
46 return Err(ClientError::io("invoke `git status`", e));
47 }
48 Ok(output) => {
49 if output.status.success() {
50 String::from_utf8_lossy(&output.stdout)
51 .to_string()
52 .trim_end_matches('\n')
54 .lines()
55 .filter(|l| !l.starts_with(' '))
57 .count()
58 } else {
59 let err_msg = String::from_utf8_lossy(&output.stderr).to_string();
60 return Err(ClientError::GitCommand(err_msg));
61 }
62 }
63 }
64 };
65 let mut diff_args = vec!["diff".to_string()];
66 if git_status != 0 {
67 diff_args.push("--staged".to_string());
70 }
71 if let Some(base) = base_diff {
72 match Command::new("git")
73 .args(["rev-parse", base.as_str()])
74 .output()
75 {
76 Err(e) => {
77 return Err(ClientError::Io {
78 task: format!("invoke `git rev-parse {base}` to validate reference"),
79 source: e,
80 });
81 }
82 Ok(output) => {
83 if output.status.success() {
84 diff_args.push(base);
85 } else if base.chars().all(|c| c.is_ascii_digit()) {
86 diff_args.push(format!("HEAD~{base}"));
89 } else {
92 let err_msg = String::from_utf8_lossy(&output.stderr).to_string();
93 return Err(ClientError::GitCommand(err_msg));
95 }
96 }
97 }
98 } else if git_status == 0 {
99 diff_args.push("HEAD~1".to_string());
102 }
103 match Command::new("git").args(&diff_args).output() {
104 Err(e) => Err(ClientError::Io {
105 task: format!("invoke `git {}`", diff_args.join(" ")),
106 source: e,
107 }),
108 Ok(output) => {
109 if output.status.success() {
110 let diff_str = String::from_utf8_lossy(&output.stdout).to_string();
111 let files = parse_diff(&diff_str, file_filter, lines_changed_only);
112 Ok(files)
113 } else {
114 let err_msg = String::from_utf8_lossy(&output.stderr).to_string();
115 Err(ClientError::GitCommand(err_msg))
116 }
117 }
118 }
119 }
120
121 fn is_pr_event(&self) -> bool {
122 false
123 }
124
125 fn set_user_agent(&mut self, _user_agent: &str) -> Result<(), ClientError> {
126 Ok(())
127 }
128
129 async fn post_thread_comment(&self, _options: ThreadCommentOptions) -> Result<(), ClientError> {
130 Ok(())
131 }
132
133 async fn cull_pr_reviews(&mut self, _options: &mut ReviewOptions) -> Result<(), ClientError> {
134 Ok(())
135 }
136
137 async fn post_pr_review(&mut self, _options: &ReviewOptions) -> Result<(), ClientError> {
138 Ok(())
139 }
140
141 fn write_output_variables(&self, vars: &[OutputVariable]) -> Result<(), ClientError> {
142 for var in vars {
143 log::info!("{}: {}", var.name, var.value);
144 }
145 Ok(())
146 }
147}