From 61eedef73f40048e3d6158a2c43cd1bf604a8422 Mon Sep 17 00:00:00 2001 From: Lucas Sta Maria Date: Fri, 22 Aug 2025 22:48:35 +0800 Subject: [PATCH] feat: add `ghpr-list-prs` --- ghpr-api.el | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ ghpr-repo.el | 10 ++++++++++ ghpr-utils.el | 34 ++++++++++++++++++++++++++++++++++ ghpr.el | 23 ++++++++++++++++++++++- 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 ghpr-utils.el diff --git a/ghpr-api.el b/ghpr-api.el index fcdb13f..c3c73ef 100644 --- a/ghpr-api.el +++ b/ghpr-api.el @@ -23,6 +23,56 @@ ;;; Code: +(require 'auth-source) +(require 'request) +(require 'json) + +(defun ghpr--get-token () + "Retrieve GitHub authentication token from auth-sources. +Returns the token string if found, nil otherwise." + (auth-source-pick-first-password :host "api.github.com")) + +(defun ghpr--parse-api-pr (pr) + "Parse a single pull request object, keeping only essential fields." + (let ((base (alist-get 'base pr))) + `((url . ,(alist-get 'url pr)) + (id . ,(alist-get 'id pr)) + (diff_url . ,(alist-get 'diff_url pr)) + (patch_url . ,(alist-get 'patch_url pr)) + (number . ,(alist-get 'number pr)) + (state . ,(alist-get 'state pr)) + (locked . ,(alist-get 'locked pr)) + (title . ,(alist-get 'title pr)) + (body . ,(alist-get 'body pr)) + (base_sha . ,(alist-get 'sha base)) + (merge_commit_sha . ,(alist-get 'merge_commit_sha pr)))))) + +(defun ghpr--parse-api-pr-list (pr-list) + "Parse a list of pull request objects, keeping only essential fields." + (mapcar #'ghpr--parse-api-pr pr-list)) + +(defun ghpr--list-open-prs (repo-name) + "List open pull requests for REPO-NAME in owner/repo format. +Returns a list of pull request objects on success, nil on failure." + (let ((token (ghpr--get-token)) + (url (format "https://api.github.com/repos/%s/pulls?state=open" repo-name)) + (result nil)) + (when token + (request url + :type "GET" + :headers `(("Accept" . "application/vnd.github+json") + ("Authorization" . ,(format "Bearer %s" token)) + ("X-GitHub-Api-Version" . "2022-11-28")) + :parser 'json-read + :sync t + :success (cl-function + (lambda (&key data &allow-other-keys) + (setq result (ghpr--parse-api-pr-list data)))) + :error (cl-function + (lambda (&key error-thrown &allow-other-keys) + (message "Error fetching pull requests: %s" error-thrown))))) + result)) + (provide 'ghpr-api) ;;; ghpr-api.el ends here diff --git a/ghpr-repo.el b/ghpr-repo.el index d78bb32..2e60bb4 100644 --- a/ghpr-repo.el +++ b/ghpr-repo.el @@ -31,6 +31,16 @@ ;;; Code: +(require 'magit) + +(defun ghpr--get-repo-name () + "Get the GitHub repository name in owner/repo format from origin remote. +Returns nil if origin remote doesn't exist or is not a GitHub repository." + (when (magit-remote-p "origin") + (let ((origin-url (magit-get "remote.origin.url"))) + (when (string-match "github\\.com[:/]\\([^/]+/[^/.]+\\)" origin-url) + (match-string 1 origin-url))))) + (provide 'ghpr-repo) ;;; ghpr-repo.el ends here diff --git a/ghpr-utils.el b/ghpr-utils.el new file mode 100644 index 0000000..d987627 --- /dev/null +++ b/ghpr-utils.el @@ -0,0 +1,34 @@ +;;; ghpr-utils.el --- Utility functions for ghpr -*- lexical-binding: t -*- + +;; This file is not part of GNU Emacs + +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + + +;;; Commentary: + +;; Provides utility functions for ghpr.el. + +;;; Code: + +(defun ghpr--display-pr-item (pr) + "Displays a PR item for the minibuffer." + (let* ((number (alist-get 'number pr)) + (title (alist-get 'title pr)) + (display-text (format "[#%s] %s" number title))) + (cons display-text pr))) + +(provide 'ghpr-utils) + +;;; ghpr-utils.el ends here diff --git a/ghpr.el b/ghpr.el index 15e4661..59ee4ca 100644 --- a/ghpr.el +++ b/ghpr.el @@ -3,7 +3,7 @@ ;; Author: Lucas Sta Maria ;; Maintainer: Lucas Sta Maria ;; Version: 0.1 -;; Package-Requires: (magit) +;; Package-Requires: (magit request) ;; Homepage: https://git.priime.dev/lucas/ghpr.el ;; Keywords: git @@ -32,6 +32,27 @@ ;;; Code: +(require 'ghpr-api) +(require 'ghpr-repo) +(require 'ghpr-utils) + +(defun ghpr-prs () + "List and choose from the current repository's open PRs." + (interactive) + (let* ((repo-name (ghpr--get-repo-name)) + (prs (and repo-name (ghpr--list-open-prs repo-name)))) + (cond + ((not repo-name) + (message "Not in a GitHub repository")) + ((not prs) + (message "No open pull requests found")) + (t + (let* ((pr-items (mapcar #'ghpr--display-pr-item prs)) + (selected-item (completing-read "Select PR: " pr-items nil t))) + (when selected-item + (let ((pr (cdr (assoc selected-item pr-items)))) + (message "Selected PR: %s" (cdr (assoc 'title pr)))))))))) + (provide 'ghpr) ;;; ghpr.el ends here