feat: implement review workflow
- identification and collection of review comments - api call for creating review - keybinds for submitting review
This commit is contained in:
parent
1e8e7df3a8
commit
aa4749c648
3 changed files with 335 additions and 2 deletions
45
ghpr-api.el
45
ghpr-api.el
|
@ -35,6 +35,7 @@ Returns the token string if found, nil otherwise."
|
||||||
(defun ghpr--parse-api-pr (pr)
|
(defun ghpr--parse-api-pr (pr)
|
||||||
"Parse a single pull request object, keeping only essential fields."
|
"Parse a single pull request object, keeping only essential fields."
|
||||||
(let ((base (alist-get 'base pr))
|
(let ((base (alist-get 'base pr))
|
||||||
|
(head (alist-get 'head pr))
|
||||||
(user (alist-get 'user pr)))
|
(user (alist-get 'user pr)))
|
||||||
`((url . ,(alist-get 'url pr))
|
`((url . ,(alist-get 'url pr))
|
||||||
(id . ,(alist-get 'id pr))
|
(id . ,(alist-get 'id pr))
|
||||||
|
@ -46,7 +47,9 @@ Returns the token string if found, nil otherwise."
|
||||||
(title . ,(alist-get 'title pr))
|
(title . ,(alist-get 'title pr))
|
||||||
(body . ,(alist-get 'body pr))
|
(body . ,(alist-get 'body pr))
|
||||||
(username . ,(alist-get 'login user))
|
(username . ,(alist-get 'login user))
|
||||||
|
(author . ,(alist-get 'login user))
|
||||||
(base_sha . ,(alist-get 'sha base))
|
(base_sha . ,(alist-get 'sha base))
|
||||||
|
(head_sha . ,(alist-get 'sha head))
|
||||||
(merge_commit_sha . ,(alist-get 'merge_commit_sha pr)))))
|
(merge_commit_sha . ,(alist-get 'merge_commit_sha pr)))))
|
||||||
|
|
||||||
(defun ghpr--parse-api-pr-list (pr-list)
|
(defun ghpr--parse-api-pr-list (pr-list)
|
||||||
|
@ -95,6 +98,48 @@ Returns a list of pull request objects on success, nil on failure."
|
||||||
(message "Error fetching diff: %s" error-thrown)))))
|
(message "Error fetching diff: %s" error-thrown)))))
|
||||||
result))
|
result))
|
||||||
|
|
||||||
|
(defun ghpr--create-review (repo-name pr-number commit-id body event comments)
|
||||||
|
"Create a review for PR-NUMBER in REPO-NAME.
|
||||||
|
COMMIT-ID is the SHA of the commit to review.
|
||||||
|
BODY is the overall review comment.
|
||||||
|
EVENT should be 'APPROVE', 'REQUEST_CHANGES', or 'COMMENT'.
|
||||||
|
COMMENTS is a list of inline comments, each with keys: path, position, body.
|
||||||
|
Returns t on success, nil on failure."
|
||||||
|
(let ((token (ghpr--get-token))
|
||||||
|
(url (format "https://api.github.com/repos/%s/pulls/%s/reviews" repo-name pr-number))
|
||||||
|
(result nil)
|
||||||
|
(payload `((commit_id . ,commit-id)
|
||||||
|
(body . ,body)
|
||||||
|
(event . ,event)
|
||||||
|
(comments . ,(vconcat comments)))))
|
||||||
|
|
||||||
|
(when token
|
||||||
|
(request url
|
||||||
|
:type "POST"
|
||||||
|
:headers `(("Accept" . "application/vnd.github+json")
|
||||||
|
("Authorization" . ,(format "Bearer %s" token))
|
||||||
|
("X-GitHub-Api-Version" . "2022-11-28")
|
||||||
|
("Content-Type" . "application/json"))
|
||||||
|
:data (json-encode payload)
|
||||||
|
:parser 'json-read
|
||||||
|
:sync t
|
||||||
|
:success (cl-function
|
||||||
|
(lambda (&key data &allow-other-keys)
|
||||||
|
(setq result t)
|
||||||
|
(message "Review created successfully")))
|
||||||
|
:error (cl-function
|
||||||
|
(lambda (&key data error-thrown response &allow-other-keys)
|
||||||
|
(let ((status-code (when response (request-response-status-code response)))
|
||||||
|
(error-body (when data (json-encode data)))
|
||||||
|
(errors (when data (alist-get 'errors data))))
|
||||||
|
(message "Error creating review (HTTP %s): %s"
|
||||||
|
(or status-code "unknown")
|
||||||
|
error-thrown)
|
||||||
|
(when errors
|
||||||
|
(--each errors
|
||||||
|
(message "Error creating review (HTTP %s): %s" (or status-code "unknown") it))))))))
|
||||||
|
result))
|
||||||
|
|
||||||
(provide 'ghpr-api)
|
(provide 'ghpr-api)
|
||||||
|
|
||||||
;;; ghpr-api.el ends here
|
;;; ghpr-api.el ends here
|
||||||
|
|
287
ghpr-review.el
287
ghpr-review.el
|
@ -32,6 +32,7 @@
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'request)
|
(require 'request)
|
||||||
|
(require 'magit-git)
|
||||||
|
|
||||||
(require 'ghpr-api)
|
(require 'ghpr-api)
|
||||||
(require 'ghpr-utils)
|
(require 'ghpr-utils)
|
||||||
|
@ -69,12 +70,30 @@
|
||||||
(defvar-local ghpr--review-diff-content nil
|
(defvar-local ghpr--review-diff-content nil
|
||||||
"Buffer-local variable storing the diff content for the current PR.")
|
"Buffer-local variable storing the diff content for the current PR.")
|
||||||
|
|
||||||
|
(defvar-local ghpr--review-pr-metadata nil
|
||||||
|
"Buffer-local variable storing the PR metadata for the current PR.")
|
||||||
|
|
||||||
|
(defvar-local ghpr--review-repo-name nil
|
||||||
|
"Buffer-local variable storing the repository name for the current PR.")
|
||||||
|
|
||||||
|
(defvar ghpr-review-mode-map
|
||||||
|
(let ((map (make-sparse-keymap))
|
||||||
|
(prefix-map (make-sparse-keymap)))
|
||||||
|
(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 map (kbd "C-c") prefix-map)
|
||||||
|
map)
|
||||||
|
"Keymap for `ghpr-review-mode'.")
|
||||||
|
|
||||||
(define-derived-mode ghpr-review-mode text-mode "GHPR Review"
|
(define-derived-mode ghpr-review-mode text-mode "GHPR Review"
|
||||||
"Major mode for reviewing GitHub pull requests."
|
"Major mode for reviewing GitHub pull requests."
|
||||||
:group 'ghpr
|
:group 'ghpr
|
||||||
|
:keymap ghpr-review-mode-map
|
||||||
(setq truncate-lines t)
|
(setq truncate-lines t)
|
||||||
(setq font-lock-defaults '(ghpr-review-font-lock-keywords t nil nil nil))
|
(setq font-lock-defaults '(ghpr-review-font-lock-keywords t nil nil nil))
|
||||||
(font-lock-ensure))
|
(font-lock-ensure))
|
||||||
|
|
||||||
(defun ghpr--prefix-line (line)
|
(defun ghpr--prefix-line (line)
|
||||||
"Prefix the LINE with `> '."
|
"Prefix the LINE with `> '."
|
||||||
(concat "> " line))
|
(concat "> " line))
|
||||||
|
@ -101,6 +120,8 @@
|
||||||
(diff-content (ghpr--get-diff-content repo-name number))
|
(diff-content (ghpr--get-diff-content repo-name number))
|
||||||
(contents (ghpr--open-pr/collect-contents pr diff-content)))
|
(contents (ghpr--open-pr/collect-contents pr diff-content)))
|
||||||
(setq ghpr--review-diff-content diff-content)
|
(setq ghpr--review-diff-content diff-content)
|
||||||
|
(setq ghpr--review-pr-metadata pr)
|
||||||
|
(setq ghpr--review-repo-name repo-name)
|
||||||
(insert (ghpr--prefix-lines contents))))
|
(insert (ghpr--prefix-lines contents))))
|
||||||
|
|
||||||
(defun ghpr--open-pr (pr repo-name)
|
(defun ghpr--open-pr (pr repo-name)
|
||||||
|
@ -114,6 +135,272 @@
|
||||||
(goto-char (point-min)))
|
(goto-char (point-min)))
|
||||||
(display-buffer buffer)))
|
(display-buffer buffer)))
|
||||||
|
|
||||||
|
(defun ghpr--is-comment-line (line)
|
||||||
|
"Return t if LINE is a user comment (no prefix), nil otherwise."
|
||||||
|
(not (or (string-prefix-p ">" line)
|
||||||
|
(string-prefix-p "<" line))))
|
||||||
|
|
||||||
|
(defun ghpr--is-code-line (line)
|
||||||
|
"Return t if LINE is a diff code line (> + or > -), nil otherwise."
|
||||||
|
(or (string-prefix-p "> +" line)
|
||||||
|
(string-prefix-p "> -" line)))
|
||||||
|
|
||||||
|
(defun ghpr--find-preceding-code-line (lines current-index)
|
||||||
|
"Find the most recent code line before CURRENT-INDEX in LINES.
|
||||||
|
Returns the line content and its index as (line . index), or nil if none found."
|
||||||
|
(let ((index (1- current-index))
|
||||||
|
(found nil))
|
||||||
|
(while (and (>= index 0) (not found))
|
||||||
|
(let ((line (aref lines index)))
|
||||||
|
(if (ghpr--is-code-line line)
|
||||||
|
(setq found (cons line index))
|
||||||
|
(setq index (1- index)))))
|
||||||
|
found))
|
||||||
|
|
||||||
|
(defun ghpr--file-path+sha (lines start-index)
|
||||||
|
"Find file path and commit SHA by searching backwards from START-INDEX in LINES.
|
||||||
|
Returns a cons cell (file-path . commit-sha)."
|
||||||
|
(let ((file-path nil)
|
||||||
|
(commit-sha nil)
|
||||||
|
(index start-index))
|
||||||
|
(while (and (>= index 0) (or (not file-path) (not commit-sha)))
|
||||||
|
(let ((line (aref lines index)))
|
||||||
|
(cond
|
||||||
|
((string-match "^> diff --git a/\\(.+\\) b/" line)
|
||||||
|
(setq file-path (match-string 1 line)))
|
||||||
|
((string-match "^> \\+\\+\\+ b/\\(.+\\)$" line)
|
||||||
|
(setq file-path (match-string 1 line)))
|
||||||
|
((string-match "^> index \\([a-f0-9]+\\)\\.\\.\\([a-f0-9]+\\)" line)
|
||||||
|
(setq commit-sha (match-string 2 line))))
|
||||||
|
(setq index (1- index))))
|
||||||
|
(cons (when file-path (substring-no-properties file-path))
|
||||||
|
(when commit-sha (substring-no-properties commit-sha)))))
|
||||||
|
|
||||||
|
(defun ghpr--hunk-header (lines start-index)
|
||||||
|
"Find hunk header by searching backwards from START-INDEX in LINES.
|
||||||
|
Returns a cons cell (hunk-info . hunk-start-index) or (nil . nil) if not found."
|
||||||
|
(let ((index start-index)
|
||||||
|
(hunk-info nil)
|
||||||
|
(hunk-start nil))
|
||||||
|
(while (and (>= index 0) (not hunk-info))
|
||||||
|
(let ((line (aref lines index)))
|
||||||
|
(when (string-match "^> @@.*@@" line)
|
||||||
|
(setq hunk-info (substring-no-properties line))
|
||||||
|
(setq hunk-start index)))
|
||||||
|
(setq index (1- index)))
|
||||||
|
(cons hunk-info hunk-start)))
|
||||||
|
|
||||||
|
(defun ghpr--line-meta-p (line)
|
||||||
|
"Determines if the given LINE starts with `>'."
|
||||||
|
(string-prefix-p ">" line))
|
||||||
|
|
||||||
|
(defun ghpr--line-existing-comment-p (line)
|
||||||
|
"Determines if the given LINE is an existing comment starting with `>'."
|
||||||
|
(string-prefix-p "<" line))
|
||||||
|
|
||||||
|
(defun ghpr--special-line-p (line)
|
||||||
|
"Determines if the given LINE starts with a special character."
|
||||||
|
(or (ghpr--line-meta-p line)
|
||||||
|
(ghpr--line-existing-comment-p line)))
|
||||||
|
|
||||||
|
(defun ghpr--calculate-diff-position (lines hunk-start-index code-line-index)
|
||||||
|
"Calculate diff position from HUNK-START-INDEX to CODE-LINE-INDEX in LINES.
|
||||||
|
Position 1 is the line just below the first @@ hunk header.
|
||||||
|
Count only lines that start with > (diff content, not comments).
|
||||||
|
Returns the diff position as an integer."
|
||||||
|
(let ((position-count 0)
|
||||||
|
(index (1+ hunk-start-index)))
|
||||||
|
(while (<= index code-line-index)
|
||||||
|
(let ((line (aref lines index)))
|
||||||
|
(when (ghpr--line-meta-p line)
|
||||||
|
(setq position-count (1+ position-count))))
|
||||||
|
(setq index (1+ index)))
|
||||||
|
position-count))
|
||||||
|
|
||||||
|
(defun ghpr--determine-line-type (lines code-line-index)
|
||||||
|
"Determine the line type (added, removed, or context) for the line at CODE-LINE-INDEX.
|
||||||
|
Returns a string: \"added\", \"removed\", or \"context\"."
|
||||||
|
(let ((code-line (aref lines code-line-index)))
|
||||||
|
(cond
|
||||||
|
((string-prefix-p "> +" code-line) "added")
|
||||||
|
((string-prefix-p "> -" code-line) "removed")
|
||||||
|
(t "context"))))
|
||||||
|
|
||||||
|
(defun ghpr--parse-diff-context (lines code-line-index)
|
||||||
|
"Parse diff context around CODE-LINE-INDEX to extract file path, line type, commit SHA, and diff position.
|
||||||
|
Returns an alist with file-path, line-type, commit-sha, and diff-position information."
|
||||||
|
(let* ((file+sha (ghpr--file-path+sha lines code-line-index))
|
||||||
|
(file-path (car file+sha))
|
||||||
|
(commit-sha (cdr file+sha))
|
||||||
|
(hunk-result (ghpr--hunk-header lines code-line-index))
|
||||||
|
(hunk-info (car hunk-result))
|
||||||
|
(hunk-start (cdr hunk-result))
|
||||||
|
(diff-position
|
||||||
|
(when hunk-start
|
||||||
|
(ghpr--calculate-diff-position lines hunk-start code-line-index)))
|
||||||
|
(line-type (ghpr--determine-line-type lines code-line-index)))
|
||||||
|
|
||||||
|
`((file-path . ,file-path)
|
||||||
|
(line-type . ,line-type)
|
||||||
|
(hunk-info . ,hunk-info)
|
||||||
|
(commit-sha . ,commit-sha)
|
||||||
|
(diff-position . ,diff-position))))
|
||||||
|
|
||||||
|
(defun ghpr--collect-multiline-comment (lines start-index)
|
||||||
|
"Collect consecutive comment lines starting from START-INDEX in LINES.
|
||||||
|
Returns a cons cell (comment-lines . next-index) where comment-lines is a list
|
||||||
|
of comment lines and next-index is the index after the last comment line."
|
||||||
|
(let ((comment-lines (list (substring-no-properties (aref lines start-index))))
|
||||||
|
(next-index (1+ start-index)))
|
||||||
|
(while (and (< next-index (length lines))
|
||||||
|
(ghpr--is-comment-line (aref lines next-index)))
|
||||||
|
(push (substring-no-properties (aref lines next-index)) comment-lines)
|
||||||
|
(setq next-index (1+ next-index)))
|
||||||
|
(cons (reverse comment-lines) next-index)))
|
||||||
|
|
||||||
|
(defun ghpr--collect-review-body ()
|
||||||
|
"Collect review body comment from the top of the buffer.
|
||||||
|
Returns the body text as a string, or nil if no body found.
|
||||||
|
Body is everything from the start until the first line with special characters (>, <)."
|
||||||
|
(let* ((lines (vconcat (split-string (buffer-string) "\n")))
|
||||||
|
(body-lines '())
|
||||||
|
(index 0))
|
||||||
|
(while (and (< index (length lines))
|
||||||
|
(not (ghpr--special-line-p (aref lines index))))
|
||||||
|
(let ((line (aref lines index)))
|
||||||
|
(unless (string-empty-p (string-trim line))
|
||||||
|
(push (substring-no-properties line) body-lines)))
|
||||||
|
(setq index (1+ index)))
|
||||||
|
(when body-lines
|
||||||
|
(string-join (reverse body-lines) "\n"))))
|
||||||
|
|
||||||
|
(defun ghpr--skip-review-body (lines)
|
||||||
|
"Skip to the first line with special characters (>, <) to get past the body.
|
||||||
|
Returns the index of the first line after the review body."
|
||||||
|
(let ((index 0)
|
||||||
|
(past-body nil))
|
||||||
|
(while (and (< index (length lines)) (not past-body))
|
||||||
|
(let ((line (aref lines index)))
|
||||||
|
(when (ghpr--special-line-p line)
|
||||||
|
(setq past-body t))
|
||||||
|
(unless past-body
|
||||||
|
(setq index (1+ index)))))
|
||||||
|
index))
|
||||||
|
|
||||||
|
(defun ghpr--collect-inline-comments-after-body (lines start-index)
|
||||||
|
"Collect inline comments starting from START-INDEX in LINES.
|
||||||
|
Returns a list of comment entries with their GitHub API context."
|
||||||
|
(let ((comments '())
|
||||||
|
(index start-index))
|
||||||
|
(while (< index (length lines))
|
||||||
|
(let ((line (aref lines index)))
|
||||||
|
(when (ghpr--is-comment-line line)
|
||||||
|
(let* ((comment-result (ghpr--collect-multiline-comment lines index))
|
||||||
|
(comment-lines (car comment-result))
|
||||||
|
(next-index (cdr comment-result))
|
||||||
|
(comment-entry (ghpr--build-comment-with-context comment-lines index lines)))
|
||||||
|
(when comment-entry
|
||||||
|
(push comment-entry comments))
|
||||||
|
(setq index (1- next-index))))
|
||||||
|
(setq index (1+ index))))
|
||||||
|
(reverse comments)))
|
||||||
|
|
||||||
|
(defun ghpr--comment-to-api-format (comment)
|
||||||
|
"Convert a collected comment to GitHub API format.
|
||||||
|
Errors if the comment is missing required fields or has empty body."
|
||||||
|
(let ((path (alist-get 'file-path comment))
|
||||||
|
(position (alist-get 'diff-position comment))
|
||||||
|
(comment-body (alist-get 'comment comment)))
|
||||||
|
(unless path
|
||||||
|
(error "Comment missing file path: %S" comment))
|
||||||
|
(unless position
|
||||||
|
(error "Comment missing diff position: %S" comment))
|
||||||
|
(unless (and comment-body (not (string-empty-p (string-trim comment-body))))
|
||||||
|
(error "Comment missing or empty body: %S" comment))
|
||||||
|
`((path . ,path)
|
||||||
|
(position . ,position)
|
||||||
|
(body . ,comment-body))))
|
||||||
|
|
||||||
|
(defun ghpr--build-comment-with-context (comment-lines comment-start-index lines)
|
||||||
|
"Build a comment entry with GitHub API context for COMMENT-LINES.
|
||||||
|
Uses COMMENT-START-INDEX to find the preceding code line in LINES.
|
||||||
|
Returns an alist with comment, file-path, commit-sha, and diff-position, or nil if no context found."
|
||||||
|
(let ((preceding-code (ghpr--find-preceding-code-line lines comment-start-index)))
|
||||||
|
(when preceding-code
|
||||||
|
(let* ((code-index (cdr preceding-code))
|
||||||
|
(context (ghpr--parse-diff-context lines code-index))
|
||||||
|
(full-comment (string-join comment-lines "\n")))
|
||||||
|
`((comment . ,full-comment)
|
||||||
|
(file-path . ,(alist-get 'file-path context))
|
||||||
|
(commit-sha . ,(alist-get 'commit-sha context))
|
||||||
|
(diff-position . ,(alist-get 'diff-position context)))))))
|
||||||
|
|
||||||
|
(defun ghpr--collect-review-comments ()
|
||||||
|
"Collect all inline review comments from the current buffer.
|
||||||
|
Returns an alist of comments with their associated diff lines and GitHub API context.
|
||||||
|
Multi-line comments are grouped together until the next line with angle brackets.
|
||||||
|
Skips the review body at the top of the buffer."
|
||||||
|
(let* ((lines (vconcat (split-string (buffer-string) "\n")))
|
||||||
|
(body-end-index (ghpr--skip-review-body lines)))
|
||||||
|
(ghpr--collect-inline-comments-after-body lines body-end-index)))
|
||||||
|
|
||||||
|
(defun ghpr-collect-review-comments ()
|
||||||
|
"Interactive command to collect and display review comments from current buffer."
|
||||||
|
(interactive)
|
||||||
|
(let ((body (ghpr--collect-review-body))
|
||||||
|
(comments (ghpr--collect-review-comments)))
|
||||||
|
(if (or body comments)
|
||||||
|
(with-output-to-temp-buffer "*GHPR Review Comments*"
|
||||||
|
(prin1 body)
|
||||||
|
(prin1 comments))
|
||||||
|
(message "No review comments found in current buffer."))))
|
||||||
|
|
||||||
|
(defun ghpr--submit-review (event)
|
||||||
|
"Submit a review with the specified EVENT type.
|
||||||
|
EVENT should be 'COMMENT', 'APPROVE', or 'REQUEST_CHANGES'.
|
||||||
|
Collects review body and inline comments from current buffer."
|
||||||
|
(unless ghpr--review-pr-metadata
|
||||||
|
(error "No PR metadata found in buffer"))
|
||||||
|
(unless ghpr--review-repo-name
|
||||||
|
(error "No repository name found in buffer"))
|
||||||
|
|
||||||
|
(let* ((body (ghpr--collect-review-body))
|
||||||
|
(inline-comments (ghpr--collect-review-comments))
|
||||||
|
(pr-number (alist-get 'number ghpr--review-pr-metadata))
|
||||||
|
(commit-sha (magit-rev-parse (or (alist-get 'head_sha ghpr--review-pr-metadata)
|
||||||
|
(alist-get 'merge_commit_sha ghpr--review-pr-metadata))))
|
||||||
|
(api-comments (mapcar #'ghpr--comment-to-api-format inline-comments)))
|
||||||
|
|
||||||
|
(when (and (not body) (not api-comments))
|
||||||
|
(error "No review body or comments found"))
|
||||||
|
|
||||||
|
(when (and (member event '("REQUEST_CHANGES" "COMMENT"))
|
||||||
|
(or (not body) (string-empty-p (string-trim body))))
|
||||||
|
(error "Review body is required for %s events" event))
|
||||||
|
|
||||||
|
(unless (ghpr--create-review ghpr--review-repo-name
|
||||||
|
pr-number
|
||||||
|
commit-sha
|
||||||
|
(or body "")
|
||||||
|
event
|
||||||
|
api-comments)
|
||||||
|
(message "Failed to submit review"))))
|
||||||
|
|
||||||
|
(defun ghpr-review-comment ()
|
||||||
|
"Submit review comments with COMMENT event."
|
||||||
|
(interactive)
|
||||||
|
(ghpr--submit-review "COMMENT"))
|
||||||
|
|
||||||
|
(defun ghpr-review-approve ()
|
||||||
|
"Submit review with APPROVE event."
|
||||||
|
(interactive)
|
||||||
|
(ghpr--submit-review "APPROVE"))
|
||||||
|
|
||||||
|
(defun ghpr-review-reject-changes ()
|
||||||
|
"Submit review with REQUEST_CHANGES event."
|
||||||
|
(interactive)
|
||||||
|
(ghpr--submit-review "REQUEST_CHANGES"))
|
||||||
|
|
||||||
(provide 'ghpr-review)
|
(provide 'ghpr-review)
|
||||||
|
|
||||||
;;; ghpr-review.el ends here
|
;;; ghpr-review.el ends here
|
||||||
|
|
|
@ -25,8 +25,9 @@
|
||||||
(defun ghpr--pr-summary (pr)
|
(defun ghpr--pr-summary (pr)
|
||||||
"Formats a PR into a summary."
|
"Formats a PR into a summary."
|
||||||
(let* ((number (alist-get 'number pr))
|
(let* ((number (alist-get 'number pr))
|
||||||
(title (alist-get 'title pr)))
|
(title (alist-get 'title pr))
|
||||||
(format "[#%s] %s" number title)))
|
(author (alist-get 'author pr)))
|
||||||
|
(format "[#%s] @%s: %s" number author title)))
|
||||||
|
|
||||||
(defun ghpr--pr-summary-selection (pr)
|
(defun ghpr--pr-summary-selection (pr)
|
||||||
"Formats a PR into a summary for a minibuffer selection."
|
"Formats a PR into a summary for a minibuffer selection."
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue