Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src-tauri/src/git/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,89 @@ pub async fn sync_review_to_github(
})
}

/// A review comment from GitHub.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrReviewComment {
pub id: u64,
pub path: String,
pub body: String,
pub line: Option<u32>,
pub start_line: Option<u32>,
pub side: String,
pub user: String,
pub created_at: String,
}
Comment on lines +789 to +800
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testing comment sync


/// Fetch all review comments for a PR from GitHub.
pub async fn fetch_pr_comments(
repo: &Path,
pr_number: u64,
) -> Result<Vec<PrReviewComment>, GitError> {
let token = get_github_token()?;
let (owner, repo_name) = get_github_repo(repo)?;

log::info!(
"Fetching comments for PR #{} in {}/{}",
pr_number,
owner,
repo_name
);

let client = reqwest::Client::new();
let url = format!(
"https://api.github.com/repos/{}/{}/pulls/{}/comments",
owner, repo_name, pr_number
);

let response = client
.get(&url)
.header("Authorization", format!("Bearer {}", token))
.header("Accept", "application/vnd.github+json")
.header("User-Agent", "staged-app")
.header("X-GitHub-Api-Version", "2022-11-28")
.send()
.await
.map_err(|e| GitError::CommandFailed(format!("Failed to fetch PR comments: {}", e)))?;

if !response.status().is_success() {
return Err(GitError::CommandFailed(format!(
"Failed to fetch PR comments: {}",
response.status()
)));
}

#[derive(Debug, Deserialize)]
struct GhComment {
id: u64,
path: String,
body: String,
line: Option<u32>,
start_line: Option<u32>,
side: String,
user: GhUser,
created_at: String,
}

let comments: Vec<GhComment> = response
.json()
.await
.map_err(|e| GitError::CommandFailed(format!("Failed to parse comments: {}", e)))?;

Ok(comments
.into_iter()
.map(|c| PrReviewComment {
id: c.id,
path: c.path,
body: c.body,
line: c.line,
start_line: c.start_line,
side: c.side,
user: c.user.login,
created_at: c.created_at,
})
.collect())
}

// =============================================================================
// Pull Request Creation
// =============================================================================
Expand Down
8 changes: 4 additions & 4 deletions src-tauri/src/git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ pub use commit::commit;
pub use diff::{get_file_diff, get_unified_diff, list_diff_files};
pub use files::{get_file_at_ref, search_files};
pub use github::{
check_github_auth, create_pull_request, fetch_pr, get_pr_for_branch,
invalidate_cache as invalidate_pr_cache, list_pull_requests, push_branch, search_pull_requests,
sync_review_to_github, update_pull_request, CreatePrResult, GitHubAuthStatus, GitHubSyncResult,
PullRequest, PullRequestInfo,
check_github_auth, create_pull_request, fetch_pr, fetch_pr_comments, get_pr_for_branch,
invalidate_cache as invalidate_pr_cache, list_pull_requests, push_branch,
search_pull_requests, sync_review_to_github, update_pull_request, CreatePrResult,
GitHubAuthStatus, GitHubSyncResult, PrReviewComment, PullRequest, PullRequestInfo,
};
pub use refs::{
detect_default_branch, get_repo_root, list_branches, list_refs, merge_base, resolve_ref,
Expand Down
70 changes: 70 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,74 @@ async fn sync_review_to_github(
.map_err(|e| e.to_string())
}

/// Fetch review comments from a GitHub PR.
///
/// Returns all review comments on the PR (not general issue comments).
#[tauri::command(rename_all = "camelCase")]
async fn fetch_pr_comments(
repo_path: Option<String>,
pr_number: u64,
) -> Result<Vec<git::PrReviewComment>, String> {
let path = repo_path
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("."));

git::fetch_pr_comments(&path, pr_number)
.await
.map_err(|e| e.to_string())
}

/// Import comments from a GitHub PR into the local review.
///
/// This converts PR review comments to local comments and adds them to the review.
/// Only imports comments that are on lines that exist in the current diff.
#[tauri::command(rename_all = "camelCase")]
async fn import_pr_comments(
repo_path: Option<String>,
pr_number: u64,
spec: DiffSpec,
) -> Result<usize, String> {
let path = repo_path
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("."));

let pr_comments = git::fetch_pr_comments(&path, pr_number)
.await
.map_err(|e| e.to_string())?;

let store = review::get_store().map_err(|e| e.0)?;
let id = make_diff_id(&path, &spec)?;

let mut imported_count = 0;

for pr_comment in pr_comments {
if let Some(line) = pr_comment.line {
let start_line = pr_comment.start_line.unwrap_or(line);

let comment = review::Comment {
id: uuid::Uuid::new_v4().to_string(),
path: pr_comment.path,
span: git::Span {
start: start_line - 1,
end: line,
},
content: format!(
"{} ({})\n\n{}",
pr_comment.user, pr_comment.created_at, pr_comment.body
),
author: review::CommentAuthor::User,
category: Some("pr".to_string()),
created_at: Some(pr_comment.created_at),
};

store.add_comment(&id, &comment).map_err(|e| e.0)?;
imported_count += 1;
}
}

Ok(imported_count)
}

/// Get the PR associated with a branch (if one exists).
/// Returns None if no PR exists for this branch.
#[tauri::command(rename_all = "camelCase")]
Expand Down Expand Up @@ -3366,6 +3434,8 @@ pub fn run() {
search_pull_requests,
fetch_pr,
sync_review_to_github,
fetch_pr_comments,
import_pr_comments,
invalidate_pr_cache,
get_pr_for_branch,
push_branch,
Expand Down
Loading