1#[cfg(not(target_arch = "wasm32"))]
6use octocrab::Octocrab;
7use tracing::{debug, instrument};
8
9use crate::error::AptuError;
10#[cfg(not(target_arch = "wasm32"))]
11use crate::github::pulls::fetch_pr_details;
12
13#[derive(Debug, Clone)]
15pub struct RevertOutcome {
16 pub dry_run: bool,
18 pub labels_removed: Vec<String>,
20 pub comment_ids: Vec<u64>,
22}
23
24#[cfg(not(target_arch = "wasm32"))]
45#[instrument(skip(client), fields(owner = %owner, repo = %repo, number = number, dry_run))]
46pub async fn revert_issue(
47 client: &Octocrab,
48 owner: &str,
49 repo: &str,
50 number: u64,
51 dry_run: bool,
52) -> crate::Result<RevertOutcome> {
53 use crate::github::issues::{
54 delete_issue_comment, fetch_issue_with_comments, remove_issue_label,
55 };
56
57 debug!("Reverting issue comments and labels");
58
59 let authenticated_user = client
61 .current()
62 .user()
63 .await
64 .map_err(|e| AptuError::GitHub {
65 message: format!("Failed to get authenticated user: {e}"),
66 })?;
67 let auth_login = authenticated_user.login.clone();
68 debug!(auth_login = %auth_login, "Authenticated as user");
69
70 let issue_details = fetch_issue_with_comments(client, owner, repo, number)
72 .await
73 .map_err(|e| AptuError::GitHub {
74 message: format!("Failed to fetch issue: {e}"),
75 })?;
76
77 let mut comment_ids_to_delete = Vec::new();
79 for comment in &issue_details.comments {
80 if comment.author == auth_login {
81 comment_ids_to_delete.push(comment.id);
82 debug!(comment_id = comment.id, "Found aptu-authored comment");
83 }
84 }
85
86 let labels_to_remove: Vec<String> = issue_details.labels.clone();
88
89 if dry_run {
90 debug!(
91 comment_count = comment_ids_to_delete.len(),
92 label_count = labels_to_remove.len(),
93 "Dry-run mode: no deletions will be performed"
94 );
95 return Ok(RevertOutcome {
96 dry_run: true,
97 labels_removed: labels_to_remove,
98 comment_ids: comment_ids_to_delete,
99 });
100 }
101
102 for comment_id in &comment_ids_to_delete {
104 if let Err(e) = delete_issue_comment(client, owner, repo, *comment_id).await {
105 return Err(AptuError::GitHub {
106 message: format!("Failed to delete comment #{comment_id}: {e}"),
107 });
108 }
109 }
110 debug!(count = comment_ids_to_delete.len(), "Comments deleted");
111
112 for label in &labels_to_remove {
114 if let Err(e) = remove_issue_label(client, owner, repo, number, label).await {
115 return Err(AptuError::GitHub {
116 message: format!("Failed to remove label '{label}': {e}"),
117 });
118 }
119 }
120 debug!(count = labels_to_remove.len(), "Labels removed");
121
122 Ok(RevertOutcome {
123 dry_run: false,
124 labels_removed: labels_to_remove,
125 comment_ids: comment_ids_to_delete,
126 })
127}
128
129#[cfg(target_arch = "wasm32")]
130pub async fn revert_issue(
131 _owner: &str,
132 _repo: &str,
133 _number: u64,
134 _dry_run: bool,
135) -> crate::Result<()> {
136 Err(AptuError::GitHub {
137 message: "revert_issue is not supported on wasm32-unknown-unknown".into(),
138 })
139}
140
141#[cfg(not(target_arch = "wasm32"))]
162#[instrument(skip(client), fields(owner = %owner, repo = %repo, number = number, dry_run))]
163pub async fn revert_pr(
164 client: &Octocrab,
165 owner: &str,
166 repo: &str,
167 number: u64,
168 dry_run: bool,
169) -> crate::Result<RevertOutcome> {
170 use crate::github::issues::remove_issue_label;
171 use crate::github::pulls::delete_pr_review_comment;
172
173 debug!("Reverting PR comments and labels");
174
175 let authenticated_user = client
177 .current()
178 .user()
179 .await
180 .map_err(|e| AptuError::GitHub {
181 message: format!("Failed to get authenticated user: {e}"),
182 })?;
183 let auth_login = authenticated_user.login.clone();
184 debug!(auth_login = %auth_login, "Authenticated as user");
185
186 let pr_details = fetch_pr_details(
188 client,
189 owner,
190 repo,
191 number,
192 &crate::config::ReviewConfig::default(),
193 )
194 .await
195 .map_err(|e| AptuError::GitHub {
196 message: format!("Failed to fetch PR: {e}"),
197 })?;
198
199 let mut comment_ids_to_delete = Vec::new();
201 for comment in &pr_details.review_comments {
202 if comment.author == auth_login {
203 comment_ids_to_delete.push(comment.id);
204 debug!(
205 comment_id = comment.id,
206 "Found aptu-authored review comment"
207 );
208 }
209 }
210
211 let labels_to_remove: Vec<String> = pr_details.labels.clone();
213
214 if dry_run {
215 debug!(
216 comment_count = comment_ids_to_delete.len(),
217 label_count = labels_to_remove.len(),
218 "Dry-run mode: no deletions will be performed"
219 );
220 return Ok(RevertOutcome {
221 dry_run: true,
222 labels_removed: labels_to_remove,
223 comment_ids: comment_ids_to_delete,
224 });
225 }
226
227 for comment_id in &comment_ids_to_delete {
229 if let Err(e) = delete_pr_review_comment(client, owner, repo, *comment_id).await {
230 return Err(AptuError::GitHub {
231 message: format!("Failed to delete PR review comment #{comment_id}: {e}"),
232 });
233 }
234 }
235 debug!(
236 count = comment_ids_to_delete.len(),
237 "PR review comments deleted"
238 );
239
240 for label in &labels_to_remove {
242 if let Err(e) = remove_issue_label(client, owner, repo, number, label).await {
243 return Err(AptuError::GitHub {
244 message: format!("Failed to remove label '{label}': {e}"),
245 });
246 }
247 }
248 debug!(count = labels_to_remove.len(), "Labels removed from PR");
249
250 Ok(RevertOutcome {
251 dry_run: false,
252 labels_removed: labels_to_remove,
253 comment_ids: comment_ids_to_delete,
254 })
255}
256
257#[cfg(target_arch = "wasm32")]
258pub async fn revert_pr(
259 _owner: &str,
260 _repo: &str,
261 _number: u64,
262 _dry_run: bool,
263) -> crate::Result<()> {
264 Err(AptuError::GitHub {
265 message: "revert_pr is not supported on wasm32-unknown-unknown".into(),
266 })
267}