From c49a89705fb281ebde677530de0c02e9676e52df Mon Sep 17 00:00:00 2001 From: Lucas Sta Maria Date: Tue, 26 Aug 2025 21:29:58 +0800 Subject: [PATCH 1/4] feat: checkout branch from `ghpr-review-mode` --- ghpr-api.el | 4 ++++ ghpr-repo.el | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ ghpr-review.el | 9 +++++++++ 3 files changed, 62 insertions(+) diff --git a/ghpr-api.el b/ghpr-api.el index 94d4060..1cfeb21 100644 --- a/ghpr-api.el +++ b/ghpr-api.el @@ -49,7 +49,11 @@ Returns the token string if found, nil otherwise." (username . ,(alist-get 'login user)) (author . ,(alist-get 'login user)) (base_sha . ,(alist-get 'sha base)) + (base_ref . ,(alist-get 'ref base)) (head_sha . ,(alist-get 'sha head)) + (head_ref . ,(alist-get 'ref head)) + (head_repo . ,(alist-get 'repo head)) + (head_user . ,(alist-get 'user head)) (merge_commit_sha . ,(alist-get 'merge_commit_sha pr))))) (defun ghpr--parse-api-pr-list (pr-list) diff --git a/ghpr-repo.el b/ghpr-repo.el index 2e60bb4..c0ae781 100644 --- a/ghpr-repo.el +++ b/ghpr-repo.el @@ -41,6 +41,55 @@ Returns nil if origin remote doesn't exist or is not a GitHub repository." (when (string-match "github\\.com[:/]\\([^/]+/[^/.]+\\)" origin-url) (match-string 1 origin-url))))) +(defun ghpr--is-fork-pr-p (pr-metadata) + "Return t if PR is from a fork, nil if from same repository. +PR-METADATA should contain head_repo and head_user information." + (let ((head-repo (alist-get 'head_repo pr-metadata)) + (head-user (alist-get 'head_user pr-metadata)) + (origin-repo-name (ghpr--get-repo-name))) + (when (and head-repo head-user origin-repo-name) + (let ((head-repo-full-name (alist-get 'full_name head-repo)) + (head-user-login (alist-get 'login head-user))) + (not (string= head-repo-full-name origin-repo-name)))))) + +(defun ghpr--get-or-add-remote (pr-metadata) + "Get or add the remote for the PR's head repository. +Returns the remote name to use for fetching the PR branch." + (let* ((head-repo (alist-get 'head_repo pr-metadata)) + (head-user (alist-get 'head_user pr-metadata)) + (head-user-login (alist-get 'login head-user)) + (head-repo-clone-url (alist-get 'clone_url head-repo)) + (remote-name head-user-login)) + + (if (ghpr--is-fork-pr-p pr-metadata) + (progn + (unless (magit-remote-p remote-name) + (magit-remote-add remote-name head-repo-clone-url)) + remote-name) + "origin"))) + +(defun ghpr--checkout-pr-branch (pr-metadata) + "Check out the PR branch using Magit. +Fetches the branch from appropriate remote if needed, then checks it out. +PR-METADATA should contain head_ref, head_sha, and repository information." + (let* ((head-ref (alist-get 'head_ref pr-metadata)) + (head-sha (alist-get 'head_sha pr-metadata)) + (pr-number (alist-get 'number pr-metadata)) + (remote-name (ghpr--get-or-add-remote pr-metadata)) + (remote-branch-ref (format "%s/%s" remote-name head-ref)) + (local-branch-name head-ref)) + + (unless head-ref + (error "PR metadata missing head branch reference")) + + (message "Fetching PR branch from %s..." remote-name) + (magit-fetch-branch remote-name head-ref local-branch-name) + + (message "Checking out PR branch: %s" local-branch-name) + (magit-checkout local-branch-name) + + (message "Checked out PR #%s branch: %s" pr-number local-branch-name))) + (provide 'ghpr-repo) ;;; ghpr-repo.el ends here diff --git a/ghpr-review.el b/ghpr-review.el index 3e73d3f..3c46df6 100644 --- a/ghpr-review.el +++ b/ghpr-review.el @@ -35,6 +35,7 @@ (require 'magit-git) (require 'ghpr-api) +(require 'ghpr-repo) (require 'ghpr-utils) (defface ghpr-review-added-line @@ -82,6 +83,7 @@ (define-key prefix-map (kbd "C-c") 'ghpr-review-comment) (define-key prefix-map (kbd "C-a") 'ghpr-review-approve) (define-key prefix-map (kbd "C-r") 'ghpr-review-reject-changes) + (define-key prefix-map (kbd "C-o") 'ghpr-review-checkout-branch) (define-key map (kbd "C-c") prefix-map) map) "Keymap for `ghpr-review-mode'.") @@ -401,6 +403,13 @@ Collects review body and inline comments from current buffer." (interactive) (ghpr--submit-review "REQUEST_CHANGES")) +(defun ghpr-review-checkout-branch () + "Check out the PR branch using Magit." + (interactive) + (unless ghpr--review-pr-metadata + (error "No PR metadata found in buffer")) + (ghpr--checkout-pr-branch ghpr--review-pr-metadata)) + (provide 'ghpr-review) ;;; ghpr-review.el ends here From ac1757c0a07f029c280777884712244b858fb5ee Mon Sep 17 00:00:00 2001 From: Lucas Sta Maria Date: Tue, 26 Aug 2025 21:34:42 +0800 Subject: [PATCH 2/4] fix: add review quitting --- ghpr-review.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ghpr-review.el b/ghpr-review.el index 3c46df6..29748d2 100644 --- a/ghpr-review.el +++ b/ghpr-review.el @@ -84,6 +84,7 @@ (define-key prefix-map (kbd "C-a") 'ghpr-review-approve) (define-key prefix-map (kbd "C-r") 'ghpr-review-reject-changes) (define-key prefix-map (kbd "C-o") 'ghpr-review-checkout-branch) + (define-key prefix-map (kbd "C-k") 'ghpr-review-quit) (define-key map (kbd "C-c") prefix-map) map) "Keymap for `ghpr-review-mode'.") @@ -410,6 +411,12 @@ Collects review body and inline comments from current buffer." (error "No PR metadata found in buffer")) (ghpr--checkout-pr-branch ghpr--review-pr-metadata)) +(defun ghpr-review-quit () + "Quit and close the review buffer with confirmation." + (interactive) + (when (yes-or-no-p "Quit review? ") + (kill-buffer (current-buffer)))) + (provide 'ghpr-review) ;;; ghpr-review.el ends here From 3fd3e90633d82637b78e2f811058737de2b5ed89 Mon Sep 17 00:00:00 2001 From: Lucas Sta Maria Date: Tue, 26 Aug 2025 22:28:21 +0800 Subject: [PATCH 3/4] feat: add diffing in review buffer --- ghpr-review.el | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/ghpr-review.el b/ghpr-review.el index 29748d2..cdd9054 100644 --- a/ghpr-review.el +++ b/ghpr-review.el @@ -84,6 +84,7 @@ (define-key prefix-map (kbd "C-a") 'ghpr-review-approve) (define-key prefix-map (kbd "C-r") 'ghpr-review-reject-changes) (define-key prefix-map (kbd "C-o") 'ghpr-review-checkout-branch) + (define-key prefix-map (kbd "C-d") 'ghpr-review-magit-diff) (define-key prefix-map (kbd "C-k") 'ghpr-review-quit) (define-key map (kbd "C-c") prefix-map) map) @@ -411,6 +412,63 @@ Collects review body and inline comments from current buffer." (error "No PR metadata found in buffer")) (ghpr--checkout-pr-branch ghpr--review-pr-metadata)) +(defun ghpr--review-magit-diff/both-local (local-base-branch local-pr-branch) + "Handle case where both base and PR branches exist locally." + (message "Diffing local branches: %s..%s" local-base-branch local-pr-branch) + (magit-diff-range (format "%s..%s" local-base-branch local-pr-branch))) + +(defun ghpr--review-magit-diff/base-local-pr-remote (local-base-branch local-pr-branch pr-metadata) + "Handle case where base branch exists locally but PR branch needs to be fetched." + (message "Local base branch found, fetching and checking out PR branch...") + (magit-git-fetch "origin" nil) + (ghpr--checkout-pr-branch pr-metadata) + (if (magit-branch-p local-pr-branch) + (progn + (message "Diffing: %s..%s" local-base-branch local-pr-branch) + (magit-diff-range (format "%s..%s" local-base-branch local-pr-branch))) + (error "Unable to create PR branch. This may happen when testing on a repository that doesn't contain the actual PR"))) + +(defun ghpr--review-magit-diff/remote-handling (local-base-branch local-pr-branch remote-base-branch local-base-exists-p local-pr-exists-p pr-metadata) + "Handle case where remote operations are needed for base and/or PR branch." + (message "Local branches not found, fetching and checking out...") + (magit-git-fetch "origin" nil) + (unless local-pr-exists-p + (ghpr--checkout-pr-branch pr-metadata)) + (let ((base-to-use (if local-base-exists-p local-base-branch remote-base-branch))) + (if (magit-branch-p local-pr-branch) + (progn + (message "Diffing: %s..%s" base-to-use local-pr-branch) + (magit-diff-range (format "%s..%s" base-to-use local-pr-branch))) + (error "Unable to create PR branch. This may happen when testing on a repository that doesn't contain the actual PR")))) + +(defun ghpr-review-magit-diff () + "Show Magit diff between PR base and head branches. +Checks if branches exist locally first, falls back to remote handling if needed." + (interactive) + (unless ghpr--review-pr-metadata + (error "No PR metadata found in buffer")) + + (let* ((pr-number (alist-get 'number ghpr--review-pr-metadata)) + (base-ref (alist-get 'base_ref ghpr--review-pr-metadata)) + (head-ref (alist-get 'head_ref ghpr--review-pr-metadata)) + (local-pr-branch head-ref) + (local-base-branch base-ref) + (remote-base-branch (format "origin/%s" base-ref)) + (local-base-exists-p (magit-branch-p local-base-branch)) + (local-pr-exists-p (magit-branch-p local-pr-branch))) + (unless base-ref + (error "PR metadata missing base branch reference")) + (unless head-ref + (error "PR metadata missing head branch reference")) + + (cond + ((and local-base-exists-p local-pr-exists-p) + (ghpr--review-magit-diff/both-local local-base-branch local-pr-branch)) + (local-base-exists-p + (ghpr--review-magit-diff/base-local-pr-remote local-base-branch local-pr-branch ghpr--review-pr-metadata)) + (t + (ghpr--review-magit-diff/remote-handling local-base-branch local-pr-branch remote-base-branch local-base-exists-p local-pr-exists-p ghpr--review-pr-metadata))))) + (defun ghpr-review-quit () "Quit and close the review buffer with confirmation." (interactive) From d142a39966a11afade1dae809b75ad54329e82d4 Mon Sep 17 00:00:00 2001 From: Lucas Sta Maria Date: Tue, 26 Aug 2025 22:31:42 +0800 Subject: [PATCH 4/4] fix: switch focus on pr select --- ghpr-review.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghpr-review.el b/ghpr-review.el index cdd9054..4e7a98c 100644 --- a/ghpr-review.el +++ b/ghpr-review.el @@ -137,7 +137,7 @@ (ghpr-review-mode) (ghpr--open-pr/insert-contents pr repo-name) (goto-char (point-min))) - (display-buffer buffer))) + (switch-to-buffer-other-window buffer))) (defun ghpr--is-comment-line (line) "Return t if LINE is a user comment (no prefix), nil otherwise."