use octocrab::Octocrab;
use tracing::{debug, instrument};
use crate::error::AptuError;
use crate::github::pulls::fetch_pr_details;
#[derive(Debug, Clone)]
pub struct RevertOutcome {
pub dry_run: bool,
pub labels_removed: Vec<String>,
pub comment_ids: Vec<u64>,
}
#[instrument(skip(client), fields(owner = %owner, repo = %repo, number = number, dry_run))]
pub async fn revert_issue(
client: &Octocrab,
owner: &str,
repo: &str,
number: u64,
dry_run: bool,
) -> crate::Result<RevertOutcome> {
use crate::github::issues::{
delete_issue_comment, fetch_issue_with_comments, remove_issue_label,
};
debug!("Reverting issue comments and labels");
let authenticated_user = client
.current()
.user()
.await
.map_err(|e| AptuError::GitHub {
message: format!("Failed to get authenticated user: {e}"),
})?;
let auth_login = authenticated_user.login.clone();
debug!(auth_login = %auth_login, "Authenticated as user");
let issue_details = fetch_issue_with_comments(client, owner, repo, number)
.await
.map_err(|e| AptuError::GitHub {
message: format!("Failed to fetch issue: {e}"),
})?;
let mut comment_ids_to_delete = Vec::new();
for comment in &issue_details.comments {
if comment.author == auth_login {
comment_ids_to_delete.push(comment.id);
debug!(comment_id = comment.id, "Found aptu-authored comment");
}
}
let labels_to_remove: Vec<String> = issue_details.labels.clone();
if dry_run {
debug!(
comment_count = comment_ids_to_delete.len(),
label_count = labels_to_remove.len(),
"Dry-run mode: no deletions will be performed"
);
return Ok(RevertOutcome {
dry_run: true,
labels_removed: labels_to_remove,
comment_ids: comment_ids_to_delete,
});
}
for comment_id in &comment_ids_to_delete {
if let Err(e) = delete_issue_comment(client, owner, repo, *comment_id).await {
return Err(AptuError::GitHub {
message: format!("Failed to delete comment #{comment_id}: {e}"),
});
}
}
debug!(count = comment_ids_to_delete.len(), "Comments deleted");
for label in &labels_to_remove {
if let Err(e) = remove_issue_label(client, owner, repo, number, label).await {
return Err(AptuError::GitHub {
message: format!("Failed to remove label '{label}': {e}"),
});
}
}
debug!(count = labels_to_remove.len(), "Labels removed");
Ok(RevertOutcome {
dry_run: false,
labels_removed: labels_to_remove,
comment_ids: comment_ids_to_delete,
})
}
#[instrument(skip(client), fields(owner = %owner, repo = %repo, number = number, dry_run))]
pub async fn revert_pr(
client: &Octocrab,
owner: &str,
repo: &str,
number: u64,
dry_run: bool,
) -> crate::Result<RevertOutcome> {
use crate::github::issues::remove_issue_label;
use crate::github::pulls::delete_pr_review_comment;
debug!("Reverting PR comments and labels");
let authenticated_user = client
.current()
.user()
.await
.map_err(|e| AptuError::GitHub {
message: format!("Failed to get authenticated user: {e}"),
})?;
let auth_login = authenticated_user.login.clone();
debug!(auth_login = %auth_login, "Authenticated as user");
let pr_details = fetch_pr_details(
client,
owner,
repo,
number,
&crate::config::ReviewConfig::default(),
)
.await
.map_err(|e| AptuError::GitHub {
message: format!("Failed to fetch PR: {e}"),
})?;
let mut comment_ids_to_delete = Vec::new();
for comment in &pr_details.review_comments {
if comment.author == auth_login {
comment_ids_to_delete.push(comment.id);
debug!(
comment_id = comment.id,
"Found aptu-authored review comment"
);
}
}
let labels_to_remove: Vec<String> = pr_details.labels.clone();
if dry_run {
debug!(
comment_count = comment_ids_to_delete.len(),
label_count = labels_to_remove.len(),
"Dry-run mode: no deletions will be performed"
);
return Ok(RevertOutcome {
dry_run: true,
labels_removed: labels_to_remove,
comment_ids: comment_ids_to_delete,
});
}
for comment_id in &comment_ids_to_delete {
if let Err(e) = delete_pr_review_comment(client, owner, repo, *comment_id).await {
return Err(AptuError::GitHub {
message: format!("Failed to delete PR review comment #{comment_id}: {e}"),
});
}
}
debug!(
count = comment_ids_to_delete.len(),
"PR review comments deleted"
);
for label in &labels_to_remove {
if let Err(e) = remove_issue_label(client, owner, repo, number, label).await {
return Err(AptuError::GitHub {
message: format!("Failed to remove label '{label}': {e}"),
});
}
}
debug!(count = labels_to_remove.len(), "Labels removed from PR");
Ok(RevertOutcome {
dry_run: false,
labels_removed: labels_to_remove,
comment_ids: comment_ids_to_delete,
})
}