+++ /dev/null
-;;; buck.el --- Client library for the Bitbucket API -*- lexical-binding:t -*-
-
-;; Copyright (C) 2016-2024 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-
-;; SPDX-License-Identifier: GPL-3.0-or-later
-
-;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Buck is a library that provides basic support for using the Bitbucket API
-;; from Emacs packages. It abstracts access to API resources using only
-;; a handful of functions that are not resource-specific.
-
-;; This library is implemented on top of Ghub. Unlike Ghub, Buck does
-;; not support the guided creation of tokens because Bitbucket lacks the
-;; features that would be necessary to implement that. Users have to
-;; create tokens through the web interface.
-
-;;; Code:
-
-(require 'ghub)
-
-(defconst buck-default-host "api.bitbucket.org/2.0"
- "The default host that is used if `buck.host' is not set.")
-
-;; HEAD and PATCH are not supported according to
-;; https://developer.atlassian.com/bitbucket/api/2/reference/meta/uri-uuid
-
-(cl-defun buck-get (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `GET' request for RESOURCE, with optional query PARAMS.
-Like calling `ghub-request' (which see) with \"GET\" as METHOD
-and `bitbucket' as FORGE."
- (ghub-request "GET" resource params :forge 'bitbucket
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun buck-put (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PUT\" as METHOD
-and `bitbucket' as FORGE."
- (ghub-request "PUT" resource params :forge 'bitbucket
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun buck-post (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `POST' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"POST\" as METHOD
-and `bitbucket' as FORGE."
- (ghub-request "POST" resource params :forge 'bitbucket
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun buck-delete (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"DELETE\" as METHOD
-and `bitbucket' as FORGE."
- (ghub-request "DELETE" resource params :forge 'bitbucket
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun buck-request (method resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a request for RESOURCE and return the response body.
-Like calling `ghub-request' (which see) with `bitbucket' as FORGE."
- (ghub-request method resource params :forge 'bitbucket
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun buck-repository-id (owner name &key username auth host)
- "Return the id of the repository specified by OWNER, NAME and HOST."
- (substring (cdr (assq 'uuid
- (buck-get (format "/repositories/%s/%s" owner name)
- nil
- :username username :auth auth :host host)))
- 1 -1))
-
-;;; _
-(provide 'buck)
-;;; buck.el ends here
+++ /dev/null
-This is the file .../info/dir, which contains the
-topmost node of the Info hierarchy, called (dir)Top.
-The first time you invoke Info you start off looking at this node.
-\1f
-File: dir, Node: Top This is the top of the INFO tree
-
- This (the Directory node) gives a menu of major topics.
- Typing "q" exits, "H" lists all Info commands, "d" returns here,
- "h" gives a primer for first-timers,
- "mEmacs<Return>" visits the Emacs manual, etc.
-
- In Emacs, you can click mouse button 2 on a menu item or cross reference
- to select it.
-
-* Menu:
-
-Emacs
-* Ghub: (ghub). Client library for the Github API.
+++ /dev/null
-;;; ghub-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
-;; Generated by the `loaddefs-generate' function.
-
-;; This file is part of GNU Emacs.
-
-;;; Code:
-
-(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
-
-
-\f
-;;; Generated autoloads from buck.el
-
-(register-definition-prefixes "buck" '("buck-default-host"))
-
-\f
-;;; Generated autoloads from ghub.el
-
-(autoload 'ghub-clear-caches "ghub" "\
-Clear all caches that might negatively affect Ghub.
-
-If a library that is used by Ghub caches incorrect information
-such as a mistyped password, then that can prevent Ghub from
-asking the user for the correct information again.
-
-Set `url-http-real-basic-auth-storage' to nil
-and call `auth-source-forget+'." t)
-(register-definition-prefixes "ghub" '("ghub-"))
-
-\f
-;;; Generated autoloads from ghub-graphql.el
-
-(register-definition-prefixes "ghub-graphql" '("ghub-"))
-
-\f
-;;; Generated autoloads from glab.el
-
-(register-definition-prefixes "glab" '("glab-default-host"))
-
-\f
-;;; Generated autoloads from gogs.el
-
-(register-definition-prefixes "gogs" '("gogs-default-host"))
-
-\f
-;;; Generated autoloads from gsexp.el
-
-(register-definition-prefixes "gsexp" '("gsexp-"))
-
-\f
-;;; Generated autoloads from gtea.el
-
-(register-definition-prefixes "gtea" '("gtea-default-host"))
-\f
-;;; End of scraped data
-
-(provide 'ghub-autoloads)
-
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; no-native-compile: t
-;; coding: utf-8-emacs-unix
-;; End:
-
-;;; ghub-autoloads.el ends here
+++ /dev/null
-;;; ghub-graphql.el --- Access Github API using GrapthQL -*- lexical-binding:t -*-
-
-;; Copyright (C) 2016-2024 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-
-;; SPDX-License-Identifier: GPL-3.0-or-later
-
-;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
-
-;;; Code:
-
-(require 'ghub)
-(require 'gsexp)
-(require 'treepy)
-
-;; Needed for Emacs < 27.
-(eval-when-compile (require 'json))
-(declare-function json-read-from-string "json" (string))
-(declare-function json-encode "json" (object))
-
-(eval-when-compile (require 'pp)) ; Needed for Emacs < 29.
-(eval-when-compile (require 'subr-x))
-
-;;; Api
-
-(define-error 'ghub-graphql-error "GraphQL Error" 'ghub-error)
-
-(defvar ghub-graphql-items-per-request 50
- "Number of GraphQL items to query for entities that return a collection.
-
-Adjust this value if you're hitting query timeouts against larger
-repositories.")
-
-(cl-defun ghub-graphql (graphql
- &optional variables
- &key username auth host forge
- headers silent
- callback errorback value extra)
- "Make a GraphQL request using GRAPHQL and VARIABLES.
-Return the response as a JSON-like alist. Even if the response
-contains `errors', do not raise an error. GRAPHQL is a GraphQL
-string. VARIABLES is a JSON-like alist. The other arguments
-behave as for `ghub-request' (which see)."
- (cl-assert (not (stringp variables)))
- (cl-assert (or (stringp graphql)
- (memq (car-safe graphql) '(query mutation))))
- (unless (stringp graphql)
- (setq graphql (gsexp-encode (ghub--graphql-prepare-query graphql))))
- (ghub-request "POST"
- (if (eq forge 'gitlab) "/api/graphql" "/graphql")
- nil
- :payload `((query . ,graphql)
- ,@(and variables `((variables ,@variables))))
- :headers headers :silent silent
- :username username :auth auth :host host :forge forge
- :callback callback :errorback errorback
- :extra extra :value value))
-
-(cl-defun ghub-graphql-rate-limit (&key username auth host)
- "Return rate limit information."
- (let-alist (ghub-graphql
- '(query (rateLimit limit cost remaining resetAt))
- nil :username username :auth auth :host host)
- .data.rateLimit))
-
-(cl-defun ghub--repository-id (owner name &key username auth host)
- "Return the id of the repository specified by OWNER, NAME and HOST."
- (let-alist (ghub-graphql
- '(query (repository [(owner $owner String!)
- (name $name String!)]
- id))
- `((owner . ,owner)
- (name . ,name))
- :username username :auth auth :host host)
- .data.repository.id))
-
-;;; Api (drafts)
-
-(defconst ghub-fetch-repository-sparse
- '(query
- (repository
- [(owner $owner String!)
- (name $name String!)]
- name
- id
- createdAt
- updatedAt
- nameWithOwner
- (parent nameWithOwner)
- description
- homepageUrl
- (defaultBranchRef name)
- isArchived
- isFork
- isLocked
- isMirror
- isPrivate
- hasIssuesEnabled
- hasWikiEnabled
- (licenseInfo name)
- (stargazers totalCount)
- (watchers totalCount))))
-
-(defconst ghub-fetch-repository
- `(query
- (repository
- ,@(cdr (cadr ghub-fetch-repository-sparse))
- (assignableUsers [(:edges t)]
- id
- login
- name)
- (issues [(:edges t)
- (:singular issue number)
- (orderBy ((field UPDATED_AT) (direction DESC)))]
- number
- id
- state
- stateReason
- isReadByViewer
- (author login)
- title
- createdAt
- updatedAt
- closedAt
- locked
- (milestone id)
- body
- (assignees [(:edges t)]
- id)
- (comments [(:edges t)]
- id
- databaseId
- (author login)
- createdAt
- updatedAt
- body)
- (labels [(:edges t)]
- id))
- (labels [(:edges t)
- (:singular label id)]
- id
- name
- color
- description)
- (milestones [(:edges t)
- (:singular milestone id)]
- id
- number
- title
- createdAt
- updatedAt
- dueOn
- closedAt
- description)
- (pullRequests [(:edges t)
- (:singular pullRequest number)
- (orderBy ((field UPDATED_AT) (direction DESC)))]
- number
- id
- state
- isReadByViewer
- (author login)
- title
- createdAt
- updatedAt
- closedAt
- mergedAt
- isDraft
- locked
- maintainerCanModify
- isCrossRepository
- (milestone id)
- body
- (baseRef name
- (repository nameWithOwner))
- baseRefOid
- (headRef name
- (repository (owner login)
- nameWithOwner))
- headRefOid
- (assignees [(:edges t)]
- id)
- (reviewRequests [(:edges t)]
- (requestedReviewer "... on User { id }\n"))
- (comments [(:edges t)]
- id
- databaseId
- (author login)
- createdAt
- updatedAt
- body)
- (labels [(:edges t)]
- id)))))
-
-(defconst ghub-fetch-repository-review-threads
- '(query
- (repository
- [(owner $owner String!)
- (name $name String!)]
- (pullRequests [(:edges t)
- (:singular pullRequest number)
- (orderBy ((field UPDATED_AT) (direction DESC)))]
- number
- baseRefOid
- headRefOid
- (reviewThreads [(:edges t)]
- id
- line
- originalLine
- diffSide
- (resolvedBy login)
- (comments [(:edges t)]
- id
- databaseId
- (author login)
- createdAt
- updatedAt
- body
- (replyTo databaseId)
- (originalCommit oid)
- path))))))
-
-(cl-defun ghub-fetch-repository ( owner name callback
- &optional until
- &key username auth host forge
- headers paginate errorback sparse)
- "Asynchronously fetch forge data about the specified repository.
-Once all data has been collected, CALLBACK is called with the
-data as the only argument."
- (ghub--graphql-vacuum (if sparse
- ghub-fetch-repository-sparse
- ghub-fetch-repository)
- `((owner . ,owner)
- (name . ,name))
- callback until
- :narrow '(repository)
- :username username
- :auth auth
- :host host
- :forge forge
- :headers headers
- :paginate paginate
- :errorback errorback))
-
-(cl-defun ghub-fetch-issue ( owner name number callback
- &optional until
- &key username auth host forge
- headers paginate errorback)
- "Asynchronously fetch forge data about the specified issue.
-Once all data has been collected, CALLBACK is called with the
-data as the only argument."
- (ghub--graphql-vacuum (ghub--graphql-narrow-query
- ghub-fetch-repository
- `(repository issues (issue . ,number)))
- `((owner . ,owner)
- (name . ,name))
- callback until
- :narrow '(repository issue)
- :username username
- :auth auth
- :host host
- :forge forge
- :headers headers
- :paginate paginate
- :errorback errorback))
-
-(cl-defun ghub-fetch-pullreq ( owner name number callback
- &optional until
- &key username auth host forge
- headers paginate errorback)
- "Asynchronously fetch forge data about the specified pull-request.
-Once all data has been collected, CALLBACK is called with the
-data as the only argument."
- (ghub--graphql-vacuum (ghub--graphql-narrow-query
- ghub-fetch-repository
- `(repository pullRequests (pullRequest . ,number)))
- `((owner . ,owner)
- (name . ,name))
- callback until
- :narrow '(repository pullRequest)
- :username username
- :auth auth
- :host host
- :forge forge
- :headers headers
- :paginate paginate
- :errorback errorback))
-
-(cl-defun ghub-fetch-review-threads ( owner name number callback
- &optional until
- &key username auth host forge
- headers paginate errorback)
- "Asynchronously fetch forge data about the review threads from a pull-request.
-Once all data has been collected, CALLBACK is called with the
-data as the only argument."
- (ghub--graphql-vacuum (ghub--graphql-narrow-query
- ghub-fetch-repository-review-threads
- `(repository pullRequests (pullRequest . ,number)))
- `((owner . ,owner)
- (name . ,name))
- callback until
- :narrow '(repository pullRequest)
- :username username
- :auth auth
- :host host
- :forge forge
- :headers headers
- :paginate paginate
- :errorback errorback))
-
-;;; Internal
-
-(defvar ghub--graphql-debug nil
- "Whether `ghub--graphql-retrieve' updates the \" *gsexp-encode*\" buffer.")
-
-(cl-defstruct (ghub--graphql-req
- (:include ghub--req)
- (:constructor ghub--make-graphql-req)
- (:copier nil))
- (query nil :read-only t)
- (query-str nil :read-only nil)
- (variables nil :read-only t)
- (until nil :read-only t)
- (pages 0 :read-only nil)
- (paginate nil :read-only nil)
- (narrow nil :read-only t))
-
-(cl-defun ghub--graphql-vacuum ( query variables callback
- &optional until
- &key narrow username auth host forge
- headers paginate errorback)
- "Make a GraphQL request using QUERY and VARIABLES.
-See Info node `(ghub)GraphQL Support'."
- (unless host
- (setq host (ghub--host forge)))
- (unless (or username (stringp auth) (eq auth 'none))
- (setq username (ghub--username host forge)))
- (ghub--graphql-retrieve
- (ghub--make-graphql-req
- :url (url-generic-parse-url
- (format "https://%s/graphql"
- (if (string-suffix-p "/v3" host)
- (substring host 0 -3)
- host)))
- :method "POST"
- :headers (ghub--headers headers host auth username forge)
- :handler #'ghub--graphql-handle-response
- :query query
- :variables variables
- :until until
- :buffer (current-buffer)
- :narrow narrow
- :paginate (or paginate
- (and-let* ((p (and (eq auth 'forge)
- (fboundp 'magit-get)
- (magit-get "forge.graphqlItemLimit"))))
- (string-to-number p)))
- :callback (and (not (eq callback 'synchronous)) callback)
- :errorback (and (not (eq callback 'synchronous)) errorback))))
-
-(defvar ghub--graphql-synchronous-value nil)
-
-(cl-defun ghub--graphql-synchronous ( query variables
- &optional until
- &key narrow username auth host forge
- headers paginate)
- "Make a synchronous GraphQL request using QUERY and VARIABLES.
-See Info node `(ghub)GraphQL Support'."
- (unwind-protect
- (progn (ghub--graphql-vacuum query variables 'synchronous until
- :narrow narrow :username username
- :auth auth :host host :forge forge
- :headers headers :paginate paginate)
- ghub--graphql-synchronous-value)
- (setq ghub--graphql-synchronous-value nil)))
-
-(cl-defun ghub--graphql-retrieve (req &optional lineage cursor)
- (let ((p (cl-incf (ghub--graphql-req-pages req))))
- (when (> p 1)
- (ghub--graphql-set-mode-line req "Fetching page %s" p)))
- (setf (ghub--graphql-req-query-str req)
- (gsexp-encode
- (ghub--graphql-prepare-query
- (ghub--graphql-req-query req)
- lineage cursor)))
- (when ghub--graphql-debug
- (with-current-buffer (get-buffer-create " *gsexp-encode*")
- (erase-buffer)
- (insert (ghub--graphql-req-query-str req))))
- (ghub--retrieve
- (let ((json-false nil))
- (ghub--encode-payload
- `((query . ,(ghub--graphql-req-query-str req))
- (variables . ,(ghub--graphql-req-variables req)))))
- req))
-
-(defun ghub--graphql-prepare-query (query &optional lineage cursor paginate)
- (when lineage
- (setq query (ghub--graphql-narrow-query query lineage cursor)))
- (let ((loc (ghub--alist-zip query))
- variables)
- (cl-block nil
- (while t
- (let ((node (treepy-node loc)))
- (when (and (vectorp node)
- (listp (aref node 0)))
- (let ((alist (cl-coerce node 'list))
- vars)
- (when-let ((edges (cadr (assq :edges alist))))
- (push (list 'first
- (apply
- #'min
- (delq nil (list (and (numberp edges) edges)
- paginate
- ghub-graphql-items-per-request))))
- vars)
- (setq loc (treepy-up loc))
- (setq node (treepy-node loc))
- (setq loc (treepy-replace
- loc `(,(car node)
- ,(cadr node)
- (pageInfo endCursor hasNextPage)
- (edges (node ,@(cddr node))))))
- (setq loc (treepy-down loc))
- (setq loc (treepy-next loc)))
- (dolist (elt alist)
- (cond ((keywordp (car elt)))
- ((length= elt 3)
- (push (list (nth 0 elt) (nth 1 elt)) vars)
- (push (list (nth 1 elt) (nth 2 elt)) variables))
- ((length= elt 2)
- (push elt vars))))
- (setq loc (treepy-replace loc (vconcat (nreverse vars)))))))
- (if (treepy-end-p loc)
- (let ((node (copy-sequence (treepy-node loc))))
- (when variables
- (push (cl-coerce variables 'vector)
- (cdr node)))
- (cl-return node))
- (setq loc (treepy-next loc)))))))
-
-(defun ghub--graphql-handle-response (status req)
- (let ((buf (current-buffer)))
- (unwind-protect
- (progn
- (set-buffer-multibyte t)
- (let* ((headers (ghub--handle-response-headers status req))
- (payload (ghub--handle-response-payload req))
- (payload (ghub--handle-response-error status payload req))
- (err (plist-get status :error))
- (errors (cdr (assq 'errors payload)))
- (errors (and errors (cons 'ghub-graphql-error errors))))
- (if (or err errors)
- (ghub--graphql-handle-failure
- req (or err errors) headers status)
- (ghub--graphql-walk-response req (assq 'data payload)))))
- (when (buffer-live-p buf)
- (kill-buffer buf)))))
-
-(defun ghub--graphql-handle-failure (req errors headers status)
- (if-let ((errorback (ghub--req-errorback req)))
- (let ((buffer (ghub--req-buffer req)))
- (with-current-buffer
- (if (buffer-live-p buffer) buffer (current-buffer))
- (funcall errorback errors headers status req)))
- (ghub--signal-error errors)))
-
-(defun ghub--graphql-handle-success (req data)
- (let ((callback (ghub--req-callback req))
- (buffer (ghub--req-buffer req))
- (narrow (ghub--graphql-req-narrow req)))
- (while-let ((key (pop narrow)))
- (setq data (cdr (assq key data))))
- (with-current-buffer
- (if (buffer-live-p buffer) buffer (current-buffer))
- (funcall (or callback #'ghub--graphql-pp-response)
- data))))
-
-(defun ghub--graphql-walk-response (req data)
- (let* ((loc (ghub--req-value req))
- (loc (if (not loc)
- (ghub--alist-zip data)
- (setq data (ghub--graphql-narrow-data
- data (ghub--graphql-lineage loc)))
- (setf (alist-get 'edges data)
- (append (alist-get 'edges (treepy-node loc))
- (or (alist-get 'edges data)
- (error "BUG: Expected new nodes"))))
- (treepy-replace loc data))))
- (cl-block nil
- (while t
- (when (eq (car-safe (treepy-node loc)) 'edges)
- (setq loc (treepy-up loc))
- (pcase-let ((`(,key . ,val) (treepy-node loc)))
- (let-alist val
- (let* ((cursor (and .pageInfo.hasNextPage
- .pageInfo.endCursor))
- (until (cdr (assq (intern (format "%s-until" key))
- (ghub--graphql-req-until req))))
- (nodes (mapcar #'cdar .edges))
- (nodes (if until
- (seq-take-while
- (lambda (node)
- (or (string> (cdr (assq 'updatedAt node))
- until)
- (setq cursor nil)))
- nodes)
- nodes)))
- (cond (cursor
- (setf (ghub--req-value req) loc)
- (ghub--graphql-retrieve req
- (ghub--graphql-lineage loc)
- cursor)
- (cl-return))
- ((setq loc (treepy-replace loc (cons key nodes)))))))))
- (cond ((not (treepy-end-p loc))
- (setq loc (treepy-next loc)))
- ((ghub--req-callback req)
- (ghub--graphql-handle-success req (treepy-root loc))
- (ghub--graphql-set-mode-line req nil)
- (cl-return))
- (t
- (setq ghub--graphql-synchronous-value (treepy-root loc))
- (cl-return)))))))
-
-(defun ghub--graphql-lineage (loc)
- (let (lineage)
- (while (treepy-up loc)
- (push (car (treepy-node loc)) lineage)
- (setq loc (treepy-up loc)))
- lineage))
-
-(defun ghub--graphql-narrow-data (data lineage)
- (while-let ((key (pop lineage)))
- (if (consp (car lineage))
- (progn (pop lineage)
- (setf data (cadr data)))
- (setq data (assq key (cdr data)))))
- data)
-
-(defun ghub--graphql-narrow-query (query lineage &optional cursor)
- (if (consp (car lineage))
- (let* ((child (cddr query))
- (alist (cl-coerce (cadr query) 'list))
- (single (cdr (assq :singular alist))))
- `(,(car single)
- ,(vector (list (cadr single) (cdr (car lineage))))
- ,@(if (cdr lineage)
- (ghub--graphql-narrow-query child (cdr lineage) cursor)
- child)))
- (let* ((child (or (assq (car lineage) (cdr query))
- ;; Alias
- (cl-find-if (lambda (c)
- (eq (car-safe (car-safe c))
- (car lineage)))
- query)
- ;; Edges
- (cl-find-if (lambda (c)
- (and (listp c)
- (vectorp (cadr c))
- (eq (cadr (assq :singular
- (cl-coerce (cadr c)
- 'list)))
- (car lineage))))
- (cdr query))
- (error "BUG: Failed to narrow query")))
- (object (car query))
- (args (and (vectorp (cadr query))
- (cadr query))))
- `(,object
- ,@(and args (list args))
- ,(cond ((cdr lineage)
- (ghub--graphql-narrow-query child (cdr lineage) cursor))
- (cursor
- `(,(car child)
- ,(vconcat `((after ,cursor))
- (cadr child))
- ,@(cddr child)))
- (t
- child))))))
-
-(defun ghub--alist-zip (root)
- (let ((branchp (lambda (elt) (and (listp elt) (listp (cdr elt)))))
- (make-node (lambda (_ children) children)))
- (treepy-zipper branchp #'identity make-node root)))
-
-(defun ghub--graphql-set-mode-line (req string &rest args)
- (let ((buffer (ghub--graphql-req-buffer req)))
- (when (buffer-live-p buffer)
- (with-current-buffer buffer
- (setq mode-line-process
- (and string (concat " " (apply #'format string args))))
- (force-mode-line-update t)))))
-
-(defun ghub--graphql-pp-response (data)
- (require 'pp) ; needed for Emacs < 29.
- (pp-display-expression data "*Pp Eval Output*"))
-
-;;; _
-(provide 'ghub-graphql)
-;;; ghub-graphql.el ends here
+++ /dev/null
-;; -*- no-byte-compile: t; lexical-binding: nil -*-
-(define-package "ghub" "20241208.2219"
- "Client libraries for Git forge APIs."
- '((emacs "29.1")
- (compat "30.0.0.0")
- (let-alist "1.0.6")
- (treepy "0.1.2"))
- :url "https://github.com/magit/ghub"
- :commit "97edaf450ef74f40e6c0bd6a35ebd3fcb710ca4d"
- :revdesc "97edaf450ef7"
- :keywords '("tools")
- :authors '(("Jonas Bernoulli" . "emacs.ghubNOSPAMjonas.bernoulli.dev"))
- :maintainers '(("Jonas Bernoulli" . "emacs.ghubNOSPAMjonas.bernoulli.dev")))
+++ /dev/null
-;;; ghub.el --- Client libraries for Git forge APIs -*- lexical-binding:t -*-
-
-;; Copyright (C) 2016-2024 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-
-;; Package-Version: 20241208.2219
-;; Package-Revision: 97edaf450ef7
-;; Package-Requires: (
-;; (emacs "29.1")
-;; (compat "30.0.0.0")
-;; (let-alist "1.0.6")
-;; (treepy "0.1.2"))
-
-;; SPDX-License-Identifier: GPL-3.0-or-later
-
-;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Ghub provides basic support for using the APIs of various Git forges
-;; from Emacs packages. Originally it only supported the Github REST
-;; API, but now it also supports the Github GraphQL API as well as the
-;; REST APIs of Gitlab, Gitea, Gogs and Bitbucket.
-
-;; Ghub abstracts access to API resources using only a handful of basic
-;; functions such as `ghub-get'. These are convenience wrappers around
-;; `ghub-request'. Additional forge-specific wrappers like `glab-put',
-;; `gtea-put', `gogs-post' and `buck-delete' are also available. Ghub
-;; does not provide any resource-specific functions, with the exception
-;; of `FORGE-repository-id'.
-
-;; When accessing Github, then Ghub handles the creation and storage of
-;; access tokens using a setup wizard to make it easier for users to get
-;; started. The tokens for other forges have to be created manually.
-
-;; Ghub is intentionally limited to only provide these two essential
-;; features — basic request functions and guided setup — to avoid being
-;; too opinionated, which would hinder wide adoption. It is assumed that
-;; wide adoption would make life easier for users and maintainers alike,
-;; because then all packages that talk to forge APIs could be configured
-;; the same way.
-
-;; Please consult the manual (info "ghub") for more information.
-
-;;; Code:
-
-(require 'auth-source)
-(require 'cl-lib)
-(require 'compat)
-(require 'gnutls)
-(require 'let-alist)
-(require 'url)
-(require 'url-auth)
-(require 'url-http)
-
-(eval-when-compile (require 'subr-x))
-
-;; Needed for Emacs < 27.
-(eval-when-compile (require 'json))
-(declare-function json-read-from-string "json" (string))
-(declare-function json-encode "json" (object))
-
-(declare-function glab-repository-id "glab" (owner name &key username auth host))
-(declare-function gtea-repository-id "gtea" (owner name &key username auth host))
-(declare-function gogs-repository-id "gogs" (owner name &key username auth host))
-(declare-function buck-repository-id "buck" (owner name &key username auth host))
-
-(defvar url-callback-arguments)
-(defvar url-http-end-of-headers)
-(defvar url-http-extra-headers)
-(defvar url-http-response-status)
-
-;;; Settings
-
-(defconst ghub-default-host "api.github.com"
- "The default host that is used if `ghub.host' is not set.")
-
-(defvar ghub-github-token-scopes '(repo)
- "The Github API scopes that your private tools need.
-
-You have to manually create or update the token at
-https://github.com/settings/tokens. This variable
-only serves as documentation.")
-
-(defvar ghub-insecure-hosts nil
- "List of hosts that use http instead of https.")
-
-(defvar ghub-json-use-jansson nil
- "Whether to use the Jansson library, if available.
-This is experimental. Only let-bind this but do not enable it
-globally because doing that is likely to break other packages
-that use `ghub'. As a user also do not enable this yet.
-See https://github.com/magit/ghub/pull/149.")
-
-(defvar ghub-json-object-type 'alist
- "The object type that is used for json payload decoding.")
-
-(defvar ghub-json-array-type 'list
- "The array type that is used for json payload decoding.")
-
-;;; Request
-;;;; Object
-
-(cl-defstruct (ghub--req
- (:constructor ghub--make-req)
- (:copier nil))
- (url nil :read-only nil)
- (forge nil :read-only t)
- (silent nil :read-only t)
- (method nil :read-only t)
- (headers nil :read-only t)
- (handler nil :read-only t)
- (unpaginate nil :read-only nil)
- (noerror nil :read-only t)
- (reader nil :read-only t)
- (buffer nil :read-only t)
- (callback nil :read-only t)
- (errorback nil :read-only t)
- (value nil :read-only nil)
- (extra nil :read-only nil))
-
-(defalias 'ghub-req-extra #'ghub--req-extra)
-
-;;;; API
-
-(define-error 'ghub-error "Ghub/Url Error" 'error)
-(define-error 'ghub-http-error "HTTP Error" 'ghub-error)
-
-(defvar ghub-response-headers nil
- "The headers returned in response to the last request.
-`ghub-request' returns the response body and stores the
-response headers in this variable.")
-
-(cl-defun ghub-head (resource
- &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `HEAD' request for RESOURCE, with optional query PARAMS.
-Like calling `ghub-request' (which see) with \"HEAD\" as METHOD."
- (ghub-request "HEAD" resource params
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun ghub-get (resource
- &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `GET' request for RESOURCE, with optional query PARAMS.
-Like calling `ghub-request' (which see) with \"GET\" as METHOD."
- (ghub-request "GET" resource params
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun ghub-put (resource
- &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PUT\" as METHOD."
- (ghub-request "PUT" resource params
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun ghub-post (resource
- &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `POST' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"POST\" as METHOD."
- (ghub-request "POST" resource params
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun ghub-patch (resource
- &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PATCH' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PATCH\" as METHOD."
- (ghub-request "PATCH" resource params
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun ghub-delete (resource
- &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"DELETE\" as METHOD."
- (ghub-request "DELETE" resource params
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun ghub-request ( method resource
- &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host forge
- callback errorback value extra)
- "Make a request for RESOURCE and return the response body.
-
-Also place the response headers in `ghub-response-headers'.
-
-METHOD is the HTTP method, given as a string.
-RESOURCE is the resource to access, given as a string beginning
- with a slash.
-
-PARAMS, QUERY, PAYLOAD and HEADERS are alists used to specify
- data. The Github API documentation is vague on how data has
- to be transmitted and for a particular resource usually just
- talks about \"parameters\". Generally speaking when the METHOD
- is \"HEAD\" or \"GET\", then they have to be transmitted as a
- query, otherwise as a payload.
-Use PARAMS to automatically transmit like QUERY or PAYLOAD would
- depending on METHOD.
-Use QUERY to explicitly transmit data as a query.
-Use PAYLOAD to explicitly transmit data as a payload.
- Instead of an alist, PAYLOAD may also be a string, in which
- case it gets encoded as UTF-8 but is otherwise transmitted as-is.
-Use HEADERS for those rare resources that require that the data
- is transmitted as headers instead of as a query or payload.
- When that is the case, then the API documentation usually
- mentions it explicitly.
-
-If SILENT is non-nil, then don't message progress reports and
- the like.
-
-If UNPAGINATE is t, then make as many requests as necessary to
- get all values. If UNPAGINATE is a natural number, then get
- at most that many pages. For any other non-nil value raise
- an error.
-If NOERROR is non-nil, then do not raise an error if the request
- fails and return nil instead. If NOERROR is `return', then
- return the error payload instead of nil.
-If READER is non-nil, then it is used to read and return from the
- response buffer. The default is `ghub--read-json-payload'.
- For the very few resources that do not return JSON, you might
- want to use `ghub--decode-payload'.
-
-If USERNAME is non-nil, then make a request on behalf of that
- user. It is better to specify the user using the Git variable
- `github.user' for \"api.github.com\", or `github.HOST.user' if
- connecting to a Github Enterprise instance.
-
-Each package that uses `ghub' should use its own token. If AUTH
- is nil, then the generic `ghub' token is used instead. This
- is only acceptable for personal utilities. A packages that
- is distributed to other users should always use this argument
- to identify itself, using a symbol matching its name.
-
- Package authors who find this inconvenient should write a
- wrapper around this function and possibly for the
- method-specific functions as well.
-
- Some symbols have a special meaning. `none' means to make an
- unauthorized request. `basic' means to make a password based
- request. If the value is a string, then it is assumed to be
- a valid token. `basic' and an explicit token string are only
- intended for internal and debugging uses.
-
-If HOST is non-nil, then connect to that Github instance. This
- defaults to \"api.github.com\". When a repository is connected
- to a Github Enterprise instance, then it is better to specify
- that using the Git variable `github.host' instead of using this
- argument.
-
-If FORGE is `gitlab', then connect to Gitlab.com or, depending
- on HOST, to another Gitlab instance. This is only intended for
- internal use. Instead of using this argument you should use
- function `glab-request' and other `glab-*' functions.
-
-If CALLBACK and/or ERRORBACK is non-nil, then make one or more
- asynchronous requests and call CALLBACK or ERRORBACK when
- finished. If no error occurred, then call CALLBACK, unless
- that is nil.
-
- If an error occurred, then call ERRORBACK, or if that is nil,
- then CALLBACK. ERRORBACK can also be t, in which case an error
- is signaled instead. NOERROR is ignored for all asynchronous
- requests.
-
-Both callbacks are called with four arguments.
- 1. For CALLBACK, the combined value of the retrieved pages.
- For ERRORBACK, the error that occurred when retrieving the
- last page.
- 2. The headers of the last page as an alist.
- 3. Status information provided by `url-retrieve'. Its `:error'
- property holds the same information as ERRORBACK's first
- argument.
- 4. A `ghub--req' struct, which can be passed to `ghub-continue'
- (which see) to retrieve the next page, if any."
- (cl-assert (or (booleanp unpaginate) (natnump unpaginate)))
- (unless (string-prefix-p "/" resource)
- (setq resource (concat "/" resource)))
- (unless host
- (setq host (ghub--host forge)))
- (unless (or username (stringp auth) (eq auth 'none))
- (setq username (ghub--username host forge)))
- (cond ((not params))
- ((member method '("GET" "HEAD"))
- (when query
- (error "PARAMS and QUERY are mutually exclusive for METHOD %S"
- method))
- (setq query params))
- (t
- (when payload
- (error "PARAMS and PAYLOAD are mutually exclusive for METHOD %S"
- method))
- (setq payload params)))
- (when (or callback errorback)
- (setq noerror t))
- (ghub--retrieve
- (ghub--encode-payload payload)
- (ghub--make-req
- :url (url-generic-parse-url
- (concat (if (member host ghub-insecure-hosts) "http://" "https://")
- (cond ((and (equal resource "/graphql")
- (string-suffix-p "/v3" host))
- ;; Needed for some Github Enterprise instances.
- (substring host 0 -3))
- ((and (equal resource "/api/graphql")
- (string-suffix-p "/api/v4" host))
- ;; Needed for all Gitlab instances.
- (substring host 0 -7))
- (host))
- resource
- (and query (concat "?" (ghub--url-encode-params query)))))
- :forge forge
- :silent silent
- ;; Encode in case caller used (symbol-name 'GET). #35
- :method (encode-coding-string method 'utf-8)
- :headers (ghub--headers headers host auth username forge)
- :handler #'ghub--handle-response
- :unpaginate unpaginate
- :noerror noerror
- :reader reader
- :buffer (current-buffer)
- :callback callback
- :errorback errorback
- :value value
- :extra extra)))
-
-(defun ghub-continue (req)
- "If there is a next page, then retrieve that.
-
-This function is only intended to be called from callbacks. If
-there is a next page, then retrieve that and return the buffer
-that the result will be loaded into, or t if the process has
-already completed. If there is no next page, then return nil.
-
-Callbacks are called with four arguments (see `ghub-request').
-The forth argument is a `ghub--req' struct, intended to be passed
-to this function. A callback may use the struct's `extra' slot
-to pass additional information to the callback that will be
-called after the next request has finished. Use the function
-`ghub-req-extra' to get and set the value of this slot."
- (and (assq 'next (ghub-response-link-relations req))
- (or (ghub--retrieve nil req) t)))
-
-(cl-defun ghub-wait (resource
- &optional duration
- &key username auth host forge)
- "Busy-wait up to DURATION seconds for RESOURCE to become available.
-
-DURATION specifies how many seconds to wait at most. It defaults
-to 64 seconds. The first attempt is made immediately, the second
-after two seconds, and each subsequent attempt is made after
-waiting as long again as we already waited between all preceding
-attempts combined.
-
-See `ghub-request' for information about the other arguments."
- (unless duration
- (setq duration 64))
- (with-local-quit
- (let ((total 0))
- (while (not (ghub-request "GET" resource nil
- :noerror t
- :username username
- :auth auth
- :host host
- :forge forge))
- (message "Waited (%3ss of %ss) for %s..." total duration resource)
- (if (= total duration)
- (error "%s is taking too long to create %s"
- (if forge (capitalize (symbol-name forge)) "Github")
- resource)
- (if (> total 0)
- (let ((wait (min total (- duration total))))
- (sit-for wait)
- (cl-incf total wait))
- (sit-for (setq total 2))))))))
-
-(defun ghub-response-link-relations (req &optional headers payload)
- "Return an alist of link relations in HEADERS.
-If optional HEADERS is nil, then return those that were
-previously stored in the variable `ghub-response-headers'.
-
-When accessing a Bitbucket instance then the link relations
-are in PAYLOAD instead of HEADERS, making their API merely
-RESTish and forcing this function to append those relations
-to the value of `ghub-response-headers', for later use when
-this function is called with nil for PAYLOAD."
- (if (eq (ghub--req-forge req) 'bitbucket)
- (if payload
- (let* ((page (mapcan (lambda (key)
- (and-let* ((elt (assq key payload)))
- (list elt)))
- '(size page pagelen next previous)))
- (headers (cons (cons 'link-alist page) headers)))
- (if (and req (or (ghub--req-callback req)
- (ghub--req-errorback req)))
- (setq-local ghub-response-headers headers)
- (setq-default ghub-response-headers headers))
- page)
- (cdr (assq 'link-alist ghub-response-headers)))
- (and-let* ((headers (or headers ghub-response-headers))
- (rels (cdr (or (assoc "Link" headers)
- (assoc "link" headers)))))
- (mapcar (lambda (elt)
- (pcase-let ((`(,url ,rel) (split-string elt "; ")))
- (cons (intern (substring rel 5 -1))
- (substring url 1 -1))))
- (split-string rels ", ")))))
-
-(cl-defun ghub-repository-id (owner name &key username auth host forge noerror)
- "Return the id of the specified repository.
-Signal an error if the id cannot be determined."
- (let ((fn (cl-case forge
- ((nil ghub github) #'ghub--repository-id)
- (gitlab #'glab-repository-id)
- (gitea #'gtea-repository-id)
- (gogs #'gogs-repository-id)
- (bitbucket #'buck-repository-id)
- (t (intern (format "%s-repository-id" forge))))))
- (unless (fboundp fn)
- (error "ghub-repository-id: Forge type/abbreviation `%s' is unknown"
- forge))
- (or (funcall fn owner name :username username :auth auth :host host)
- (and (not noerror)
- (error "Repository %S does not exist on %S.\n%s%S?"
- (concat owner "/" name)
- (or host (ghub--host forge))
- "Maybe it was renamed and you have to update "
- "remote.<remote>.url")))))
-
-;;;; Internal
-
-(cl-defun ghub--retrieve (payload req)
- (let ((url-request-extra-headers
- (let ((headers (ghub--req-headers req)))
- (if (functionp headers) (funcall headers) headers)))
- (url-request-method (ghub--req-method req))
- (url-request-data payload)
- (url-show-status nil)
- (url (ghub--req-url req))
- (handler (ghub--req-handler req))
- (silent (ghub--req-silent req)))
- (if (or (ghub--req-callback req)
- (ghub--req-errorback req))
- (url-retrieve url handler (list req) silent)
- (if-let ((buf (url-retrieve-synchronously url silent)))
- (with-current-buffer buf
- (funcall handler (car url-callback-arguments) req))
- (error "ghub--retrieve: No buffer returned")))))
-
-(defun ghub--handle-response (status req)
- (let ((buf (current-buffer)))
- (unwind-protect
- (progn
- (set-buffer-multibyte t)
- (let* ((unpaginate (ghub--req-unpaginate req))
- (headers (ghub--handle-response-headers status req))
- (payload (ghub--handle-response-payload req))
- (payload (ghub--handle-response-error status payload req))
- (value (ghub--handle-response-value payload req))
- (prev (ghub--req-url req))
- (next (cdr (assq 'next (ghub-response-link-relations
- req headers payload)))))
- (when (numberp unpaginate)
- (cl-decf unpaginate))
- (setf (ghub--req-url req)
- (url-generic-parse-url next))
- (setf (ghub--req-unpaginate req) unpaginate)
- (or (and next
- unpaginate
- (or (eq unpaginate t)
- (> unpaginate 0))
- (ghub-continue req))
- (let ((req-buf (ghub--req-buffer req))
- (callback (ghub--req-callback req))
- (errorback (ghub--req-errorback req))
- (err (plist-get status :error)))
- (cond ((and err errorback)
- (setf (ghub--req-url req) prev)
- (funcall (if (eq errorback t)
- 'ghub--errorback
- errorback)
- err headers status req))
- (callback
- (save-current-buffer
- (when (buffer-live-p req-buf)
- (set-buffer req-buf))
- (funcall callback value headers status req)))
- (t value))))))
- (when (buffer-live-p buf)
- (kill-buffer buf)))))
-
-(defun ghub--handle-response-headers (_status req)
- (let (headers)
- (when (memq url-http-end-of-headers '(nil 0))
- (unless url-debug (setq url-debug t))
- (error "BUG: Missing headers in response buffer %s" (current-buffer)))
- (goto-char (point-min))
- (forward-line 1)
- (while (re-search-forward "^\\([^:]*\\): \\(.+\\)"
- url-http-end-of-headers t)
- (push (cons (match-string 1)
- (match-string 2))
- headers))
- (setq headers (nreverse headers))
- (goto-char (1+ url-http-end-of-headers))
- (if (and req (or (ghub--req-callback req)
- (ghub--req-errorback req)))
- (setq-local ghub-response-headers headers)
- (setq-default ghub-response-headers headers))
- headers))
-
-(defun ghub--handle-response-error (status payload req)
- (if-let ((err (plist-get status :error)))
- (if-let ((noerror (ghub--req-noerror req)))
- (if (eq noerror 'return)
- payload
- (setcdr (last err) (list payload))
- nil)
- (ghub--signal-error err payload req))
- payload))
-
-(defun ghub--signal-error (err &optional payload req)
- (pcase-let ((`(,symb . ,data) err))
- (if (eq symb 'error)
- (if (eq (car-safe data) 'http)
- (signal 'ghub-http-error
- (let ((code (car (cdr-safe data))))
- (list code
- (nth 2 (assq code url-http-codes))
- (and req (url-recreate-url (ghub--req-url req)))
- payload)))
- (signal 'ghub-error data))
- (signal symb data))))
-
-(defun ghub--errorback (err _headers _status req)
- (ghub--signal-error err (nth 3 err) req))
-
-(defun ghub--handle-response-value (payload req)
- (setf (ghub--req-value req)
- (nconc (ghub--req-value req)
- (if-let ((nested (and (eq (ghub--req-forge req) 'bitbucket)
- (assq 'values payload))))
- (cdr nested)
- payload))))
-
-(defun ghub--handle-response-payload (req)
- (funcall (or (ghub--req-reader req)
- 'ghub--read-json-payload)
- url-http-response-status))
-
-(defun ghub--read-json-payload (_status)
- (let ((raw (ghub--decode-payload)))
- (and raw
- (condition-case nil
- (if (and ghub-json-use-jansson
- (fboundp 'json-parse-string))
- (json-parse-string
- raw
- :object-type ghub-json-object-type
- :array-type ghub-json-array-type
- :false-object nil
- :null-object nil)
- (require 'json)
- (let ((json-object-type ghub-json-object-type)
- (json-array-type ghub-json-array-type)
- (json-false nil)
- (json-null nil))
- (json-read-from-string raw)))
- ((json-parse-error json-readtable-error)
- `((message
- . ,(if (looking-at "<!DOCTYPE html>")
- (if (re-search-forward
- "<p>\\(?:<strong>\\)?\\([^<]+\\)" nil t)
- (match-string 1)
- "error description missing")
- (string-trim (buffer-substring (point) (point-max)))))
- (documentation_url
- . "https://github.com/magit/ghub/wiki/Github-Errors")))))))
-
-(defun ghub--decode-payload (&optional _status)
- (and (not (eobp))
- (decode-coding-string
- (buffer-substring-no-properties (point) (point-max))
- 'utf-8)))
-
-(defun ghub--encode-payload (payload)
- (and payload
- (progn
- (unless (stringp payload)
- (setq payload
- (if (and ghub-json-use-jansson
- (fboundp 'json-serialize))
- (json-serialize payload
- ;; :object-type and :array-type
- ;; are not supported here.
- :false-object nil
- :null-object :null)
- (require 'json)
- (let ((json-object-type ghub-json-object-type)
- (json-array-type ghub-json-array-type)
- (json-false nil)
- (json-null :null))
- ;; Unfortunately `json-encode' may modify the input.
- ;; See https://debbugs.gnu.org/cgi/bugreport.cgi?bug=40693.
- ;; and https://github.com/magit/forge/issues/267
- (json-encode (copy-tree payload))))))
- (encode-coding-string payload 'utf-8))))
-
-(defun ghub--url-encode-params (params)
- (mapconcat (lambda (param)
- (pcase-let ((`(,key . ,val) param))
- (concat (url-hexify-string (symbol-name key)) "="
- (cl-typecase val
- (integer (number-to-string val))
- (boolean (if val "true" "false"))
- (t (url-hexify-string val))))))
- params "&"))
-
-;;; Authentication
-;;;; API
-
-;;;###autoload
-(defun ghub-clear-caches ()
- "Clear all caches that might negatively affect Ghub.
-
-If a library that is used by Ghub caches incorrect information
-such as a mistyped password, then that can prevent Ghub from
-asking the user for the correct information again.
-
-Set `url-http-real-basic-auth-storage' to nil
-and call `auth-source-forget+'."
- (interactive)
- (setq url-http-real-basic-auth-storage nil)
- (auth-source-forget+))
-
-;;;; Internal
-
-(defun ghub--headers (headers host auth username forge)
- (push (cons "Content-Type" "application/json") headers)
- (if (eq auth 'none)
- headers
- (unless (or username (stringp auth))
- (setq username (ghub--username host forge)))
- (lambda ()
- (if (eq auth 'basic)
- (cons (cons "Authorization" (ghub--basic-auth host username))
- headers)
- (cons (ghub--auth host auth username forge) headers)))))
-
-(cl-defgeneric ghub--auth (host auth &optional username forge)
- (unless username
- (setq username (ghub--username host forge)))
- (if (eq auth 'basic)
- (cl-ecase forge
- ((nil gitea gogs bitbucket)
- (cons "Authorization" (ghub--basic-auth host username)))
- ((github gitlab)
- (error "%s does not support basic authentication"
- (capitalize (symbol-name forge)))))
- (cons (cl-ecase forge
- ((nil github gitea gogs bitbucket)
- "Authorization")
- (gitlab
- "Private-Token"))
- (if (eq forge 'bitbucket)
- ;; For some undocumented reason Bitbucket supports
- ;; values of the form "token <token>" only for GET
- ;; requests. For PUT requests we have to use basic
- ;; authentication. Note that the secret is a token
- ;; (aka "app password"), not the actual password.
- ;; The documentation fails to mention this little
- ;; detail. See #97.
- (concat "Basic "
- (base64-encode-string
- (concat username ":"
- (ghub--token host username auth nil forge))
- t))
- (concat
- (and (not (eq forge 'gitlab)) "token ")
- (encode-coding-string
- (cl-typecase auth
- (string auth)
- (null (ghub--token host username 'ghub nil forge))
- (symbol (ghub--token host username auth nil forge))
- (t (signal 'wrong-type-argument
- `((or stringp symbolp) ,auth))))
- 'utf-8))))))
-
-(defun ghub--basic-auth (host username)
- (let ((url (url-generic-parse-url
- (if (member host ghub-insecure-hosts) "http://" "https://"))))
- (setf (url-user url) username)
- (url-basic-auth url t)))
-
-(defun ghub--token (host username package &optional nocreate forge)
- (let* ((user (ghub--ident username package))
- (token
- (or (car (ghub--auth-source-get (list :secret)
- :host host :user user))
- (progn
- ;; Auth-Source caches the information that there is no
- ;; value, but in our case that is a situation that needs
- ;; fixing so we want to keep trying by invalidating that
- ;; information.
- ;; The (:max 1) is needed and has to be placed at the
- ;; end for Emacs releases before 26.1. #24 #64 #72
- (auth-source-forget (list :host host :user user :max 1))
- (and (not nocreate)
- (error "\
-Required %s token (\"%s\" for \"%s\") does not exist.
-See https://magit.vc/manual/ghub/Getting-Started.html
-or (info \"(ghub)Getting Started\") for instructions.
-\(The setup wizard no longer exists.)"
- (capitalize (symbol-name (or forge 'github)))
- user host))))))
- (if (functionp token) (funcall token) token)))
-
-(cl-defgeneric ghub--host (&optional forge)
- (cl-ecase forge
- ((nil github)
- (or (ignore-errors (car (process-lines "git" "config" "github.host")))
- ghub-default-host))
- (gitlab
- (or (ignore-errors (car (process-lines "git" "config" "gitlab.host")))
- (bound-and-true-p glab-default-host)))
- (gitea
- (or (ignore-errors (car (process-lines "git" "config" "gitea.host")))
- (bound-and-true-p gtea-default-host)))
- (gogs
- (or (ignore-errors (car (process-lines "git" "config" "gogs.host")))
- (bound-and-true-p gogs-default-host)))
- (bitbucket
- (or (ignore-errors (car (process-lines "git" "config" "bitbucket.host")))
- (bound-and-true-p buck-default-host)))))
-
-(cl-defgeneric ghub--username (host &optional forge)
- (let ((var
- (cl-ecase forge
- ((nil github)
- (if (equal host ghub-default-host)
- "github.user"
- (format "github.%s.user" host)))
- (gitlab
- (if (equal host "gitlab.com/api/v4")
- "gitlab.user"
- (format "gitlab.%s.user" host)))
- (bitbucket
- (if (equal host "api.bitbucket.org/2.0")
- "bitbucket.user"
- (format "bitbucket.%s.user" host)))
- (gitea
- (when (zerop (call-process "git" nil nil nil "config" "gitea.host"))
- (message "WARNING: gitea.host is set but always ignored"))
- (format "gitea.%s.user" host))
- (gogs
- (when (zerop (call-process "git" nil nil nil "config" "gogs.host"))
- (message "WARNING: gogs.host is set but always ignored"))
- (format "gogs.%s.user" host)))))
- (condition-case nil
- (car (process-lines "git" "config" var))
- (error
- (let ((user (read-string
- (format "Git variable `%s' is unset. Set to: " var))))
- (if (equal user "")
- (user-error "The empty string is not a valid username")
- (call-process
- "git" nil nil nil "config"
- (if (eq (read-char-choice
- (format "Set %s=%s [g]lobally (recommended) or [l]ocally? "
- var user)
- (list ?g ?l))
- ?g)
- "--global"
- "--local")
- var user)
- user))))))
-
-(defun ghub--ident (username package)
- (format "%s^%s" username package))
-
-(defun ghub--auth-source-get (keys &rest spec)
- (declare (indent 1))
- (let ((plist (car (apply #'auth-source-search
- (append spec (list :max 1))))))
- (mapcar (lambda (k)
- (plist-get plist k))
- keys)))
-
-;;; _
-(provide 'ghub)
-(require 'ghub-graphql)
-;;; ghub.el ends here
+++ /dev/null
-This is ghub.info, produced by makeinfo version 6.8 from ghub.texi.
-
- Copyright (C) 2017-2024 Jonas Bernoulli
- <emacs.ghubNOSPAMjonas.bernoulli.dev>
-
- You can redistribute this document 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 document 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.
-
-INFO-DIR-SECTION Emacs
-START-INFO-DIR-ENTRY
-* Ghub: (ghub). Client library for the Github API.
-END-INFO-DIR-ENTRY
-
-\1f
-File: ghub.info, Node: Top, Next: Introduction, Up: (dir)
-
-Ghub User and Developer Manual
-******************************
-
-Ghub is an Emacs library that is used by various packages to access the
-APIs of various instances of various Git forge implementations.
-
-This manual is for Ghub version 4.2.0.
-
- Copyright (C) 2017-2024 Jonas Bernoulli
- <emacs.ghubNOSPAMjonas.bernoulli.dev>
-
- You can redistribute this document 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 document 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.
-
-* Menu:
-
-* Introduction::
-* Getting Started::
-* API::
-* Notes::
-* Function Index::
-* Variable Index::
-
-— The Detailed Node Listing —
-
-Getting Started
-
-* Basic Concepts, Arguments and Variables: Basic Concepts Arguments and Variables.
-* Setting the Username::
-* Creating and Storing a Token::
-* Github Configuration Variables::
-
-Creating and Storing a Token
-
-* Creating a Token::
-* Storing a Token::
-
-
-API
-
-* Their APIs::
-* Making REST Requests::
-* Making GraphQL Requests::
-* Github Convenience Wrappers::
-* Non-Github Convenience Wrappers::
-
-Notes
-
-* Using Ghub in Personal Scripts::
-* Using Ghub in Your Own Package::
-* Forge Limitations and Notes::
-
-
-\1f
-File: ghub.info, Node: Introduction, Next: Getting Started, Prev: Top, Up: Top
-
-1 Introduction
-**************
-
-Ghub is an Emacs library that is used by various packages to access the
-APIs of various instances of various Git forge implementations.
-
- A forge is a web-based collaborative software platform for developing
-and distributing computer applications. Examples include Github and
-Gitlab.
-
-\1f
-File: ghub.info, Node: Getting Started, Next: API, Prev: Introduction, Up: Top
-
-2 Getting Started
-*****************
-
-This manual guides you through the steps that are necessary to use the
-Forge package and/or to make a request using just Ghub itself, such as
-this:
-
- (ghub-request "GET" "/user")
-
- To be able to do that, Ghub needs to know who you want to talk to,
-who you are, and how you are going to prove the latter to the former.
-
- Additionally Ghub wants to know on behalf of which Emacs package it
-is making a request. So the question of "who is making the request" has
-to be rephrased as "which human (or bot) is using what (Emacs) package
-to make the request". If, for example, the human known as "tarsius" is
-using the ‘forge’ package, then that is represented in some places using
-the string "tarsius^forge".
-
-* Menu:
-
-* Basic Concepts, Arguments and Variables: Basic Concepts Arguments and Variables.
-* Setting the Username::
-* Creating and Storing a Token::
-* Github Configuration Variables::
-
-\1f
-File: ghub.info, Node: Basic Concepts Arguments and Variables, Next: Setting the Username, Up: Getting Started
-
-2.1 Basic Concepts, Arguments and Variables
-===========================================
-
-(If you are trying to configure Forge for <https://github.com> and/or
-<https://gitlab.com>, then you can skip this section.)
-
- Originally Ghub supported only Github but now it also supports
-Gitlab, Gitea, Gogs and Bitbucket. Because of that, the function
-‘ghub-request’ defaults to acting on a ‘github’ forge, but can be told
-to act on other forges using the FORGE argument.
-
- The FORGE argument only specifies what kind of forge we are
-accessing, not which host specifically. The HOST argument can be used
-to select the instance. For some forges a default instance is defined:
-
- • Forge ‘github’ defaults to host ‘api.github.com’.
- • Forge ‘gitlab’ defaults to host ‘gitlab.com/api/v4’.
- • Forge ‘bitbucket’ defaults to host ‘api.bitbucket.org/2.0’.
- • No canonical host exists for the ‘gitea’ and ‘gogs’ forges and
- ‘localhost:3000/api/v1’ is used as the default host in both cases.
-
- Together the FORGE and HOST arguments specify the forge type and
-instance. In addition to that, it is also necessary to specify on whose
-behalf the request is being made, which can be done using the USERNAME
-and AUTH arguments. For example:
-
- (ghub-request "GET" "/user" nil
- :forge 'github
- :host "api.github.com"
- :username "tarsius"
- :auth 'forge)
-
- Having to specify these arguments for every request is inconvenient.
-Additional variables and convenience functions can be used to make that
-unnecessary in most cases.
-
- (But for debugging purposes the above explicit form is very useful.
-You will obviously have to change the value of USERNAME and you should
-use ‘ghub’ as AUTH when first trying this at home.)
-
- These variables can be set globally and/or for a specific repository.
-
- • For "api.github.com" (aka the API of <https://github.com>) the Git
- variable ‘github.user’ specifies the user.
- • For another ‘github’ instance the Git variable ‘github.HOST.user’
- specifies the user. The HOST in that variable name is the same as
- the value of the HOST argument of the called function.
- • Instead of specifying the HOST in every function call, the Git
- variable ‘github.host’ can be used. This should only be set
- locally.
-
- These ‘github’ specific variables are discussed in more detail in
-*note Github Configuration Variables::.
-
-For ‘gitlab’ and ‘bitbucket’ forges similar variables are available:
-
- • ‘gitlab.user’ specifies the <https://gitlab.com> user.
- • ‘gitlab.HOST.user’ specifies the user for the HOST ‘gitlab’
- instance.
- • ‘gitlab.host’ specifies the ‘gitlab’ host, unless the HOST argument
- is non-nil
- • ‘bitbucket.user’ specifies the <https://bitbucket.org> user.
- • ‘bitbucket.HOST.user’ specifies the user for the HOST ‘bitbucket’
- instance.
- • ‘bitbucket.host’ specifies the ‘bitbucket’ host, unless the HOST
- argument is non-nil.
-
- For the ‘gitea’ and ‘gogs’ forges some similar variables are
-available, however for some of the ‘ghub.*’ variables no equivalent
-variable exist for these two forges:
-
- • ‘gitea.user’ is *not* used because no canonical ‘gitea’ instance
- exists.
- • ‘gitea.HOST.user’ specifies the user for the HOST ‘gitea’ instance.
- • ‘gitea.host’ specifies the ‘gitea’ host, unless the HOST argument
- is non-nil
- • ‘gogs.user’ is *not* used because no canonical ‘gogs’ instance
- exists.
- • ‘gogs.HOST.user’ specifies the user for the HOST ‘gogs’ instance.
- • ‘gogs.host’ specifies the ‘gogs’ host, unless the HOST argument is
- non-nil
-
-\1f
-File: ghub.info, Node: Setting the Username, Next: Creating and Storing a Token, Prev: Basic Concepts Arguments and Variables, Up: Getting Started
-
-2.2 Setting the Username
-========================
-
-Ghub needs to know your username, which you use on the host that you
-want it to connect to. For each host a different Git variable has to be
-set, to specify the username on that host. More than one variable is
-needed, because you might use different usernames on different hosts.
-
-Setting your Github.com Username
---------------------------------
-
-To inform Ghub about your "github.com" username do this:
-
- git config --global github.user USERNAME
-
- If you need to identify as another user in a particular repository,
-then you have to set that variable locally:
-
- cd /path/to/repo
- git config --local github.user USERNAME
-
-Setting your Gitlab.com Username
---------------------------------
-
-To inform Ghub about your "gitlab.com" username do this:
-
- git config --global gitlab.user USERNAME
-
- If you need to identify as another user in a particular repository,
-then you have to set that variable locally:
-
- cd /path/to/repo
- git config --local gitlab.user USERNAME
-
- Make sure you use the correct USERNAME for this forge/host. It might
-not be the same as on "github.com"!
-
-Setting your Github Enterprise Username
----------------------------------------
-
-For Github Enterprise instances you have to specify where the API can be
-accessed and a different variable has to be used to set the username.
-
- For example if the API is available at ‘https://example.com/api/v3’,
-then you should do this:
-
- git config --global github.example.com/api/v3.user USERNAME
-
- Make sure you use the correct USERNAME for this instance. It might
-not be the same as on "github.com"!
-
- Doing this only tells Ghub who you are on this host, additionally you
-have to tell Ghub which repositories are connected to that forge/host,
-like so:
-
- cd /path/to/repo
- git config --local github.host example.com/api/v3
-
-Setting your Username for Other Hosts and/or Forges
----------------------------------------------------
-
-To inform Ghub about your username on HOST (a FORGE instance) do this:
-
- git config --global FORGE.HOST.user USERNAME
-
- FORGE can be one of ‘bitbucket’, ‘gitea’ or ‘gogs’. It can also be
-‘github’ or ‘gitlab’; but if that is the case, then you should look at
-the preceding sections instead, which discuss these cases specifically.
-
- HOST identifies the instance. This actually points at the top-level
-endpoint of the API and may contain path components, e.g.:
-‘example.com/api’.
-
- If you need to identify as another user in a particular repository,
-then you have to set that variable locally:
-
- cd /path/to/repo
- git config --local FORGE.HOST.user USERNAME
-
-\1f
-File: ghub.info, Node: Creating and Storing a Token, Next: Github Configuration Variables, Prev: Setting the Username, Up: Getting Started
-
-2.3 Creating and Storing a Token
-================================
-
-* Menu:
-
-* Creating a Token::
-* Storing a Token::
-
-\1f
-File: ghub.info, Node: Creating a Token, Next: Storing a Token, Up: Creating and Storing a Token
-
-2.3.1 Creating a Token
-----------------------
-
-To create a token, use the web interface of the host you want to connect
-to. Here is a list of pages to do this for certain popular hosts:
-
- • <https://github.com/settings/tokens>
- • <https://gitlab.com/-/profile/personal_access_tokens>
-
- For other forges we cannot provide a functioning URL because they
-contain unknown values such as your name. Just go to the general
-settings page of the respective host and then go from there.
-
- Except on ‘gitea’ and ‘gogs’ each token can be limited to certain
-"scopes", i.e., it is possible to limit for which purposes any given
-token can be used.
-
- Before you create a token to be used for a certain package, you
-should consult the documentation of that package, which in turn should
-tell you which scopes are needed and why. The Forge package for example
-does so in *note (forge)Token Creation::.
-
-\1f
-File: ghub.info, Node: Storing a Token, Prev: Creating a Token, Up: Creating and Storing a Token
-
-2.3.2 Storing a Token
----------------------
-
-Please also see *note (auth)Top:: for all the gory details about
-Auth-Source.
-
- The variable ‘auth-sources’ controls how and where Auth-Source keeps
-its secrets. The default value is a list of three files:
-‘("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")’, but to avoid confusion
-you should make sure that only one of these files exists, and then you
-should also adjust the value of the variable to only ever use that file,
-for example:
-
- (setq auth-sources '("~/.authinfo"))
-
- In ‘~/.authinfo’ secrets are stored in plain text. If you don’t want
-that, then you should use the encrypted ‘~/.authinfo.gpg’ instead:
-
- (setq auth-sources '("~/.authinfo.gpg"))
-
- Auth-Source also supports storing secrets in various external
-key-chains. See *note (auth)Top:: for more information.
-
- The default Auth-Source backends only support storing three values
-per entry; the "machine", the "login" and the "password". Because Ghub
-uses separate tokens for each package, it has to squeeze four values
-into those three slots, and it does that by using "USERNAME^PACKAGE" as
-the "login".
-
- Assuming your *Github* username is "ziggy", the package is named
-"forge", and you want to access *Github.com* with the *token*
-"012345abcdef...", an entry in one of the three mentioned files would
-then look like this:
-
- machine api.github.com login ziggy^forge password 012345abcdef...
-
- Assuming your *Gitlab* username is "ziggy", the package is named
-"forge", and you want to access *Gitlab.com* with the *token*
-"012345abcdef...", an entry in one of the three mentioned files would
-then look like this:
-
- machine gitlab.com/api/v4 login ziggy^forge password 012345abcdef...
-
-\1f
-File: ghub.info, Node: Github Configuration Variables, Prev: Creating and Storing a Token, Up: Getting Started
-
-2.4 Github Configuration Variables
-==================================
-
-The username and, unless you only use Github.com itself, the Github
-Enterprise instance have to be configured using Git variables. In rare
-cases it might also be necessary to specify the identity of the local
-machine, which is done using a lisp variable.
-
- -- Variable: github.user
- The Github.com username. This should be set globally and if you
- have multiple Github.com user accounts, then you should set this
- locally only for those repositories that you want to access using
- the secondary identity.
-
- -- Variable: github.HOST.user
- This variable serves the same purpose as ‘github.user’ but for the
- Github Enterprise instance identified by ‘HOST’.
-
- The reason why separate variables are used is that this makes it
- possible to set both values globally instead of having to set one
- of the values locally in each and every repository that is
- connected to the Github Enterprise instance, not Github.com.
-
- -- Variable: github.host
- This variable should only be set locally for a repository and
- specifies the Github Enterprise edition that that repository is
- connected to. You should not set this globally because then each
- and every repository becomes connected to the specified Github
- Enterprise instance, including those that should actually be
- connected to Github.com.
-
- When this is undefined, then "api.github.com" is used (defined in
- the constant ‘ghub-default-host’, which you should never attempt to
- change.)
-
-\1f
-File: ghub.info, Node: API, Next: Notes, Prev: Getting Started, Up: Top
-
-3 API
-*****
-
-* Menu:
-
-* Their APIs::
-* Making REST Requests::
-* Making GraphQL Requests::
-* Github Convenience Wrappers::
-* Non-Github Convenience Wrappers::
-
-\1f
-File: ghub.info, Node: Their APIs, Next: Making REST Requests, Up: API
-
-3.1 Their APIs
-==============
-
-Of course this manual does not cover the APIs of all forges that it
-supports, but for your convenience, here are the links to their API
-manuals:
-
- • Github:
- • <https://developer.github.com/v4> (GraphQl)
- • <https://developer.github.com/v3> (REST)
- • Gitlab:
- • <https://docs.gitlab.com/ee/api/README.html>
- • Gitea:
- • <https://docs.gitea.io/en-us/api-usage>
- • <https://try.gitea.io/api/swagger>
- • Gogs:
- • <https://github.com/gogs/go-gogs-client/wiki>
- • Bitbucket:
- • <https://developer.atlassian.com/bitbucket/api/2/reference>
-
-\1f
-File: ghub.info, Node: Making REST Requests, Next: Making GraphQL Requests, Prev: Their APIs, Up: API
-
-3.2 Making REST Requests
-========================
-
- -- Function: ghub-request method resource &optional params &key query
- payload headers unpaginate noerror reader username auth host
- callback errorback url value error extra method*
- This function makes a request for ‘RESOURCE’ using ‘METHOD’.
- ‘PARAMS’, ‘QUERY’, ‘PAYLOAD’ and/or ‘HEADERS’ are alists holding
- additional request data. The response body is returned and the
- response headers are stored in the variable
- ‘ghub-response-headers’.
-
- • ‘METHOD’ is the HTTP method, given as a string.
- • ‘RESOURCE’ is the resource to access, given as a string
- beginning with a slash.
-
- • ‘PARAMS’, ‘QUERY’, ‘PAYLOAD’ and ‘HEADERS’ are alists and are
- used to specify request data. All these arguments are alists
- that resemble the JSON expected and returned by the Github
- API. The keys are symbols and the values stored in the ‘cdr’
- (not the ‘cadr’) can be strings, integers, or lists of strings
- and integers.
-
- The Github API documentation is vague on how data has to be
- transmitted and for a particular resource usually just talks
- about "parameters". Generally speaking when the ‘METHOD’ is
- "HEAD" or "GET", then they have to be transmitted as a query,
- otherwise as a payload.
-
- • Use ‘PARAMS’ to automatically transmit like ‘QUERY’ or
- ‘PAYLOAD’ would depending on ‘METHOD’.
- • Use ‘QUERY’ to explicitly transmit data as a query.
- • Use ‘PAYLOAD’ to explicitly transmit data as a payload.
- Instead of an alist, ‘PAYLOAD’ may also be a string, in
- which case it gets encoded as UTF-8 but is otherwise
- transmitted as-is.
- • Use ‘HEADERS’ for those rare resources that require that
- the data is transmitted as headers instead of as a query
- or payload. When that is the case, then the Github API
- documentation usually mentions it explicitly.
-
- • If ‘SILENT’ is non-nil, then progress reports and the like are
- not messaged.
-
- • If ‘UNPAGINATE’ is t, then this function makes as many
- requests as necessary to get all values. If ‘UNPAGINATE’ is a
- natural number, then it gets at most that many pages. For any
- other non-nil value it raises an error.
-
- • If ‘NOERROR’ is non-nil, then no error is raised if the
- request fails and ‘nil’ is returned instead. If ‘NOERROR’ is
- ‘return’, then the error payload is returned instead of ‘nil’.
-
- • If ‘READER’ is non-nil, then it is used to read and return
- from the response buffer. The default is
- ‘ghub--read-json-payload’. For the very few resources that do
- not return JSON, you might want to use ‘ghub--decode-payload’.
-
- • If ‘USERNAME’ is non-nil, then the request is made on behalf
- of that user. It is better to specify the user using the Git
- variable ‘github.user’ for "api.github.com", or
- ‘github.HOST.user’ if connecting to a Github Enterprise
- instance.
-
- • Each package that uses Ghub should use its own token. If
- ‘AUTH’ is ‘nil’ or unspecified, then the generic ‘ghub’ token
- is used instead. This is only acceptable for personal
- utilities. A package that is distributed to other users
- should always use this argument to identify itself, using a
- symbol matching its name.
-
- Package authors who find this inconvenient should write a
- wrapper around this function and possibly for the
- method-specific functions as well.
-
- Beside ‘nil’, some other symbols have a special meaning too.
- ‘none’ means to make an unauthorized request. ‘basic’ means
- to make a password based request. If the value is a string,
- then it is assumed to be a valid token. ‘basic’ and an
- explicit token string are only intended for internal and
- debugging uses.
-
- If ‘AUTH’ is a package symbol, then the scopes are specified
- using the variable ‘AUTH-github-token-scopes’. It is an error
- if that is not specified. See ‘ghub-github-token-scopes’ for
- an example.
-
- • If ‘HOST’ is non-nil, then connect to that Github instance.
- This defaults to "api.github.com". When a repository is
- connected to a Github Enterprise instance, then it is better
- to specify that using the Git variable ‘github.host’ instead
- of using this argument.
-
- • If ‘FORGE’ is ‘gitlab’, then connect to Gitlab.com or,
- depending on ‘HOST’, to another Gitlab instance. This is only
- intended for internal use. Instead of using this argument you
- should use function ‘glab-request’ and other ‘glab-*’
- functions.
-
- • If ‘CALLBACK’ and/or ‘ERRORBACK’ is non-nil, then this
- function makes one or more asynchronous requests and calls
- ‘CALLBACK’ or ‘ERRORBACK’ when finished. If no error
- occurred, then it calls ‘CALLBACK’, unless that is ‘nil’.
-
- If an error occurred, then it calls ‘ERRORBACK’, or if that is
- nil, then ‘CALLBACK’. ‘ERRORBACK’ can also be ‘t’, in which
- case it signals instead. ‘NOERROR’ is ignored for all
- asynchronous requests.
-
- Both callbacks are called with four arguments.
-
- 1. For ‘CALLBACK’, the combined value of the retrieved
- pages. For ‘ERRORBACK’, the error that occurred when
- retrieving the last page.
- 2. The headers of the last page as an alist.
- 3. Status information provided by ‘url-retrieve’. Its
- ‘:error’ property holds the same information as the first
- argument to ‘ERRORBACK’.
- 4. A ‘ghub--req’ struct, which can be passed to
- ‘ghub-continue’ (which see) to retrieve the next page, if
- any.
-
- -- Function: ghub-continue args
- If there is a next page, then this function retrieves that.
-
- This function is only intended to be called from callbacks. If
- there is a next page, then that is retrieved and the buffer that
- the result will be loaded into is returned, or t if the process has
- already completed. If there is no next page, then return nil.
-
- Callbacks are called with four arguments (see ‘ghub-request’). The
- forth argument is a ‘ghub--req’ struct, intended to be passed to
- this function. A callback may use the struct’s ‘extra’ slot to
- pass additional information to the callback that will be called
- after the next request. Use the function ‘ghub-req-extra’ to get
- and set the value of that slot.
-
- As an example, using ‘ghub-continue’ in a callback like so:
-
- (ghub-get "/users/tarsius/repos" nil
- :callback (lambda (value _headers _status req)
- (unless (ghub-continue req)
- (setq my-value value))))
-
- is equivalent to:
-
- (ghub-get "/users/tarsius/repos" nil
- :unpaginate t
- :callback (lambda (value _headers _status _req)
- (setq my-value value)))
-
- To demonstrate how to pass information from one callback to the
- next, here we record when we start fetching each page:
-
- (ghub-get "/users/tarsius/repos" nil
- :extra (list (current-time))
- :callback (lambda (value _headers _status req)
- (push (current-time) (ghub-req-extra req))
- (unless (ghub-continue req)
- (setq my-times (ghub-req-extra req))
- (setq my-value value))))
-
- -- Variable: ghub-response-headers
- A select few Github API resources respond by transmitting data in
- the response header instead of in the response body. Because there
- are so few of these inconsistencies, ‘ghub-request’ always returns
- the response body.
-
- To access the response headers use this variable after
- ‘ghub-request’ has returned.
-
- -- Function: ghub-response-link-relations req headers payload
- This function returns an alist of the link relations in ‘HEADERS’,
- or if optional ‘HEADERS’ is nil, then those in
- ‘ghub-response-headers’.
-
- When accessing a Bitbucket instance then the link relations are in
- ‘PAYLOAD’ instead of ‘HEADERS’, making their API merely RESTish and
- forcing this function to append those relations to the value of
- ‘ghub-response-headers’, for later use when this function is called
- with ‘nil’ for ‘PAYLOAD’.
-
-\1f
-File: ghub.info, Node: Making GraphQL Requests, Next: Github Convenience Wrappers, Prev: Making REST Requests, Up: API
-
-3.3 Making GraphQL Requests
-===========================
-
- -- Function: ghub-graphql graphql &optional variables &key username
- auth host callback silent callback errorback value extra
- This function makes a GraphQL request using ‘GRAPHQL’ and
- ‘VARIABLES’ as inputs. ‘GRAPHQL’ is a GraphQL string. ‘VARIABLES’
- is a JSON-like alist. The other arguments behave as for
- ‘ghub-request’ (which see).
-
- The response is returned as a JSON-like alist. Even if the
- response contains ‘errors’, this function does not raise an error.
- Cursor-handling is likewise left to the caller.
-
- ‘ghub-graphql’ is a thin convenience wrapper around ‘ghub-request’,
-similar to ‘ghub-post’ and friends. While the latter only hard-code the
-value of the ‘METHOD’ argument, the former also hard-codes ‘RESOURCE’
-and constructs ‘PAYLOAD’ from ‘GRAPHQL’ and ‘VARIABLES’. It also drops
-‘UNPAGINATE’, ‘NOERROR’, ‘READER’ (internal functions expect alist-ified
-JSON) and ‘FORGE’ (only Github currently supports GraphQL).
-
- ‘ghub-graphql’ does not account for the fact that pagination works
-differently in GraphQL than it does in REST, so users of this function
-have to deal with that themselves. Likewise error handling works
-differently and has to be done by the caller too.
-
- An early attempt at implementing automatic unpaginating for GraphQL
-can be found in the ‘faithful-graphql’ branch, provided I haven’t
-deleted that by now. On that branch I try to do things as intended by
-the designers of GraphQL, using variables and fragments, and drowning in
-a sea of boilerplate.
-
- The problem with that approach is that it only works for applications
-that fetch specific information on demand and actually want things to be
-paginated. I am convinced that GraphQL is very nice for web apps.
-
- However the Forge package for which I have implemented all of this
-has very different needs. It wants to fetch "all the data" and "cache"
-it locally, so that it is available even when there is no internet
-connection. GraphQL was designed around the idea that you should be
-able to "ask for what you need and get exactly that". But when that
-boils down to "look, if I persist, then you are going to hand me over
-all the data anyway, so just caught it up already", then things start to
-fall apart. If Github’s GraphQL allowed pagination to be turned off
-completely, then teaching ‘ghub-graphql’ about error handling would be
-enough.
-
- But it doesn’t and when doing things as intended, then that leads to
-huge amounts of repetitive boilerplate, which is so boring to write that
-doing it without introducing bugs left and right is near impossible; so
-I decided to give up on GraphQL variables, fragments and conditions, and
-instead implement something more powerful, though also more opinionated.
-
- -- Function: ghub--graphql-vacuum query variables callback &optional
- until &key narrow username auth host forge
- This function is an opinionated alternative to ‘ghub-graphql’. It
- relies on dark magic to get the job done.
-
- It makes an initial request using ‘QUERY’. It then looks for
- paginated edges in the returned data and makes more requests to
- resolve them. In order to do so it automatically transforms the
- initial ‘QUERY’ into another query suitable for that particular
- edge. The data retrieved by subsequent requests is then injected
- into the data of the original request before that is returned or
- passed to the callback. If subsequently retrieved data features
- new paginated edges, then those are followed recursively.
-
- The end result is essentially the same as using ‘ghub-graphql’, if
- only it were possible to say "do not paginate anything". The
- implementation is much more complicated because it is not possible
- to do that.
-
- ‘QUERY’ is a GraphQL query expressed as an s-expression. The
- bundled ‘gsexp’ library is used to turn that into a GraphQL query
- string. Only a subset of the GraphQL features are supported;
- fragments for example are not, and magical stuff happens to
- variables. This is not documented yet, I am afraid. Look at
- existing callers.
-
- ‘VARIABLES’ is a JSON-like alist as for ‘ghub-graphql’.
-
- ‘UNTIL’ is an alist ‘((EDGE-until . VALUE)...)’. When unpaginating
- ‘EDGE’ try not to fetch beyond the element whose first field has
- the value ‘VALUE’ and remove that element as well as all "lesser"
- elements from the retrieved data if necessary. Look at
- ‘forge--pull-repository’ for an example. This is only useful if
- you "cache" the response locally and want to avoid fetching data
- again that you already have.
-
- Other arguments behave as for ‘ghub-graphql’ and ‘ghub-request’,
- more or less. If ‘CALLBACK’ is nil, pretty-print the response.
-
- Using ‘ghub--graphql-vacuum’, the following resource specific
-functions are implemented. These functions are not part of the public
-API yet and are very much subject to change.
-
- -- Function: ghub-fetch-repository owner name callback &optional until
- &key username auth host forge
- This function asynchronously fetches forge data about the specified
- repository. Once all data has been collected, ‘CALLBACK’ is called
- with the data as the only argument.
-
- -- Function: ghub-fetch-issue owner name callback &optional until &key
- username auth host forge
- This function asynchronously fetches forge data about the specified
- issue. Once all data has been collected, ‘CALLBACK’ is called with
- the data as the only argument.
-
- -- Function: ghub-fetch-pullreq owner name callback &optional until
- &key username auth host forge
- This function asynchronously fetches forge data about the specified
- pull-request. Once all data has been collected, ‘CALLBACK’ is
- called with the data as the only argument.
-
- Note that in order to avoid duplication all of these functions base
-their initial query on the query stored in ‘ghub-fetch-repository’. The
-latter two pass that query through ‘ghub--graphql-prepare-query’, which
-then uses ‘ghub--graphql-narrow-query’ to remove parts the caller is not
-interested in. These two functions are also used internally, when
-unpaginating, but as demonstrated here they can be useful even before
-making an initial request.
-
-\1f
-File: ghub.info, Node: Github Convenience Wrappers, Next: Non-Github Convenience Wrappers, Prev: Making GraphQL Requests, Up: API
-
-3.4 Github Convenience Wrappers
-===============================
-
- -- Function: ghub-head resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: ghub-get resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- These functions are simple wrappers around ‘ghub-request’. Their
- signature is identical to that of the latter, except that they do
- not have an argument named ‘METHOD’. The HTTP method is instead
- given by the second word in the function name.
-
- As described in the documentation for ‘ghub-request’, it depends on
- the used method whether the value of the ‘PARAMS’ argument is used
- as the query or the payload. For the "HEAD" and "GET" methods it
- is used as the query.
-
- -- Function: ghub-put resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: ghub-post resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: ghub-patch resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: ghub-delete resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- These functions are simple wrappers around ‘ghub-request’. Their
- signature is identical to that of the latter, except that they do
- not have an argument named ‘METHOD’. The HTTP method is instead
- given by the second word in the function name.
-
- As described in the documentation for ‘ghub-request’, it depends on
- the used method whether the value of the ‘PARAMS’ argument is used
- as the query or the payload. For the "PUT", "POST", "PATCH" and
- "DELETE" methods it is used as the payload.
-
- -- Function: ghub-wait resource &optional duration &key username auth
- host
- Some API requests result in an immediate successful response even
- when the requested action has not actually been carried out yet.
- An example is the request for the creation of a new repository,
- which doesn’t cause the repository to immediately become available.
- The Github API documentation usually mentions this when describing
- an affected resource.
-
- If you want to do something with some resource right after making a
- request for its creation, then you might have to wait for it to
- actually be created. This function can be used to do so. It
- repeatedly tries to access the resource until it becomes available
- or until a timeout is reached. In the latter case it signals
- ‘ghub-error’.
-
- ‘RESOURCE’ specifies the resource that this function waits for.
-
- ‘DURATION’ specifies the maximum number of seconds to wait for,
- defaulting to 64 seconds. Emacs will block during that time, but
- the user can abort using ‘C-g’.
-
- The first attempt is made immediately and will often succeed. If
- not, then another attempt is made after two seconds, and each
- subsequent attempt is made after waiting as long as we already
- waited between all preceding attempts combined.
-
- See ‘ghub-request’’s documentation above for information about the
- other arguments.
-
-\1f
-File: ghub.info, Node: Non-Github Convenience Wrappers, Prev: Github Convenience Wrappers, Up: API
-
-3.5 Non-Github Convenience Wrappers
-===================================
-
-‘ghub-request’ and ‘ghub-METHOD’ can be used to make a request for any
-of the supported forge types, but except when making a request for a
-‘github’ instance, then that requires the use of the FORGE argument.
-
- To avoid that, functions named ‘FORGE-request’ and ‘FORGE-METHOD’ are
-also available. The following forms are equivalent, for example:
-
- (ghub-get ... :auth 'PACKAGE :forge 'gitlab)
- (glab-get ... :auth 'PACKAGE)
-
- These forms would remain equivalent even if you did not specify a
-value for the AUTH arguments — but you should not do that if you plan to
-share your code with others (see *note Using Ghub in Your Own
-Package::). If you do omit AUTH, then the request is made on behalf of
-the ‘ghub’ package, *regardless* of the symbol prefix of the function
-you use to do so.
-
- All ‘FORGE-request’ and ‘FORGE-METHOD’ functions, including but not
-limited to ‘ghub-METHOD’, are very simple wrappers around
-‘ghub-request’. They take fewer arguments than ‘ghub-request’ and
-instead pass constant values for the arguments METHOD and/or FORGE.
-
- -- Function: buck-request resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: glab-request resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gogs-request resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- Wrappers around ‘ghub-request’ which hardcode the FORGE to either
- ‘bitbucket’, ‘gitlab’, ‘gogs’ or ‘gitea’.
-
- -- Function: buck-get resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: buck-put resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: buck-post resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: buck-delete resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: glab-head resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: glab-get resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: glab-put resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: glab-post resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: glab-patch resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: glab-delete resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gogs-get resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gogs-put resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gogs-post resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gogs-patch resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gogs-delete resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gtea-get resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gtea-put resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gtea-post resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gtea-patch resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- -- Function: gtea-delete resource &optional params &key query payload
- headers unpaginate noerror reader username auth host callback
- errorback
- Wrappers around ‘FORGE-METHOD’ which hardcode the FORGE to either
- ‘bitbucket’, ‘gitlab’, ‘gogs’ or ‘gitea’, and the METHOD to the
- implied method.
-
- Note that ‘buck-head’, ‘buck-patch’, ‘gogs-head’ and ‘gtea-head’ do
- not exist because the respective APIs do not appear to support
- these methods.
-
-\1f
-File: ghub.info, Node: Notes, Next: Function Index, Prev: API, Up: Top
-
-4 Notes
-*******
-
-* Menu:
-
-* Using Ghub in Personal Scripts::
-* Using Ghub in Your Own Package::
-* Forge Limitations and Notes::
-
-\1f
-File: ghub.info, Node: Using Ghub in Personal Scripts, Next: Using Ghub in Your Own Package, Up: Notes
-
-4.1 Using Ghub in Personal Scripts
-==================================
-
-You can of course use ‘ghub-request’ and its wrapper functions in your
-personal scripts. Unlike when you use Ghub in a package that you
-distribute for others to use, you don’t have to explicitly specify a
-package in personal scripts.
-
- ;; This is perfectly acceptable in personal scripts ...
- (ghub-get "/user")
-
- ;; ... and actually equals to
- (ghub-get "/user" nil :auth 'ghub)
-
- ;; In packages you have to specify the package using AUTH.
- (ghub-get "/user" nil :auth 'some-package)
-
- When the ‘AUTH’ argument is not specified, then a request is made on
-behalf of the ‘ghub’ package itself. Like for any other package you
-have to create a dedicated token of course.
-
-\1f
-File: ghub.info, Node: Using Ghub in Your Own Package, Next: Forge Limitations and Notes, Prev: Using Ghub in Personal Scripts, Up: Notes
-
-4.2 Using Ghub in Your Own Package
-==================================
-
-Every package should use its own token. This allows you as the author
-of some package to only request access to API scopes that are actually
-needed, which in turn might make it easier for users to trust your
-package not to do unwanted things.
-
- You have to tell ‘ghub-request’ on behalf of which package a request
-is being made by passing the symbol ‘PACKAGE’ as the value of its ‘AUTH’
-argument.
-
- (ghub-request "GET" "/user" nil :auth 'PACKAGE)
-
- Keep in mind that the users of your package will have to manually
-create a suitable token. To make that easier, you should not only link
-to this manual but also prominently mention the scopes the token needs;
-and explain what they are needed for.
-
-\1f
-File: ghub.info, Node: Forge Limitations and Notes, Prev: Using Ghub in Your Own Package, Up: Notes
-
-4.3 Forge Limitations and Notes
-===============================
-
- • There are no default Gitea and Gogs instances so the variables
- ‘gitea.host’ and ‘gogs.host’ are not taken into account.
-
- • Gitea and Gogs do not support limiting a token to certain scopes.
-
- • The Bitbucket API is fairly broken and my willingness to deal with
- that is extremely limited unless someone pays me vast amounts of
- money.
-
- • The Gitlab API documentation is not always accurate, though I don’t
- have an example at hand. It also isn’t structured well, making it
- occasionally difficult to find the information one is looking for.
-
- • Where one would use ‘user/repo’ when accessing another forge, one
- has to use ‘user%2Frepo’ when accessing Gitlab, e.g.:
-
- (glab-get "/projects/python-mode-devs%2Fpython-mode")
-
-\1f
-File: ghub.info, Node: Function Index, Next: Variable Index, Prev: Notes, Up: Top
-
-Appendix A Function Index
-*************************
-
-\0\b[index\0\b]
-* Menu:
-
-* buck-delete: Non-Github Convenience Wrappers.
- (line 49)
-* buck-get: Non-Github Convenience Wrappers.
- (line 40)
-* buck-post: Non-Github Convenience Wrappers.
- (line 46)
-* buck-put: Non-Github Convenience Wrappers.
- (line 43)
-* buck-request: Non-Github Convenience Wrappers.
- (line 28)
-* ghub--graphql-vacuum: Making GraphQL Requests.
- (line 56)
-* ghub-continue: Making REST Requests.
- (line 125)
-* ghub-delete: Github Convenience Wrappers.
- (line 31)
-* ghub-fetch-issue: Making GraphQL Requests.
- (line 105)
-* ghub-fetch-pullreq: Making GraphQL Requests.
- (line 111)
-* ghub-fetch-repository: Making GraphQL Requests.
- (line 99)
-* ghub-get: Github Convenience Wrappers.
- (line 9)
-* ghub-graphql: Making GraphQL Requests.
- (line 6)
-* ghub-head: Github Convenience Wrappers.
- (line 6)
-* ghub-patch: Github Convenience Wrappers.
- (line 28)
-* ghub-post: Github Convenience Wrappers.
- (line 25)
-* ghub-put: Github Convenience Wrappers.
- (line 22)
-* ghub-request: Making REST Requests.
- (line 6)
-* ghub-response-link-relations: Making REST Requests.
- (line 174)
-* ghub-wait: Github Convenience Wrappers.
- (line 44)
-* glab-delete: Non-Github Convenience Wrappers.
- (line 67)
-* glab-get: Non-Github Convenience Wrappers.
- (line 55)
-* glab-head: Non-Github Convenience Wrappers.
- (line 52)
-* glab-patch: Non-Github Convenience Wrappers.
- (line 64)
-* glab-post: Non-Github Convenience Wrappers.
- (line 61)
-* glab-put: Non-Github Convenience Wrappers.
- (line 58)
-* glab-request: Non-Github Convenience Wrappers.
- (line 31)
-* gogs-delete: Non-Github Convenience Wrappers.
- (line 82)
-* gogs-get: Non-Github Convenience Wrappers.
- (line 70)
-* gogs-patch: Non-Github Convenience Wrappers.
- (line 79)
-* gogs-post: Non-Github Convenience Wrappers.
- (line 76)
-* gogs-put: Non-Github Convenience Wrappers.
- (line 73)
-* gogs-request: Non-Github Convenience Wrappers.
- (line 34)
-* gtea-delete: Non-Github Convenience Wrappers.
- (line 97)
-* gtea-get: Non-Github Convenience Wrappers.
- (line 85)
-* gtea-patch: Non-Github Convenience Wrappers.
- (line 94)
-* gtea-post: Non-Github Convenience Wrappers.
- (line 91)
-* gtea-put: Non-Github Convenience Wrappers.
- (line 88)
-
-\1f
-File: ghub.info, Node: Variable Index, Prev: Function Index, Up: Top
-
-Appendix B Variable Index
-*************************
-
-\0\b[index\0\b]
-* Menu:
-
-* ghub-response-headers: Making REST Requests.
- (line 165)
-* github.host: Github Configuration Variables.
- (line 26)
-* github.HOST.user: Github Configuration Variables.
- (line 17)
-* github.user: Github Configuration Variables.
- (line 11)
-
-
-\1f
-Tag Table:
-Node: Top\7f773
-Node: Introduction\7f2316
-Node: Getting Started\7f2721
-Node: Basic Concepts Arguments and Variables\7f3748
-Node: Setting the Username\7f7728
-Ref: Setting your Githubcom Username\7f8216
-Ref: Setting your Gitlabcom Username\7f8572
-Ref: Setting your Github Enterprise Username\7f9038
-Ref: Setting your Username for Other Hosts and/or Forges\7f9771
-Node: Creating and Storing a Token\7f10577
-Node: Creating a Token\7f10841
-Node: Storing a Token\7f11855
-Node: Github Configuration Variables\7f13701
-Node: API\7f15412
-Node: Their APIs\7f15650
-Node: Making REST Requests\7f16375
-Node: Making GraphQL Requests\7f25788
-Node: Github Convenience Wrappers\7f32471
-Node: Non-Github Convenience Wrappers\7f36175
-Node: Notes\7f41679
-Node: Using Ghub in Personal Scripts\7f41886
-Node: Using Ghub in Your Own Package\7f42777
-Node: Forge Limitations and Notes\7f43712
-Node: Function Index\7f44685
-Node: Variable Index\7f50334
-\1f
-End Tag Table
-
-\1f
-Local Variables:
-coding: utf-8
-End:
+++ /dev/null
-;;; glab.el --- Client library for the Gitlab API -*- lexical-binding:t -*-
-
-;; Copyright (C) 2016-2024 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-
-;; SPDX-License-Identifier: GPL-3.0-or-later
-
-;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Glab is a library that provides basic support for using the Gitlab API
-;; from Emacs packages. It abstracts access to API resources using only
-;; a handful of functions that are not resource-specific.
-
-;; This library is implemented on top of Ghub. Unlike Ghub, Glab does
-;; not support the guided creation of tokens because Gitlab lacks the
-;; features that would be necessary to implement that. Users have to
-;; create tokens through the web interface.
-
-;;; Code:
-
-(require 'ghub)
-
-(defconst glab-default-host "gitlab.com/api/v4"
- "The default host that is used if `glab.host' is not set.")
-
-(cl-defun glab-head (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `HEAD' request for RESOURCE, with optional query PARAMS.
-Like calling `ghub-request' (which see) with \"HEAD\" as METHOD
-and `gitlab' as FORGE."
- (ghub-request "HEAD" resource params :forge 'gitlab
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun glab-get (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `GET' request for RESOURCE, with optional query PARAMS.
-Like calling `ghub-request' (which see) with \"GET\" as METHOD
-and `gitlab' as FORGE."
- (ghub-request "GET" resource params :forge 'gitlab
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun glab-put (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PUT\" as METHOD
-and `gitlab' as FORGE."
- (ghub-request "PUT" resource params :forge 'gitlab
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun glab-post (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `POST' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"POST\" as METHOD
-and `gitlab' as FORGE."
- (ghub-request "POST" resource params :forge 'gitlab
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun glab-patch (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PATCH' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PATCH\" as METHOD
-and `gitlab' as FORGE."
- (ghub-request "PATCH" resource params :forge 'gitlab
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun glab-delete (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"DELETE\" as METHOD
-and `gitlab' as FORGE."
- (ghub-request "DELETE" resource params :forge 'gitlab
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun glab-request (method resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a request for RESOURCE and return the response body.
-Like calling `ghub-request' (which see) with `gitlab' as FORGE."
- (ghub-request method resource params :forge 'gitlab
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun glab-graphql (graphql &optional variables
- &key username auth host
- headers silent
- callback errorback value extra)
- "Make a GraphQL request using GRAPHQL and VARIABLES.
-Like calling `ghub-graphql' (which see) with `gitlab' as FORGE."
- (ghub-graphql graphql variables :forge 'gitlab
- :username username :auth auth :host host
- :headers headers :silent silent
- :callback callback :errorback errorback
- :value value :extra extra))
-
-(cl-defun glab-repository-id (owner name &key username auth host)
- "Return the id of the repository specified by OWNER, NAME and HOST."
- (number-to-string
- (cdr (assq 'id (glab-get (format "/projects/%s%%2F%s"
- (string-replace "/" "%2F" owner)
- name)
- nil :username username :auth auth :host host)))))
-
-;;; _
-(provide 'glab)
-;;; glab.el ends here
+++ /dev/null
-;;; gogs.el --- Client library for the Gogs API -*- lexical-binding:t -*-
-
-;; Copyright (C) 2016-2024 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-
-;; SPDX-License-Identifier: GPL-3.0-or-later
-
-;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Gogs is a library that provides basic support for using the Gogs API
-;; from Emacs packages. It abstracts access to API resources using only
-;; a handful of functions that are not resource-specific.
-
-;; This library is implemented on top of Ghub. Unlike Ghub, Gogs does
-;; not support the guided creation of tokens because Gogs lacks the
-;; features that would be necessary to implement that. Users have to
-;; create tokens through the web interface.
-
-;;; Code:
-
-(require 'ghub)
-
-(defconst gogs-default-host "localhost:3000/api/v1"
- "The default Gogs host.")
-
-;; HEAD does not appear to be supported.
-
-(cl-defun gogs-get (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `GET' request for RESOURCE, with optional query PARAMS.
-Like calling `ghub-request' (which see) with \"GET\" as METHOD
-and `gogs' as FORGE."
- (ghub-request "GET" resource params :forge 'gogs
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gogs-put (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PUT\" as METHOD
-and `gogs' as FORGE."
- (ghub-request "PUT" resource params :forge 'gogs
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gogs-post (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `POST' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"POST\" as METHOD
-and `gogs' as FORGE."
- (ghub-request "POST" resource params :forge 'gogs
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gogs-patch (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PATCH' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PATCH\" as METHOD
-and `gogs' as FORGE."
- (ghub-request "PATCH" resource params :forge 'gogs
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gogs-delete (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"DELETE\" as METHOD
-and `gogs' as FORGE."
- (ghub-request "DELETE" resource params :forge 'gogs
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gogs-request (method resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a request for RESOURCE and return the response body.
-Like calling `ghub-request' (which see) with `gogs' as FORGE."
- (ghub-request method resource params :forge 'gogs
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gogs-repository-id (owner name &key username auth host)
- "Return the id of the repository specified by OWNER, NAME and HOST."
- (number-to-string
- (cdr (assq 'id (gogs-get (format "/repos/%s/%s" owner name)
- nil :username username :auth auth :host host)))))
-
-;;; _
-(provide 'gogs)
-;;; gogs.el ends here
+++ /dev/null
-;;; gsexp.el --- GraphQl as S-expressions -*- lexical-binding:t -*-
-
-;; Copyright (C) 2016-2024 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-
-;; SPDX-License-Identifier: GPL-3.0-or-later
-
-;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; This library encodes S-expressions as GraphQl documents. Currently
-;; it is rather incomplete and only intended for use by `ghub-graphql'
-;; but I might fill the gaps and add documentation eventually. I am
-;; not in a hurry though.
-
-;;; Code:
-
-(require 'cl-lib)
-
-(defvar gsexp-one-variable-per-line nil)
-
-(defun gsexp-encode (document)
- (gsexp--pp (gsexp--encode-field document)))
-
-(defun gsexp--encode-field (field)
- (cond
- ((stringp field)
- field)
- ((symbolp field)
- (symbol-name field))
- ((listp (car field))
- (concat (format "%s: " (caar field))
- (gsexp--encode-field (cons (car (cdar field))
- (cdr field)))))
- ((concat
- (pcase (pop field)
- (`[] "")
- ((and (pred symbolp) op) (symbol-name op))
- (`[,op] (symbol-name op))
- (`[,op ,name] (format "%s %s" op name)))
- (and (vectorp (car field))
- (format " (%s%s)"
- (if gsexp-one-variable-per-line "\n" "")
- (mapconcat #'gsexp--encode-argument
- (append (pop field) nil)
- (if gsexp-one-variable-per-line ",\n" ","))))
- (and field
- (format " {\n%s\n}"
- (mapconcat #'gsexp--encode-field field "\n")))))))
-
-(cl-defun gsexp--encode-argument ((argument value))
- (format "%s: %s" argument (gsexp--encode-value value)))
-
-(defun gsexp--encode-value (value)
- (cond
- ((numberp value)
- (number-to-string value))
- ((symbolp value) ; including variables, enums, booleans and null
- (symbol-name value))
- ((stringp value)
- (prin1-to-string value))
- ((vectorp value)
- (format "(%s)" (mapconcat #'gsexp--encode-value value "")))
- ((listp value)
- (format "{%s}" (mapconcat
- (pcase-lambda (`(,name ,value))
- (format "%s: %s" name (gsexp--encode-value value)))
- value ", ")))
- ((error "Invalid field value: %S" value))))
-
-(defun gsexp--pp (string)
- (with-temp-buffer
- (save-excursion
- (insert string))
- (while (< (point) (point-max))
- (unless (and (bolp) (eolp))
- (save-excursion
- (let ((level (car (syntax-ppss (line-beginning-position)))))
- (when (looking-at "\\s-*\\s)")
- (cl-decf level))
- (indent-line-to (* 2 level)))))
- (forward-line 1))
- (buffer-string)))
-
-;;; _
-(provide 'gsexp)
-;;; gsexp.el ends here
+++ /dev/null
-;;; gtea.el --- Client library for the Gitea API -*- lexical-binding:t -*-
-
-;; Copyright (C) 2016-2024 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-
-;; SPDX-License-Identifier: GPL-3.0-or-later
-
-;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Gtea is a library that provides basic support for using the Gitea API
-;; from Emacs packages. It abstracts access to API resources using only
-;; a handful of functions that are not resource-specific.
-
-;; This library is implemented on top of Ghub. Unlike Ghub, Gtea does
-;; not support the guided creation of tokens because Gitea lacks the
-;; features that would be necessary to implement that. Users have to
-;; create tokens through the web interface.
-
-;;; Code:
-
-(require 'ghub)
-
-(defconst gtea-default-host "localhost:3000/api/v1"
- "The default Gitea host.")
-
-;; HEAD does not appear to be supported.
-
-(cl-defun gtea-get (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `GET' request for RESOURCE, with optional query PARAMS.
-Like calling `ghub-request' (which see) with \"GET\" as METHOD
-and `gitea' as FORGE."
- (ghub-request "GET" resource params :forge 'gitea
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gtea-put (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PUT\" as METHOD
-and `gitea' as FORGE."
- (ghub-request "PUT" resource params :forge 'gitea
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gtea-post (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `POST' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"POST\" as METHOD
-and `gitea' as FORGE."
- (ghub-request "POST" resource params :forge 'gitea
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gtea-patch (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `PATCH' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"PATCH\" as METHOD
-and `gitea' as FORGE."
- (ghub-request "PATCH" resource params :forge 'gitea
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gtea-delete (resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
-Like calling `ghub-request' (which see) with \"DELETE\" as METHOD
-and `gitea' as FORGE."
- (ghub-request "DELETE" resource params :forge 'gitea
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gtea-request (method resource &optional params
- &key query payload headers
- silent unpaginate noerror reader
- username auth host
- callback errorback extra)
- "Make a request for RESOURCE and return the response body.
-Like calling `ghub-request' (which see) with `gitea' as FORGE."
- (ghub-request method resource params :forge 'gitea
- :query query :payload payload :headers headers
- :silent silent :unpaginate unpaginate
- :noerror noerror :reader reader
- :username username :auth auth :host host
- :callback callback :errorback errorback :extra extra))
-
-(cl-defun gtea-repository-id (owner name &key username auth host)
- "Return the id of the repository specified by OWNER, NAME and HOST."
- (number-to-string
- (cdr (assq 'id (gtea-get (format "/repos/%s/%s" owner name)
- nil :username username :auth auth :host host)))))
-
-;;; _
-(provide 'gtea)
-;;; gtea.el ends here
--- /dev/null
+((nil
+ (indent-tabs-mode . nil))
+ (makefile-mode
+ (indent-tabs-mode . t))
+ (git-commit-mode
+ (git-commit-major-mode . git-commit-elisp-text-mode))
+ ("CHANGELOG"
+ (nil (fill-column . 70)
+ (mode . display-fill-column-indicator))))
--- /dev/null
+;;; buck.el --- Client library for the Bitbucket API -*- lexical-binding:t -*-
+
+;; Copyright (C) 2016-2025 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Buck is a library that provides basic support for using the Bitbucket API
+;; from Emacs packages. It abstracts access to API resources using only
+;; a handful of functions that are not resource-specific.
+
+;; This library is implemented on top of Ghub. Unlike Ghub, Buck does
+;; not support the guided creation of tokens because Bitbucket lacks the
+;; features that would be necessary to implement that. Users have to
+;; create tokens through the web interface.
+
+;;; Code:
+
+(require 'ghub)
+
+(defconst buck-default-host "api.bitbucket.org/2.0"
+ "The default host that is used if `buck.host' is not set.")
+
+;; HEAD and PATCH are not supported according to
+;; https://developer.atlassian.com/bitbucket/api/2/reference/meta/uri-uuid
+
+(cl-defun buck-get (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `GET' request for RESOURCE, with optional query PARAMS.
+Like calling `ghub-request' (which see) with \"GET\" as METHOD
+and `bitbucket' as FORGE."
+ (declare (obsolete ghub-get "4.3.1"))
+ (ghub-request "GET" resource params :forge 'bitbucket
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun buck-put (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PUT\" as METHOD
+and `bitbucket' as FORGE."
+ (declare (obsolete ghub-put "4.3.1"))
+ (ghub-request "PUT" resource params :forge 'bitbucket
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun buck-post (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `POST' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"POST\" as METHOD
+and `bitbucket' as FORGE."
+ (declare (obsolete ghub-post "4.3.1"))
+ (ghub-request "POST" resource params :forge 'bitbucket
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun buck-delete (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"DELETE\" as METHOD
+and `bitbucket' as FORGE."
+ (declare (obsolete ghub-delete "4.3.1"))
+ (ghub-request "DELETE" resource params :forge 'bitbucket
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun buck-request (method resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a request for RESOURCE and return the response body.
+Like calling `ghub-request' (which see) with `bitbucket' as FORGE."
+ (declare (obsolete ghub-request "4.3.1"))
+ (ghub-request method resource params :forge 'bitbucket
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun buck-repository-id (owner name &key username auth host)
+ "Return the id of the repository specified by OWNER, NAME and HOST."
+ (substring (cdr (assq 'uuid
+ (ghub-get (format "/repositories/%s/%s" owner name)
+ nil :forge 'bitbucket
+ :username username :auth auth :host host)))
+ 1 -1))
+
+;;; _
+(provide 'buck)
+;;; buck.el ends here
--- /dev/null
+This is the file .../info/dir, which contains the
+topmost node of the Info hierarchy, called (dir)Top.
+The first time you invoke Info you start off looking at this node.
+\1f
+File: dir, Node: Top This is the top of the INFO tree
+
+ This (the Directory node) gives a menu of major topics.
+ Typing "q" exits, "H" lists all Info commands, "d" returns here,
+ "h" gives a primer for first-timers,
+ "mEmacs<Return>" visits the Emacs manual, etc.
+
+ In Emacs, you can click mouse button 2 on a menu item or cross reference
+ to select it.
+
+* Menu:
+
+Emacs
+* Ghub: (ghub). Client library for the Github API.
--- /dev/null
+;;; ghub-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from buck.el
+
+(register-definition-prefixes "buck" '("buck-default-host"))
+
+\f
+;;; Generated autoloads from ghub.el
+
+(autoload 'ghub-clear-caches "ghub" "\
+Clear all caches that might negatively affect Ghub.
+
+If a library that is used by Ghub caches incorrect information
+such as a mistyped password, then that can prevent Ghub from
+asking the user for the correct information again.
+
+Set `url-http-real-basic-auth-storage' to nil
+and call `auth-source-forget+'." t)
+(register-definition-prefixes "ghub" '("ghub-"))
+
+\f
+;;; Generated autoloads from ghub-graphql.el
+
+(register-definition-prefixes "ghub-graphql" '("ghub-"))
+
+\f
+;;; Generated autoloads from glab.el
+
+(register-definition-prefixes "glab" '("glab-default-host"))
+
+\f
+;;; Generated autoloads from gogs.el
+
+(register-definition-prefixes "gogs" '("gogs-default-host"))
+
+\f
+;;; Generated autoloads from gsexp.el
+
+(register-definition-prefixes "gsexp" '("gsexp-"))
+
+\f
+;;; Generated autoloads from gtea.el
+
+(register-definition-prefixes "gtea" '("gtea-default-host"))
+\f
+;;; End of scraped data
+
+(provide 'ghub-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; ghub-autoloads.el ends here
--- /dev/null
+;;; ghub-graphql.el --- Access Github API using GraphQL -*- lexical-binding:t -*-
+
+;; Copyright (C) 2016-2025 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library implements GraphQL queries for Github.
+
+;;; Code:
+
+(require 'ghub)
+(require 'gsexp)
+(require 'treepy)
+
+(eval-when-compile (require 'subr-x))
+
+(define-error 'ghub-graphql-error "GraphQL Error" 'ghub-error)
+
+;;; Settings
+
+(defvar ghub-graphql-message-progress nil
+ "Whether to show \"Fetching page N...\" in echo area during requests.
+By default this information is only shown in the mode-line of the buffer
+from which the request was initiated, and if you kill that buffer, then
+nowhere. That may make it desirable to display the same message in the
+echo area as well.")
+
+(defvar ghub-graphql-items-per-request 50
+ "Number of GraphQL items to query for entities that return a collection.
+
+Adjust this value if you're hitting query timeouts against larger
+repositories.")
+
+;;; Mutations
+
+(cl-defun ghub-graphql (graphql
+ &optional variables
+ &key username auth host forge
+ headers silent
+ callback errorback value extra)
+ "Make a GraphQL request using GRAPHQL and VARIABLES.
+Return the response as a JSON-like alist. Even if the response
+contains `errors', do not raise an error. GRAPHQL is a GraphQL
+string. VARIABLES is a JSON-like alist. The other arguments
+behave as for `ghub-request' (which see)."
+ (cl-assert (not (stringp variables)))
+ (cl-assert (or (stringp graphql)
+ (memq (car-safe graphql) '(query mutation))))
+ (unless (stringp graphql)
+ (setq graphql (gsexp-encode (ghub--graphql-prepare-query graphql))))
+ (ghub-request "POST"
+ (if (eq forge 'gitlab) "/api/graphql" "/graphql")
+ nil
+ :payload `((query . ,graphql)
+ ,@(and variables `((variables ,@variables))))
+ :headers headers :silent silent
+ :username username :auth auth :host host :forge forge
+ :callback callback :errorback errorback
+ :extra extra :value value))
+
+(cl-defun ghub--graphql (graphql
+ &optional variables
+ &key username auth host forge
+ headers
+ callback errorback)
+ "An experimental and unfinished replacement for `ghub-graphql'."
+ (let ((ghub-graphql-message-progress nil))
+ (ghub--graphql-vacuum graphql variables callback nil
+ :username username
+ :auth auth
+ :host host
+ :forge forge
+ :headers headers
+ :errorback errorback)))
+
+;;; Queries
+
+(cl-defun ghub-graphql-rate-limit (&key username auth host)
+ "Return rate limit information."
+ (let-alist (ghub-graphql
+ '(query (rateLimit limit cost remaining resetAt))
+ nil :username username :auth auth :host host)
+ .data.rateLimit))
+
+(defconst ghub-fetch-repository-sparse
+ '(query
+ (repository
+ [(owner $owner String!)
+ (name $name String!)]
+ name
+ id
+ createdAt
+ updatedAt
+ nameWithOwner
+ (parent nameWithOwner)
+ description
+ homepageUrl
+ (defaultBranchRef name)
+ isArchived
+ isFork
+ isLocked
+ isMirror
+ isPrivate
+ hasDiscussionsEnabled
+ hasIssuesEnabled
+ hasWikiEnabled
+ (licenseInfo name)
+ (stargazers totalCount)
+ (watchers totalCount))))
+
+(defconst ghub-fetch-repository
+ `(query
+ (repository
+ ,@(cdr (cadr ghub-fetch-repository-sparse))
+ (assignableUsers [(:edges t)]
+ id
+ login
+ name)
+ (discussionCategories [(:edges t)]
+ id
+ name
+ emoji
+ isAnswerable
+ description)
+ (discussions [(:edges t)
+ (:singular discussion number)
+ (orderBy ((field UPDATED_AT) (direction DESC)))]
+ id
+ databaseId
+ number
+ url
+ stateReason
+ ;; Discussions lack isReadByViewer.
+ (answer id)
+ (author login)
+ title
+ createdAt
+ updatedAt
+ closedAt
+ locked
+ (category id)
+ body
+ (comments [(:edges t)]
+ id
+ databaseId
+ (author login)
+ createdAt
+ updatedAt
+ body
+ (replies [(:edges 20)]
+ id
+ databaseId
+ (author login)
+ createdAt
+ updatedAt
+ body))
+ (labels [(:edges t)]
+ id))
+ (issues [(:edges t)
+ (:singular issue number)
+ (orderBy ((field UPDATED_AT) (direction DESC)))]
+ number
+ id
+ state
+ stateReason
+ isReadByViewer
+ (author login)
+ title
+ createdAt
+ updatedAt
+ closedAt
+ locked
+ (milestone id)
+ body
+ (assignees [(:edges t)]
+ id)
+ (comments [(:edges t)]
+ id
+ databaseId
+ (author login)
+ createdAt
+ updatedAt
+ body)
+ (labels [(:edges t)]
+ id))
+ (labels [(:edges t)
+ (:singular label id)]
+ id
+ name
+ color
+ description)
+ (milestones [(:edges t)
+ (:singular milestone id)]
+ id
+ number
+ title
+ createdAt
+ updatedAt
+ dueOn
+ closedAt
+ description)
+ (pullRequests [(:edges t)
+ (:singular pullRequest number)
+ (orderBy ((field UPDATED_AT) (direction DESC)))]
+ number
+ id
+ state
+ isReadByViewer
+ (author login)
+ title
+ createdAt
+ updatedAt
+ closedAt
+ mergedAt
+ isDraft
+ locked
+ maintainerCanModify
+ isCrossRepository
+ (milestone id)
+ body
+ (baseRef name
+ (repository nameWithOwner))
+ baseRefOid
+ (headRef name
+ (repository (owner login)
+ nameWithOwner))
+ headRefOid
+ (assignees [(:edges t)]
+ id)
+ (reviewRequests [(:edges t)]
+ (requestedReviewer "... on User { id }\n"))
+ (comments [(:edges t)]
+ id
+ databaseId
+ (author login)
+ createdAt
+ updatedAt
+ body)
+ (labels [(:edges t)]
+ id)))))
+
+(defconst ghub-fetch-repository-review-threads
+ '(query
+ (repository
+ [(owner $owner String!)
+ (name $name String!)]
+ (pullRequests [(:edges t)
+ (:singular pullRequest number)
+ (orderBy ((field UPDATED_AT) (direction DESC)))]
+ number
+ baseRefOid
+ headRefOid
+ (reviewThreads [(:edges t)]
+ id
+ line
+ originalLine
+ diffSide
+ (resolvedBy login)
+ (comments [(:edges t)]
+ id
+ databaseId
+ (author login)
+ createdAt
+ updatedAt
+ body
+ (replyTo databaseId)
+ (originalCommit oid)
+ path))))))
+
+(cl-defun ghub-fetch-repository ( owner name callback
+ &optional until
+ &key username auth host forge
+ headers paginate errorback sparse)
+ "Asynchronously fetch forge data about the specified repository.
+Once all data has been collected, CALLBACK is called with the
+data as the only argument."
+ (ghub--graphql-vacuum (if sparse
+ ghub-fetch-repository-sparse
+ ghub-fetch-repository)
+ `((owner . ,owner)
+ (name . ,name))
+ callback until
+ :narrow '(repository)
+ :username username
+ :auth auth
+ :host host
+ :forge forge
+ :headers headers
+ :paginate paginate
+ :errorback errorback))
+
+(cl-defun ghub-fetch-discussion ( owner name number callback
+ &optional until
+ &key username auth host forge
+ headers errorback)
+ "Asynchronously fetch forge data about the specified discussion.
+Once all data has been collected, CALLBACK is called with the
+data as the only argument."
+ (ghub--graphql-vacuum (ghub--graphql-prepare-query
+ ghub-fetch-repository
+ `(repository discussions (discussion . ,number)))
+ `((owner . ,owner)
+ (name . ,name))
+ callback until
+ :narrow '(repository discussion)
+ :username username
+ :auth auth
+ :host host
+ :forge forge
+ :headers headers
+ :errorback errorback))
+
+(cl-defun ghub-fetch-issue ( owner name number callback
+ &optional until
+ &key username auth host forge
+ headers paginate errorback)
+ "Asynchronously fetch forge data about the specified issue.
+Once all data has been collected, CALLBACK is called with the
+data as the only argument."
+ (ghub--graphql-vacuum (ghub--graphql-narrow-query
+ ghub-fetch-repository
+ `(repository issues (issue . ,number)))
+ `((owner . ,owner)
+ (name . ,name))
+ callback until
+ :narrow '(repository issue)
+ :username username
+ :auth auth
+ :host host
+ :forge forge
+ :headers headers
+ :paginate paginate
+ :errorback errorback))
+
+(cl-defun ghub-fetch-pullreq ( owner name number callback
+ &optional until
+ &key username auth host forge
+ headers paginate errorback)
+ "Asynchronously fetch forge data about the specified pull-request.
+Once all data has been collected, CALLBACK is called with the
+data as the only argument."
+ (ghub--graphql-vacuum (ghub--graphql-narrow-query
+ ghub-fetch-repository
+ `(repository pullRequests (pullRequest . ,number)))
+ `((owner . ,owner)
+ (name . ,name))
+ callback until
+ :narrow '(repository pullRequest)
+ :username username
+ :auth auth
+ :host host
+ :forge forge
+ :headers headers
+ :paginate paginate
+ :errorback errorback))
+
+(cl-defun ghub-fetch-review-threads ( owner name number callback
+ &optional until
+ &key username auth host forge
+ headers paginate errorback)
+ "Asynchronously fetch forge data about the review threads from a pull-request.
+Once all data has been collected, CALLBACK is called with the
+data as the only argument."
+ (ghub--graphql-vacuum (ghub--graphql-narrow-query
+ ghub-fetch-repository-review-threads
+ `(repository pullRequests (pullRequest . ,number)))
+ `((owner . ,owner)
+ (name . ,name))
+ callback until
+ :narrow '(repository pullRequest)
+ :username username
+ :auth auth
+ :host host
+ :forge forge
+ :headers headers
+ :paginate paginate
+ :errorback errorback))
+
+;;; Internal
+
+(cl-defstruct (ghub--graphql-req
+ (:include ghub--req)
+ (:constructor ghub--make-graphql-req)
+ (:copier nil))
+ (query nil :read-only t)
+ (query-str nil :read-only nil)
+ (variables nil :read-only t)
+ (until nil :read-only t)
+ (pages 0 :read-only nil)
+ (paginate nil :read-only nil)
+ (narrow nil :read-only t))
+
+(cl-defun ghub--graphql-vacuum ( query variables callback
+ &optional until
+ &key narrow username auth host forge
+ headers paginate errorback)
+ "Make a GraphQL request using QUERY and VARIABLES.
+See Info node `(ghub)GraphQL Support'."
+ (unless host
+ (setq host (ghub--host forge)))
+ (unless (or username (stringp auth) (eq auth 'none))
+ (setq username (ghub--username host forge)))
+ (ghub--graphql-retrieve
+ (ghub--make-graphql-req
+ :url (url-generic-parse-url
+ (format "https://%s/graphql"
+ (if (string-suffix-p "/v3" host)
+ (substring host 0 -3)
+ host)))
+ :method "POST"
+ :headers (ghub--headers headers host auth username forge)
+ :handler #'ghub--graphql-handle-response
+ :query query
+ :variables variables
+ :until until
+ :buffer (current-buffer)
+ :narrow narrow
+ :paginate (or paginate
+ (and-let* ((p (and (eq auth 'forge)
+ (fboundp 'magit-get)
+ (magit-get "forge.graphqlItemLimit"))))
+ (string-to-number p)))
+ :callback (and (not (eq callback 'synchronous)) callback)
+ :errorback (and (not (eq callback 'synchronous)) errorback))))
+
+(defvar ghub--graphql-synchronous-value nil)
+
+(cl-defun ghub--graphql-synchronous ( query variables
+ &optional until
+ &key narrow username auth host forge
+ headers paginate)
+ "Make a synchronous GraphQL request using QUERY and VARIABLES.
+See Info node `(ghub)GraphQL Support'."
+ (unwind-protect
+ (progn (ghub--graphql-vacuum query variables 'synchronous until
+ :narrow narrow :username username
+ :auth auth :host host :forge forge
+ :headers headers :paginate paginate)
+ ghub--graphql-synchronous-value)
+ (setq ghub--graphql-synchronous-value nil)))
+
+(cl-defun ghub--graphql-retrieve (req &optional lineage cursor)
+ (let ((p (cl-incf (ghub--graphql-req-pages req))))
+ (when ghub-graphql-message-progress
+ (let ((message-log-max nil))
+ (message "Fetching page %s..." p)))
+ (when (> p 1)
+ (ghub--graphql-set-mode-line req "Fetching page %s" p)))
+ (setf (ghub--graphql-req-query-str req)
+ (gsexp-encode
+ (ghub--graphql-prepare-query
+ (ghub--graphql-req-query req)
+ lineage cursor)))
+ (when ghub-debug
+ (with-current-buffer (get-buffer-create " *gsexp-encode*")
+ (erase-buffer)
+ (insert (ghub--graphql-req-query-str req) "\n\n")
+ (let ((pos (point)))
+ (insert (ghub--encode-payload (ghub--graphql-req-variables req)) "\n")
+ (ignore-errors (json-pretty-print pos (point))))))
+ (ghub--retrieve
+ (let ((json-false nil))
+ (ghub--encode-payload
+ `((query . ,(ghub--graphql-req-query-str req))
+ (variables . ,(ghub--graphql-req-variables req)))))
+ req))
+
+(defun ghub--graphql-prepare-query (query &optional lineage cursor paginate)
+ (when lineage
+ (setq query (ghub--graphql-narrow-query query lineage cursor)))
+ (let ((loc (ghub--alist-zip query))
+ variables)
+ (cl-block nil
+ (while t
+ (let ((node (treepy-node loc)))
+ (when (and (vectorp node)
+ (listp (aref node 0)))
+ (let ((alist (cl-coerce node 'list))
+ vars)
+ (when-let ((edges (cadr (assq :edges alist))))
+ (push (list 'first
+ (apply
+ #'min
+ (delq nil (list (and (numberp edges) edges)
+ paginate
+ ghub-graphql-items-per-request))))
+ vars)
+ (setq loc (treepy-up loc))
+ (setq node (treepy-node loc))
+ (setq loc (treepy-replace
+ loc `(,(car node)
+ ,(cadr node)
+ (pageInfo endCursor hasNextPage)
+ (edges (node ,@(cddr node))))))
+ (setq loc (treepy-down loc))
+ (setq loc (treepy-next loc)))
+ (dolist (elt alist)
+ (cond ((keywordp (car elt)))
+ ((length= elt 3)
+ (push (list (nth 0 elt) (nth 1 elt)) vars)
+ (push (list (nth 1 elt) (nth 2 elt)) variables))
+ ((length= elt 2)
+ (push elt vars))))
+ (setq loc (treepy-replace loc (vconcat (nreverse vars)))))))
+ (if (treepy-end-p loc)
+ (let ((node (copy-sequence (treepy-node loc))))
+ (when variables
+ (push (cl-coerce variables 'vector)
+ (cdr node)))
+ (cl-return node))
+ (setq loc (treepy-next loc)))))))
+
+(defun ghub--graphql-handle-response (status req)
+ (let ((buf (current-buffer)))
+ (unwind-protect
+ (progn
+ (set-buffer-multibyte t)
+ (let* ((headers (ghub--handle-response-headers status req))
+ (payload (ghub--handle-response-payload req))
+ (payload (ghub--handle-response-error status payload req))
+ (err (plist-get status :error))
+ (errors (cdr (assq 'errors payload)))
+ (errors (and errors (cons 'ghub-graphql-error errors))))
+ (cond ((or err errors)
+ (when (and (not err) ghub-debug)
+ (ignore-errors (json-pretty-print (point) (point-max)))
+ (pop-to-buffer buf))
+ (ghub--graphql-handle-failure
+ req (or err errors) headers status))
+ ((ghub--graphql-walk-response req (assq 'data payload))))))
+ (when (and (buffer-live-p buf)
+ (not (buffer-local-value 'ghub-debug buf)))
+ (kill-buffer buf)))))
+
+(defun ghub--graphql-handle-failure (req errors headers status)
+ (if-let ((errorback (ghub--req-errorback req)))
+ (let ((buffer (ghub--req-buffer req)))
+ (with-current-buffer
+ (if (buffer-live-p buffer) buffer (current-buffer))
+ (funcall errorback errors headers status req)))
+ (ghub--signal-error errors)))
+
+(defun ghub--graphql-handle-success (req data)
+ (let ((callback (ghub--req-callback req))
+ (buffer (ghub--req-buffer req))
+ (narrow (ghub--graphql-req-narrow req)))
+ (while-let ((key (pop narrow)))
+ (setq data (cdr (assq key data))))
+ (with-current-buffer
+ (if (buffer-live-p buffer) buffer (current-buffer))
+ (funcall (or callback #'ghub--graphql-pp-response)
+ data))))
+
+(defun ghub--graphql-walk-response (req data)
+ (let* ((loc (ghub--req-value req))
+ (loc (if (not loc)
+ (ghub--alist-zip data)
+ (setq data (ghub--graphql-narrow-data
+ data (ghub--graphql-lineage loc)))
+ (setf (alist-get 'edges data)
+ (append (alist-get 'edges (treepy-node loc))
+ (or (alist-get 'edges data)
+ (error "BUG: Expected new nodes"))))
+ (treepy-replace loc data))))
+ (cl-block nil
+ (while t
+ (when (eq (car-safe (treepy-node loc)) 'edges)
+ (setq loc (treepy-up loc))
+ (pcase-let ((`(,key . ,val) (treepy-node loc)))
+ (let-alist val
+ (let* ((cursor (and .pageInfo.hasNextPage
+ .pageInfo.endCursor))
+ (until (cdr (assq (intern (format "%s-until" key))
+ (ghub--graphql-req-until req))))
+ (nodes (mapcar #'cdar .edges))
+ (nodes (if until
+ (seq-take-while
+ (lambda (node)
+ (or (string> (cdr (assq 'updatedAt node))
+ until)
+ (setq cursor nil)))
+ nodes)
+ nodes)))
+ (cond (cursor
+ (setf (ghub--req-value req) loc)
+ (ghub--graphql-retrieve req
+ (ghub--graphql-lineage loc)
+ cursor)
+ (cl-return))
+ ((setq loc (treepy-replace loc (cons key nodes)))))))))
+ (cond ((not (treepy-end-p loc))
+ (setq loc (treepy-next loc)))
+ ((ghub--req-callback req)
+ (ghub--graphql-handle-success req (treepy-root loc))
+ (ghub--graphql-set-mode-line req nil)
+ (cl-return))
+ (t
+ (setq ghub--graphql-synchronous-value (treepy-root loc))
+ (cl-return)))))))
+
+(defun ghub--graphql-lineage (loc)
+ (let (lineage)
+ (while (treepy-up loc)
+ (push (car (treepy-node loc)) lineage)
+ (setq loc (treepy-up loc)))
+ lineage))
+
+(defun ghub--graphql-narrow-data (data lineage)
+ (while-let ((key (pop lineage)))
+ (if (consp (car lineage))
+ (progn (pop lineage)
+ (setf data (cadr data)))
+ (setq data (assq key (cdr data)))))
+ data)
+
+(defun ghub--graphql-narrow-query (query lineage &optional cursor)
+ (if (consp (car lineage))
+ (let* ((child (cddr query))
+ (alist (cl-coerce (cadr query) 'list))
+ (single (cdr (assq :singular alist))))
+ `(,(car single)
+ ,(vector (list (cadr single) (cdr (car lineage))))
+ ,@(if (cdr lineage)
+ (ghub--graphql-narrow-query child (cdr lineage) cursor)
+ child)))
+ (let* ((child (or (assq (car lineage) (cdr query))
+ ;; Alias
+ (cl-find-if (lambda (c)
+ (eq (car-safe (car-safe c))
+ (car lineage)))
+ query)
+ ;; Edges
+ (cl-find-if (lambda (c)
+ (and (listp c)
+ (vectorp (cadr c))
+ (eq (cadr (assq :singular
+ (cl-coerce (cadr c)
+ 'list)))
+ (car lineage))))
+ (cdr query))
+ (error "BUG: Failed to narrow query")))
+ (object (car query))
+ (args (and (vectorp (cadr query))
+ (cadr query))))
+ `(,object
+ ,@(and args (list args))
+ ,(cond ((cdr lineage)
+ (ghub--graphql-narrow-query child (cdr lineage) cursor))
+ (cursor
+ `(,(car child)
+ ,(vconcat `((after ,cursor))
+ (cadr child))
+ ,@(cddr child)))
+ (t
+ child))))))
+
+(defun ghub--alist-zip (root)
+ (let ((branchp (##and (listp %) (listp (cdr %))))
+ (make-node (lambda (_ children) children)))
+ (treepy-zipper branchp #'identity make-node root)))
+
+(defun ghub--graphql-set-mode-line (req string &rest args)
+ (let ((buffer (ghub--graphql-req-buffer req)))
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (setq mode-line-process
+ (and string (concat " " (apply #'format string args))))
+ (force-mode-line-update t)))))
+
+(defun ghub--graphql-pp-response (data)
+ (pp-display-expression data "*Pp Eval Output*"))
+
+(cl-defun ghub--repository-id (owner name &key username auth host)
+ "Return the id of the repository specified by OWNER, NAME and HOST."
+ (let-alist (ghub-graphql
+ '(query (repository [(owner $owner String!)
+ (name $name String!)]
+ id))
+ `((owner . ,owner)
+ (name . ,name))
+ :username username :auth auth :host host)
+ .data.repository.id))
+
+;;; _
+(provide 'ghub-graphql)
+;;; ghub-graphql.el ends here
--- /dev/null
+;; -*- no-byte-compile: t; lexical-binding: nil -*-
+(define-package "ghub" "20250509.1440"
+ "Client libraries for Git forge APIs."
+ '((emacs "29.1")
+ (compat "30.1.0.0")
+ (let-alist "1.0.6")
+ (llama "0.6.2")
+ (treepy "0.1.2"))
+ :url "https://github.com/magit/ghub"
+ :commit "6bb612e7b7eada22569d84cc529f4ca36e08032d"
+ :revdesc "6bb612e7b7ea"
+ :keywords '("tools")
+ :authors '(("Jonas Bernoulli" . "emacs.ghubNOSPAMjonas.bernoulli.dev"))
+ :maintainers '(("Jonas Bernoulli" . "emacs.ghubNOSPAMjonas.bernoulli.dev")))
--- /dev/null
+;;; ghub.el --- Client libraries for Git forge APIs -*- lexical-binding:t -*-
+
+;; Copyright (C) 2016-2025 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; Package-Version: 20250509.1440
+;; Package-Revision: 6bb612e7b7ea
+;; Package-Requires: (
+;; (emacs "29.1")
+;; (compat "30.1.0.0")
+;; (let-alist "1.0.6")
+;; (llama "0.6.2")
+;; (treepy "0.1.2"))
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Ghub provides basic support for using the APIs of various Git forges
+;; from Emacs packages. Originally it only supported the Github REST
+;; API, but now it also supports the Github GraphQL API as well as the
+;; REST APIs of Gitlab, Gitea, Gogs and Bitbucket.
+
+;; Ghub abstracts access to API resources using only a handful of basic
+;; functions such as `ghub-get'. These are convenience wrappers around
+;; `ghub-request'. Additional forge-specific wrappers like `glab-put',
+;; `gtea-put', `gogs-post' and `buck-delete' are also available. Ghub
+;; does not provide any resource-specific functions, with the exception
+;; of `FORGE-repository-id'.
+
+;; When accessing Github, then Ghub handles the creation and storage of
+;; access tokens using a setup wizard to make it easier for users to get
+;; started. The tokens for other forges have to be created manually.
+
+;; Ghub is intentionally limited to only provide these two essential
+;; features — basic request functions and guided setup — to avoid being
+;; too opinionated, which would hinder wide adoption. It is assumed that
+;; wide adoption would make life easier for users and maintainers alike,
+;; because then all packages that talk to forge APIs could be configured
+;; the same way.
+
+;; Please consult the manual (info "ghub") for more information.
+
+;;; Code:
+
+(require 'auth-source)
+(require 'cl-lib)
+(require 'compat)
+(require 'gnutls)
+(require 'let-alist)
+(require 'llama)
+(require 'url)
+(require 'url-auth)
+(require 'url-http)
+
+(eval-when-compile (require 'subr-x))
+
+(declare-function glab-repository-id "glab" (owner name &key username auth host))
+(declare-function gtea-repository-id "gtea" (owner name &key username auth host))
+(declare-function gogs-repository-id "gogs" (owner name &key username auth host))
+(declare-function buck-repository-id "buck" (owner name &key username auth host))
+
+(defvar url-callback-arguments)
+(defvar url-http-end-of-headers)
+(defvar url-http-extra-headers)
+(defvar url-http-response-status)
+
+;;; Settings
+
+(defconst ghub-default-host "api.github.com"
+ "The default host that is used if `ghub.host' is not set.")
+
+(defvar ghub-github-token-scopes '(repo)
+ "The Github API scopes that your private tools need.
+
+You have to manually create or update the token at
+https://github.com/settings/tokens. This variable
+only serves as documentation.")
+
+(defvar ghub-insecure-hosts nil
+ "List of hosts that use http instead of https.")
+
+(defvar ghub-debug nil
+ "Record additional debug information.")
+
+;;; Request
+;;;; Object
+
+(cl-defstruct (ghub--req
+ (:constructor ghub--make-req)
+ (:copier nil))
+ (url nil :read-only nil)
+ (forge nil :read-only t)
+ (silent nil :read-only t)
+ (method nil :read-only t)
+ (headers nil :read-only t)
+ (handler nil :read-only t)
+ (unpaginate nil :read-only nil)
+ (noerror nil :read-only t)
+ (reader nil :read-only t)
+ (buffer nil :read-only t)
+ (callback nil :read-only t)
+ (errorback nil :read-only t)
+ (value nil :read-only nil)
+ (extra nil :read-only nil))
+
+(defalias 'ghub-req-extra #'ghub--req-extra)
+
+;;;; API
+
+(define-error 'ghub-error "Ghub/Url Error" 'error)
+(define-error 'ghub-http-error "HTTP Error" 'ghub-error)
+
+(defvar ghub-response-headers nil
+ "The headers returned in response to the last request.
+`ghub-request' returns the response body and stores the
+response headers in this variable.")
+
+(cl-defun ghub-head (resource
+ &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `HEAD' request for RESOURCE, with optional query PARAMS.
+Like calling `ghub-request' (which see) with \"HEAD\" as METHOD."
+ (ghub-request "HEAD" resource params
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun ghub-get (resource
+ &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `GET' request for RESOURCE, with optional query PARAMS.
+Like calling `ghub-request' (which see) with \"GET\" as METHOD."
+ (ghub-request "GET" resource params
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun ghub-put (resource
+ &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PUT\" as METHOD."
+ (ghub-request "PUT" resource params
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun ghub-post (resource
+ &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `POST' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"POST\" as METHOD."
+ (ghub-request "POST" resource params
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun ghub-patch (resource
+ &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PATCH' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PATCH\" as METHOD."
+ (ghub-request "PATCH" resource params
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun ghub-delete (resource
+ &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"DELETE\" as METHOD."
+ (ghub-request "DELETE" resource params
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun ghub-request ( method resource
+ &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host forge
+ callback errorback value extra)
+ "Make a request for RESOURCE and return the response body.
+
+Also place the response headers in `ghub-response-headers'.
+
+METHOD is the HTTP method, given as a string.
+RESOURCE is the resource to access, given as a string beginning
+ with a slash.
+
+PARAMS, QUERY, PAYLOAD and HEADERS are alists used to specify
+ data. The Github API documentation is vague on how data has
+ to be transmitted and for a particular resource usually just
+ talks about \"parameters\". Generally speaking when the METHOD
+ is \"HEAD\" or \"GET\", then they have to be transmitted as a
+ query, otherwise as a payload.
+Use PARAMS to automatically transmit like QUERY or PAYLOAD would
+ depending on METHOD.
+Use QUERY to explicitly transmit data as a query.
+Use PAYLOAD to explicitly transmit data as a payload.
+ Instead of an alist, PAYLOAD may also be a string, in which
+ case it gets encoded as UTF-8 but is otherwise transmitted as-is.
+Use HEADERS for those rare resources that require that the data
+ is transmitted as headers instead of as a query or payload.
+ When that is the case, then the API documentation usually
+ mentions it explicitly.
+
+If SILENT is non-nil, then don't message progress reports and
+ the like.
+
+If UNPAGINATE is t, then make as many requests as necessary to
+ get all values. If UNPAGINATE is a natural number, then get
+ at most that many pages. For any other non-nil value raise
+ an error.
+If NOERROR is non-nil, then do not raise an error if the request
+ fails and return nil instead. If NOERROR is `return', then
+ return the error payload instead of nil.
+If READER is non-nil, then it is used to read and return from the
+ response buffer. The default is `ghub--read-json-payload'.
+ For the very few resources that do not return JSON, you might
+ want to use `ghub--decode-payload'.
+
+If USERNAME is non-nil, then make a request on behalf of that
+ user. It is better to specify the user using the Git variable
+ `github.user' for \"api.github.com\", or `github.HOST.user' if
+ connecting to a Github Enterprise instance.
+
+Each package that uses `ghub' should use its own token. If AUTH
+ is nil, then the generic `ghub' token is used instead. This
+ is only acceptable for personal utilities. A packages that
+ is distributed to other users should always use this argument
+ to identify itself, using a symbol matching its name.
+
+ Package authors who find this inconvenient should write a
+ wrapper around this function and possibly for the
+ method-specific functions as well.
+
+ Some symbols have a special meaning. `none' means to make an
+ unauthorized request. `basic' means to make a password based
+ request. If the value is a string, then it is assumed to be
+ a valid token. `basic' and an explicit token string are only
+ intended for internal and debugging uses.
+
+If HOST is non-nil, then connect to that Github instance. This
+ defaults to \"api.github.com\". When a repository is connected
+ to a Github Enterprise instance, then it is better to specify
+ that using the Git variable `github.host' instead of using this
+ argument.
+
+If FORGE is `gitlab', then connect to Gitlab.com or, depending
+ on HOST, to another Gitlab instance. This is only intended for
+ internal use. Instead of using this argument you should use
+ function `glab-request' and other `glab-*' functions.
+
+If CALLBACK and/or ERRORBACK is non-nil, then make one or more
+ asynchronous requests and call CALLBACK or ERRORBACK when
+ finished. If no error occurred, then call CALLBACK, unless
+ that is nil.
+
+ If an error occurred, then call ERRORBACK, or if that is nil,
+ then CALLBACK. ERRORBACK can also be t, in which case an error
+ is signaled instead. NOERROR is ignored for all asynchronous
+ requests.
+
+Both callbacks are called with four arguments.
+ 1. For CALLBACK, the combined value of the retrieved pages.
+ For ERRORBACK, the error that occurred when retrieving the
+ last page.
+ 2. The headers of the last page as an alist.
+ 3. Status information provided by `url-retrieve'. Its `:error'
+ property holds the same information as ERRORBACK's first
+ argument.
+ 4. A `ghub--req' struct, which can be passed to `ghub-continue'
+ (which see) to retrieve the next page, if any."
+ (cl-assert (or (booleanp unpaginate) (natnump unpaginate)))
+ (unless (string-prefix-p "/" resource)
+ (setq resource (concat "/" resource)))
+ (unless host
+ (setq host (ghub--host forge)))
+ (unless (or username (stringp auth) (eq auth 'none))
+ (setq username (ghub--username host forge)))
+ (cond ((not params))
+ ((member method '("GET" "HEAD"))
+ (when query
+ (error "PARAMS and QUERY are mutually exclusive for METHOD %S"
+ method))
+ (setq query params))
+ (t
+ (when payload
+ (error "PARAMS and PAYLOAD are mutually exclusive for METHOD %S"
+ method))
+ (setq payload params)))
+ (when (or callback errorback)
+ (setq noerror t))
+ (ghub--retrieve
+ (ghub--encode-payload payload)
+ (ghub--make-req
+ :url (url-generic-parse-url
+ (concat (if (member host ghub-insecure-hosts) "http://" "https://")
+ (cond ((and (equal resource "/graphql")
+ (string-suffix-p "/v3" host))
+ ;; Needed for some Github Enterprise instances.
+ (substring host 0 -3))
+ ((and (equal resource "/api/graphql")
+ (string-suffix-p "/api/v4" host))
+ ;; Needed for all Gitlab instances.
+ (substring host 0 -7))
+ (host))
+ resource
+ (and query (concat "?" (ghub--url-encode-params query)))))
+ :forge forge
+ :silent silent
+ ;; Encode in case caller used (symbol-name 'GET). #35
+ :method (encode-coding-string method 'utf-8)
+ :headers (ghub--headers headers host auth username forge)
+ :handler #'ghub--handle-response
+ :unpaginate unpaginate
+ :noerror noerror
+ :reader reader
+ :buffer (current-buffer)
+ :callback callback
+ :errorback errorback
+ :value value
+ :extra extra)))
+
+(defun ghub-continue (req)
+ "If there is a next page, then retrieve that.
+
+This function is only intended to be called from callbacks. If
+there is a next page, then retrieve that and return the buffer
+that the result will be loaded into, or t if the process has
+already completed. If there is no next page, then return nil.
+
+Callbacks are called with four arguments (see `ghub-request').
+The forth argument is a `ghub--req' struct, intended to be passed
+to this function. A callback may use the struct's `extra' slot
+to pass additional information to the callback that will be
+called after the next request has finished. Use the function
+`ghub-req-extra' to get and set the value of this slot."
+ (and (assq 'next (ghub-response-link-relations req))
+ (or (ghub--retrieve nil req) t)))
+
+(cl-defun ghub-wait (resource
+ &optional duration
+ &key username auth host forge)
+ "Busy-wait up to DURATION seconds for RESOURCE to become available.
+
+DURATION specifies how many seconds to wait at most. It defaults
+to 64 seconds. The first attempt is made immediately, the second
+after two seconds, and each subsequent attempt is made after
+waiting as long again as we already waited between all preceding
+attempts combined.
+
+See `ghub-request' for information about the other arguments."
+ (unless duration
+ (setq duration 64))
+ (with-local-quit
+ (let ((total 0))
+ (while (not (ghub-request "GET" resource nil
+ :noerror t
+ :username username
+ :auth auth
+ :host host
+ :forge forge))
+ (message "Waited (%3ss of %ss) for %s..." total duration resource)
+ (if (= total duration)
+ (error "%s is taking too long to create %s"
+ (if forge (capitalize (symbol-name forge)) "Github")
+ resource)
+ (if (> total 0)
+ (let ((wait (min total (- duration total))))
+ (sit-for wait)
+ (cl-incf total wait))
+ (sit-for (setq total 2))))))))
+
+(defun ghub-response-link-relations (req &optional headers payload)
+ "Return an alist of link relations in HEADERS.
+If optional HEADERS is nil, then return those that were
+previously stored in the variable `ghub-response-headers'.
+
+When accessing a Bitbucket instance then the link relations
+are in PAYLOAD instead of HEADERS, making their API merely
+RESTish and forcing this function to append those relations
+to the value of `ghub-response-headers', for later use when
+this function is called with nil for PAYLOAD."
+ (if (eq (ghub--req-forge req) 'bitbucket)
+ (if payload
+ (let* ((page (seq-keep (##assq % payload)
+ '(size page pagelen next previous)))
+ (headers (cons (cons 'link-alist page) headers)))
+ (if (and req (or (ghub--req-callback req)
+ (ghub--req-errorback req)))
+ (setq-local ghub-response-headers headers)
+ (setq-default ghub-response-headers headers))
+ page)
+ (cdr (assq 'link-alist ghub-response-headers)))
+ (and-let* ((headers (or headers ghub-response-headers))
+ (rels (cdr (or (assoc "Link" headers)
+ (assoc "link" headers)))))
+ (mapcar (lambda (elt)
+ (pcase-let ((`(,url ,rel) (split-string elt "; ")))
+ (cons (intern (substring rel 5 -1))
+ (substring url 1 -1))))
+ (split-string rels ", ?")))))
+
+(cl-defun ghub-repository-id (owner name &key username auth host forge noerror)
+ "Return the id of the specified repository.
+Signal an error if the id cannot be determined."
+ (let ((fn (cl-case forge
+ ((nil ghub github) #'ghub--repository-id)
+ (gitlab #'glab-repository-id)
+ (gitea #'gtea-repository-id)
+ (gogs #'gogs-repository-id)
+ (bitbucket #'buck-repository-id)
+ (t (intern (format "%s-repository-id" forge))))))
+ (unless (fboundp fn)
+ (error "ghub-repository-id: Forge type/abbreviation `%s' is unknown"
+ forge))
+ (or (funcall fn owner name :username username :auth auth :host host)
+ (and (not noerror)
+ (error "Repository %S does not exist on %S.\n%s%S?"
+ (concat owner "/" name)
+ (or host (ghub--host forge))
+ "Maybe it was renamed and you have to update "
+ "remote.<remote>.url")))))
+
+;;;; Internal
+
+(cl-defun ghub--retrieve (payload req)
+ (let ((url-request-extra-headers
+ (let ((headers (ghub--req-headers req)))
+ (if (functionp headers) (funcall headers) headers)))
+ (url-request-method (ghub--req-method req))
+ (url-request-data payload)
+ (url-show-status nil)
+ (url (ghub--req-url req))
+ (handler (ghub--req-handler req))
+ (silent (ghub--req-silent req)))
+ (if (or (ghub--req-callback req)
+ (ghub--req-errorback req))
+ (url-retrieve url handler (list req) silent)
+ (if-let ((buf (url-retrieve-synchronously url silent)))
+ (with-current-buffer buf
+ (funcall handler (car url-callback-arguments) req))
+ (error "ghub--retrieve: No buffer returned")))))
+
+(defun ghub--handle-response (status req)
+ (let ((buf (current-buffer)))
+ (unwind-protect
+ (progn
+ (set-buffer-multibyte t)
+ (let* ((unpaginate (ghub--req-unpaginate req))
+ (headers (ghub--handle-response-headers status req))
+ (payload (ghub--handle-response-payload req))
+ (payload (ghub--handle-response-error status payload req))
+ (value (ghub--handle-response-value payload req))
+ (prev (ghub--req-url req))
+ (next (cdr (assq 'next (ghub-response-link-relations
+ req headers payload)))))
+ (when (numberp unpaginate)
+ (cl-decf unpaginate))
+ (setf (ghub--req-url req)
+ (url-generic-parse-url next))
+ (setf (ghub--req-unpaginate req) unpaginate)
+ (or (and next
+ unpaginate
+ (or (eq unpaginate t)
+ (> unpaginate 0))
+ (ghub-continue req))
+ (let ((req-buf (ghub--req-buffer req))
+ (callback (ghub--req-callback req))
+ (errorback (ghub--req-errorback req))
+ (err (plist-get status :error)))
+ (cond ((and err errorback)
+ (setf (ghub--req-url req) prev)
+ (funcall (if (eq errorback t)
+ 'ghub--errorback
+ errorback)
+ err headers status req))
+ (callback
+ (save-current-buffer
+ (when (buffer-live-p req-buf)
+ (set-buffer req-buf))
+ (funcall callback value headers status req)))
+ (t value))))))
+ (when (and (buffer-live-p buf)
+ (not (buffer-local-value 'ghub-debug buf)))
+ (kill-buffer buf)))))
+
+(defun ghub--handle-response-headers (_status req)
+ (let (headers)
+ (when (memq url-http-end-of-headers '(nil 0))
+ (unless url-debug (setq url-debug t))
+ (error "BUG: Missing headers in response buffer %s" (current-buffer)))
+ (goto-char (point-min))
+ (forward-line 1)
+ (while (re-search-forward "^\\([^:]*\\): \\(.+\\)"
+ url-http-end-of-headers t)
+ (push (cons (match-string 1)
+ (match-string 2))
+ headers))
+ (setq headers (nreverse headers))
+ (goto-char (1+ url-http-end-of-headers))
+ (if (and req (or (ghub--req-callback req)
+ (ghub--req-errorback req)))
+ (setq-local ghub-response-headers headers)
+ (setq-default ghub-response-headers headers))
+ headers))
+
+(defun ghub--handle-response-error (status payload req)
+ (if-let ((err (plist-get status :error)))
+ (if-let ((noerror (ghub--req-noerror req)))
+ (if (eq noerror 'return)
+ payload
+ (setcdr (last err) (list payload))
+ nil)
+ (ghub--signal-error err payload req))
+ payload))
+
+(defun ghub--signal-error (err &optional payload req)
+ (pcase-let ((`(,symb . ,data) err))
+ (if (eq symb 'error)
+ (if (eq (car-safe data) 'http)
+ (signal 'ghub-http-error
+ (let ((code (car (cdr-safe data))))
+ (list code
+ (nth 2 (assq code url-http-codes))
+ (and req (url-recreate-url (ghub--req-url req)))
+ payload)))
+ (signal 'ghub-error data))
+ (signal symb data))))
+
+(defun ghub--errorback (err _headers _status req)
+ (ghub--signal-error err (nth 3 err) req))
+
+(defun ghub--handle-response-value (payload req)
+ (setf (ghub--req-value req)
+ (nconc (ghub--req-value req)
+ (if-let ((nested (and (eq (ghub--req-forge req) 'bitbucket)
+ (assq 'values payload))))
+ (cdr nested)
+ payload))))
+
+(defun ghub--handle-response-payload (req)
+ (funcall (or (ghub--req-reader req)
+ 'ghub--read-json-payload)
+ url-http-response-status))
+
+(defun ghub--read-json-payload (_status &optional json-type-args)
+ (and-let* ((payload (ghub--decode-payload)))
+ (ghub--assert-json-available)
+ (condition-case nil
+ (apply #'json-parse-string payload
+ (or json-type-args
+ '( :object-type alist
+ :array-type list
+ :null-object nil
+ :false-object nil)))
+ (json-parse-error
+ (pop-to-buffer (current-buffer))
+ (setq-local ghub-debug t)
+ `((message . ,(if (looking-at "<!DOCTYPE html>")
+ (if (re-search-forward
+ "<p>\\(?:<strong>\\)?\\([^<]+\\)" nil t)
+ (match-string 1)
+ "error description missing")
+ (string-trim (buffer-substring (point) (point-max)))))
+ (documentation_url
+ . "https://github.com/magit/ghub/wiki/Github-Errors"))))))
+
+(defun ghub--decode-payload (&optional _status)
+ (and (not (eobp))
+ (decode-coding-string
+ (buffer-substring-no-properties (point) (point-max))
+ 'utf-8)))
+
+(defun ghub--encode-payload (payload)
+ (cl-typecase payload
+ (null nil)
+ (string (encode-coding-string payload 'utf-8))
+ (t (ghub--assert-json-available)
+ (encode-coding-string
+ (json-serialize payload
+ :null-object :null
+ :false-object nil)
+ 'utf-8))))
+
+(defun ghub--url-encode-params (params)
+ (mapconcat (lambda (param)
+ (pcase-let ((`(,key . ,val) param))
+ (concat (url-hexify-string (symbol-name key)) "="
+ (cl-typecase val
+ (integer (number-to-string val))
+ (boolean (if val "true" "false"))
+ (t (url-hexify-string val))))))
+ params "&"))
+
+(defun ghub--assert-json-available ()
+ (unless (and (fboundp 'json-available-p)
+ (json-available-p))
+ (error "Ghub requires Emacs 29 --with-json or Emacs >= 30")))
+
+;;; Authentication
+;;;; API
+
+;;;###autoload
+(defun ghub-clear-caches ()
+ "Clear all caches that might negatively affect Ghub.
+
+If a library that is used by Ghub caches incorrect information
+such as a mistyped password, then that can prevent Ghub from
+asking the user for the correct information again.
+
+Set `url-http-real-basic-auth-storage' to nil
+and call `auth-source-forget+'."
+ (interactive)
+ (setq url-http-real-basic-auth-storage nil)
+ (auth-source-forget+))
+
+;;;; Internal
+
+(defun ghub--headers (headers host auth username forge)
+ (push (cons "Content-Type" "application/json") headers)
+ (if (eq auth 'none)
+ headers
+ (unless (or username (stringp auth))
+ (setq username (ghub--username host forge)))
+ (lambda ()
+ (if (eq auth 'basic)
+ (cons (cons "Authorization" (ghub--basic-auth host username))
+ headers)
+ (cons (ghub--auth host auth username forge) headers)))))
+
+(cl-defgeneric ghub--auth (host auth &optional username forge)
+ (unless username
+ (setq username (ghub--username host forge)))
+ (if (eq auth 'basic)
+ (cl-ecase forge
+ ((nil gitea gogs bitbucket)
+ (cons "Authorization" (ghub--basic-auth host username)))
+ ((github gitlab)
+ (error "%s does not support basic authentication"
+ (capitalize (symbol-name forge)))))
+ (cons (cl-ecase forge
+ ((nil github gitea gogs bitbucket)
+ "Authorization")
+ (gitlab
+ "Private-Token"))
+ (if (eq forge 'bitbucket)
+ ;; For some undocumented reason Bitbucket supports
+ ;; values of the form "token <token>" only for GET
+ ;; requests. For PUT requests we have to use basic
+ ;; authentication. Note that the secret is a token
+ ;; (aka "app password"), not the actual password.
+ ;; The documentation fails to mention this little
+ ;; detail. See #97.
+ (concat "Basic "
+ (base64-encode-string
+ (concat username ":"
+ (ghub--token host username auth nil forge))
+ t))
+ (concat
+ (and (not (eq forge 'gitlab)) "token ")
+ (encode-coding-string
+ (cl-typecase auth
+ (string auth)
+ (null (ghub--token host username 'ghub nil forge))
+ (symbol (ghub--token host username auth nil forge))
+ (t (signal 'wrong-type-argument
+ `((or stringp symbolp) ,auth))))
+ 'utf-8))))))
+
+(defun ghub--basic-auth (host username)
+ (let ((url (url-generic-parse-url
+ (if (member host ghub-insecure-hosts) "http://" "https://"))))
+ (setf (url-user url) username)
+ (url-basic-auth url t)))
+
+(defun ghub--token (host username package &optional nocreate forge)
+ (let* ((user (ghub--ident username package))
+ (token (or (ghub--auth-source-get :secret :host host :user user)
+ (and (string-match "\\`\\([^/]+\\)" host)
+ (ghub--auth-source-get :secret
+ :host (match-string 1 host)
+ :user user)))))
+ (unless (or token nocreate)
+ (error "\
+Required %s token (%S for %s%S) does not exist.
+See https://magit.vc/manual/ghub/Getting-Started.html
+or (info \"(ghub)Getting Started\") for instructions."
+ (capitalize (symbol-name (or forge 'github)))
+ user
+ (if (string-match "\\`\\([^/]+\\)" host)
+ (format "either %S or " (match-string 1 host))
+ "")
+ host))
+ (if (functionp token) (funcall token) token)))
+
+(cl-defgeneric ghub--host (&optional forge)
+ (cl-ecase forge
+ ((nil github)
+ (or (ignore-errors (car (process-lines "git" "config" "github.host")))
+ ghub-default-host))
+ (gitlab
+ (or (ignore-errors (car (process-lines "git" "config" "gitlab.host")))
+ (bound-and-true-p glab-default-host)))
+ (gitea
+ (or (ignore-errors (car (process-lines "git" "config" "gitea.host")))
+ (bound-and-true-p gtea-default-host)))
+ (gogs
+ (or (ignore-errors (car (process-lines "git" "config" "gogs.host")))
+ (bound-and-true-p gogs-default-host)))
+ (bitbucket
+ (or (ignore-errors (car (process-lines "git" "config" "bitbucket.host")))
+ (bound-and-true-p buck-default-host)))))
+
+(cl-defgeneric ghub--username (host &optional forge)
+ (let ((var
+ (cl-ecase forge
+ ((nil github)
+ (if (equal host ghub-default-host)
+ "github.user"
+ (format "github.%s.user" host)))
+ (gitlab
+ (if (equal host "gitlab.com/api/v4")
+ "gitlab.user"
+ (format "gitlab.%s.user" host)))
+ (bitbucket
+ (if (equal host "api.bitbucket.org/2.0")
+ "bitbucket.user"
+ (format "bitbucket.%s.user" host)))
+ (gitea
+ (when (zerop (call-process "git" nil nil nil "config" "gitea.host"))
+ (message "WARNING: gitea.host is set but always ignored"))
+ (format "gitea.%s.user" host))
+ (gogs
+ (when (zerop (call-process "git" nil nil nil "config" "gogs.host"))
+ (message "WARNING: gogs.host is set but always ignored"))
+ (format "gogs.%s.user" host)))))
+ (condition-case nil
+ (car (process-lines "git" "config" var))
+ (error
+ (let ((user (read-string
+ (format "Git variable `%s' is unset. Set to: " var))))
+ (if (equal user "")
+ (user-error "The empty string is not a valid username")
+ (call-process
+ "git" nil nil nil "config"
+ (if (eq (read-char-choice
+ (format "Set %s=%s [g]lobally (recommended) or [l]ocally? "
+ var user)
+ (list ?g ?l))
+ ?g)
+ "--global"
+ "--local")
+ var user)
+ user))))))
+
+(defun ghub--ident (username package)
+ (format "%s^%s" username package))
+
+(defun ghub--auth-source-get (keys &rest spec)
+ (declare (indent 1))
+ (if-let ((plist (car (apply #'auth-source-search
+ (append spec (list :max 1))))))
+ (if (keywordp keys)
+ (plist-get plist keys)
+ (mapcar (##plist-get plist %) keys))
+ ;; Auth-Source caches the information that there is no value, but in
+ ;; our case that is a situation that needs fixing, so we want to keep
+ ;; trying, by invalidating that information.
+ (auth-source-forget spec)
+ nil))
+
+;;; _
+(provide 'ghub)
+(require 'ghub-graphql)
+;;; ghub.el ends here
--- /dev/null
+This is ghub.info, produced by makeinfo version 6.8 from ghub.texi.
+
+ Copyright (C) 2017-2025 Jonas Bernoulli
+ <emacs.ghubNOSPAMjonas.bernoulli.dev>
+
+ You can redistribute this document 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 document 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.
+
+INFO-DIR-SECTION Emacs
+START-INFO-DIR-ENTRY
+* Ghub: (ghub). Client library for the Github API.
+END-INFO-DIR-ENTRY
+
+\1f
+File: ghub.info, Node: Top, Next: Introduction, Up: (dir)
+
+Ghub User and Developer Manual
+******************************
+
+Ghub is an Emacs library that is used by various packages to access the
+APIs of various instances of various Git forge implementations.
+
+This manual is for Ghub version 4.3.1.
+
+ Copyright (C) 2017-2025 Jonas Bernoulli
+ <emacs.ghubNOSPAMjonas.bernoulli.dev>
+
+ You can redistribute this document 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 document 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.
+
+* Menu:
+
+* Introduction::
+* Getting Started::
+* API::
+* Notes::
+* Function Index::
+* Variable Index::
+
+— The Detailed Node Listing —
+
+Getting Started
+
+* Basic Concepts, Arguments and Variables: Basic Concepts Arguments and Variables.
+* Setting the Username::
+* Creating and Storing a Token::
+* Github Configuration Variables::
+
+API
+
+* Their APIs::
+* Making REST Requests::
+* Making GraphQL Requests::
+* Github Convenience Wrappers::
+* Non-Github Convenience Wrappers::
+
+Notes
+
+* Using Ghub in Personal Scripts::
+* Using Ghub in Your Own Package::
+* Forge Limitations and Notes::
+
+
+\1f
+File: ghub.info, Node: Introduction, Next: Getting Started, Prev: Top, Up: Top
+
+1 Introduction
+**************
+
+Ghub is an Emacs library that is used by various packages to access the
+APIs of various instances of various Git forge implementations.
+
+ A forge is a web-based collaborative software platform for developing
+and distributing computer applications. Examples include Github and
+Gitlab.
+
+\1f
+File: ghub.info, Node: Getting Started, Next: API, Prev: Introduction, Up: Top
+
+2 Getting Started
+*****************
+
+This manual guides you through the steps that are necessary to use the
+Forge package and/or to make a request using just Ghub itself, such as
+this:
+
+ (ghub-request "GET" "/user")
+
+ To be able to do that, Ghub needs to know who you want to talk to,
+who you are, and how you are going to prove the latter to the former.
+
+ Additionally Ghub wants to know on behalf of which Emacs package it
+is making a request. So the question of "who is making the request" has
+to be rephrased as "which human (or bot) is using what (Emacs) package
+to make the request". If, for example, the human known as "tarsius" is
+using the ‘forge’ package, then that is represented in some places using
+the string "tarsius^forge".
+
+* Menu:
+
+* Basic Concepts, Arguments and Variables: Basic Concepts Arguments and Variables.
+* Setting the Username::
+* Creating and Storing a Token::
+* Github Configuration Variables::
+
+\1f
+File: ghub.info, Node: Basic Concepts Arguments and Variables, Next: Setting the Username, Up: Getting Started
+
+2.1 Basic Concepts, Arguments and Variables
+===========================================
+
+(If you are trying to configure Forge for <https://github.com> and/or
+<https://gitlab.com>, then you can skip this section.)
+
+ Originally Ghub supported only Github but now it also supports
+Gitlab, Gitea, Gogs and Bitbucket. Because of that, the function
+‘ghub-request’ defaults to acting on a ‘github’ forge, but can be told
+to act on other forges using the FORGE argument.
+
+ The FORGE argument only specifies what kind of forge we are
+accessing, not which host specifically. The HOST argument can be used
+to select the instance. For some forges a default instance is defined:
+
+ • Forge ‘github’ defaults to host ‘api.github.com’.
+ • Forge ‘gitlab’ defaults to host ‘gitlab.com/api/v4’.
+ • Forge ‘bitbucket’ defaults to host ‘api.bitbucket.org/2.0’.
+ • No canonical host exists for the ‘gitea’ and ‘gogs’ forges and
+ ‘localhost:3000/api/v1’ is used as the default host in both cases.
+
+ Together the FORGE and HOST arguments specify the forge type and
+instance. In addition to that, it is also necessary to specify on whose
+behalf the request is being made, which can be done using the USERNAME
+and AUTH arguments. For example:
+
+ (ghub-request "GET" "/user" nil
+ :forge 'github
+ :host "api.github.com"
+ :username "tarsius"
+ :auth 'forge)
+
+ Having to specify these arguments for every request is inconvenient.
+Additional variables and convenience functions can be used to make that
+unnecessary in most cases.
+
+ (But for debugging purposes the above explicit form is very useful.
+You will obviously have to change the value of USERNAME and you should
+use ‘ghub’ as AUTH when first trying this at home.)
+
+ These variables can be set globally and/or for a specific repository.
+
+ • For "api.github.com" (aka the API of <https://github.com>) the Git
+ variable ‘github.user’ specifies the user.
+ • For another ‘github’ instance the Git variable ‘github.HOST.user’
+ specifies the user. The HOST in that variable name is the same as
+ the value of the HOST argument of the called function.
+ • Instead of specifying the HOST in every function call, the Git
+ variable ‘github.host’ can be used. This should only be set
+ locally.
+
+ These ‘github’ specific variables are discussed in more detail in
+*note Github Configuration Variables::.
+
+For ‘gitlab’ and ‘bitbucket’ forges similar variables are available:
+
+ • ‘gitlab.user’ specifies the <https://gitlab.com> user.
+ • ‘gitlab.HOST.user’ specifies the user for the HOST ‘gitlab’
+ instance.
+ • ‘gitlab.host’ specifies the ‘gitlab’ host, unless the HOST argument
+ is non-nil
+ • ‘bitbucket.user’ specifies the <https://bitbucket.org> user.
+ • ‘bitbucket.HOST.user’ specifies the user for the HOST ‘bitbucket’
+ instance.
+ • ‘bitbucket.host’ specifies the ‘bitbucket’ host, unless the HOST
+ argument is non-nil.
+
+ For the ‘gitea’ and ‘gogs’ forges some similar variables are
+available, however for some of the ‘ghub.*’ variables no equivalent
+variable exist for these two forges:
+
+ • ‘gitea.user’ is *not* used because no canonical ‘gitea’ instance
+ exists.
+ • ‘gitea.HOST.user’ specifies the user for the HOST ‘gitea’ instance.
+ • ‘gitea.host’ specifies the ‘gitea’ host, unless the HOST argument
+ is non-nil
+ • ‘gogs.user’ is *not* used because no canonical ‘gogs’ instance
+ exists.
+ • ‘gogs.HOST.user’ specifies the user for the HOST ‘gogs’ instance.
+ • ‘gogs.host’ specifies the ‘gogs’ host, unless the HOST argument is
+ non-nil
+
+\1f
+File: ghub.info, Node: Setting the Username, Next: Creating and Storing a Token, Prev: Basic Concepts Arguments and Variables, Up: Getting Started
+
+2.2 Setting the Username
+========================
+
+Ghub needs to know your username, which you use on the host that you
+want it to connect to. For each host a different Git variable has to be
+set, to specify the username on that host. More than one variable is
+needed, because you might use different usernames on different hosts.
+
+Setting your Github.com Username
+--------------------------------
+
+To inform Ghub about your "github.com" username do this:
+
+ git config --global github.user USERNAME
+
+ If you need to identify as another user in a particular repository,
+then you have to set that variable locally:
+
+ cd /path/to/repo
+ git config --local github.user USERNAME
+
+Setting your Gitlab.com Username
+--------------------------------
+
+To inform Ghub about your "gitlab.com" username do this:
+
+ git config --global gitlab.user USERNAME
+
+ If you need to identify as another user in a particular repository,
+then you have to set that variable locally:
+
+ cd /path/to/repo
+ git config --local gitlab.user USERNAME
+
+ Make sure you use the correct USERNAME for this forge/host. It might
+not be the same as on "github.com"!
+
+Setting your Github Enterprise Username
+---------------------------------------
+
+For Github Enterprise instances you have to specify where the API can be
+accessed and a different variable has to be used to set the username.
+
+ For example if the API is available at ‘https://example.com/api/v3’,
+then you should do this:
+
+ git config --global github.example.com/api/v3.user USERNAME
+
+ Make sure you use the correct USERNAME for this instance. It might
+not be the same as on "github.com"!
+
+ Doing this only tells Ghub who you are on this host, additionally you
+have to tell Ghub which repositories are connected to that forge/host,
+like so:
+
+ cd /path/to/repo
+ git config --local github.host example.com/api/v3
+
+Setting your Username for Other Hosts and/or Forges
+---------------------------------------------------
+
+To inform Ghub about your username on HOST (a FORGE instance) do this:
+
+ git config --global FORGE.HOST.user USERNAME
+
+ FORGE can be one of ‘bitbucket’, ‘gitea’ or ‘gogs’. It can also be
+‘github’ or ‘gitlab’; but if that is the case, then you should look at
+the preceding sections instead, which discuss these cases specifically.
+
+ HOST identifies the instance. This actually points at the top-level
+endpoint of the API and may contain path components, e.g.:
+‘example.com/api’.
+
+ If you need to identify as another user in a particular repository,
+then you have to set that variable locally:
+
+ cd /path/to/repo
+ git config --local FORGE.HOST.user USERNAME
+
+\1f
+File: ghub.info, Node: Creating and Storing a Token, Next: Github Configuration Variables, Prev: Setting the Username, Up: Getting Started
+
+2.3 Creating and Storing a Token
+================================
+
+2.3.1 Creating a Token
+----------------------
+
+To create a token, use the web interface of the host you want to connect
+to. Here is a list of pages to do this for certain popular hosts:
+
+ • <https://github.com/settings/tokens>
+ • <https://gitlab.com/-/profile/personal_access_tokens>
+
+ For other forges we cannot provide a functioning URL because they
+contain unknown values such as your name. Just go to the general
+settings page of the respective host and then go from there.
+
+ Except on ‘gitea’ and ‘gogs’ each token can be limited to certain
+"scopes", i.e., it is possible to limit for which purposes any given
+token can be used.
+
+ Before you create a token to be used for a certain package, you
+should consult the documentation of that package, which in turn should
+tell you which scopes are needed and why. The Forge package for example
+does so in *note (forge)Token Creation::.
+
+2.3.2 Storing a Token
+---------------------
+
+Please also see *note (auth)Top:: for all the gory details about
+Auth-Source.
+
+ The variable ‘auth-sources’ controls how and where Auth-Source keeps
+its secrets. The default value is a list of three files:
+‘("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")’, but to avoid confusion
+you should make sure that only one of these files exists, and then you
+should also adjust the value of the variable to only ever use that file,
+for example:
+
+ (setq auth-sources '("~/.authinfo"))
+
+ In ‘~/.authinfo’ secrets are stored in plain text. If you don’t want
+that, then you should use the encrypted ‘~/.authinfo.gpg’ instead:
+
+ (setq auth-sources '("~/.authinfo.gpg"))
+
+ Auth-Source also supports storing secrets in various external
+key-chains. See *note (auth)Top:: for more information.
+
+ The default Auth-Source backends only support storing three values
+per entry; the "machine", the "login" and the "password". Because Ghub
+uses separate tokens for each package, it has to squeeze four values
+into those three slots, and it does that by using "USERNAME^PACKAGE" as
+the "login".
+
+ Assuming your *Github* username is "ziggy", the package is named
+"forge", and you want to access *Github.com* with the *token*
+"012345abcdef...", an entry in one of the three mentioned files would
+then look like this:
+
+ machine api.github.com login ziggy^forge password 012345abcdef...
+
+ Assuming your *Gitlab* username is "ziggy", the package is named
+"forge", and you want to access *Gitlab.com* with the *token*
+"012345abcdef...", an entry in one of the three mentioned files would
+then look like this:
+
+ machine gitlab.com/api/v4 login ziggy^forge password 012345abcdef...
+
+\1f
+File: ghub.info, Node: Github Configuration Variables, Prev: Creating and Storing a Token, Up: Getting Started
+
+2.4 Github Configuration Variables
+==================================
+
+The username and, unless you only use Github.com itself, the Github
+Enterprise instance have to be configured using Git variables. In rare
+cases it might also be necessary to specify the identity of the local
+machine, which is done using a lisp variable.
+
+ -- Variable: github.user
+ The Github.com username. This should be set globally and if you
+ have multiple Github.com user accounts, then you should set this
+ locally only for those repositories that you want to access using
+ the secondary identity.
+
+ -- Variable: github.HOST.user
+ This variable serves the same purpose as ‘github.user’ but for the
+ Github Enterprise instance identified by ‘HOST’.
+
+ The reason why separate variables are used is that this makes it
+ possible to set both values globally instead of having to set one
+ of the values locally in each and every repository that is
+ connected to the Github Enterprise instance, not Github.com.
+
+ -- Variable: github.host
+ This variable should only be set locally for a repository and
+ specifies the Github Enterprise edition that that repository is
+ connected to. You should not set this globally because then each
+ and every repository becomes connected to the specified Github
+ Enterprise instance, including those that should actually be
+ connected to Github.com.
+
+ When this is undefined, then "api.github.com" is used (defined in
+ the constant ‘ghub-default-host’, which you should never attempt to
+ change.)
+
+\1f
+File: ghub.info, Node: API, Next: Notes, Prev: Getting Started, Up: Top
+
+3 API
+*****
+
+* Menu:
+
+* Their APIs::
+* Making REST Requests::
+* Making GraphQL Requests::
+* Github Convenience Wrappers::
+* Non-Github Convenience Wrappers::
+
+\1f
+File: ghub.info, Node: Their APIs, Next: Making REST Requests, Up: API
+
+3.1 Their APIs
+==============
+
+Of course this manual does not cover the APIs of all forges that it
+supports, but for your convenience, here are the links to their API
+manuals:
+
+ • Github:
+ • <https://developer.github.com/v4> (GraphQl)
+ • <https://developer.github.com/v3> (REST)
+ • Gitlab:
+ • <https://docs.gitlab.com/ee/api/README.html>
+ • Gitea:
+ • <https://docs.gitea.io/en-us/api-usage>
+ • <https://try.gitea.io/api/swagger>
+ • Gogs:
+ • <https://github.com/gogs/go-gogs-client/wiki>
+ • Bitbucket:
+ • <https://developer.atlassian.com/bitbucket/api/2/reference>
+
+\1f
+File: ghub.info, Node: Making REST Requests, Next: Making GraphQL Requests, Prev: Their APIs, Up: API
+
+3.2 Making REST Requests
+========================
+
+ -- Function: ghub-request method resource &optional params &key query
+ payload headers unpaginate noerror reader username auth host
+ callback errorback url value error extra method*
+ This function makes a request for ‘RESOURCE’ using ‘METHOD’.
+ ‘PARAMS’, ‘QUERY’, ‘PAYLOAD’ and/or ‘HEADERS’ are alists holding
+ additional request data. The response body is returned and the
+ response headers are stored in the variable
+ ‘ghub-response-headers’.
+
+ • ‘METHOD’ is the HTTP method, given as a string.
+ • ‘RESOURCE’ is the resource to access, given as a string
+ beginning with a slash.
+
+ • ‘PARAMS’, ‘QUERY’, ‘PAYLOAD’ and ‘HEADERS’ are alists and are
+ used to specify request data. All these arguments are alists
+ that resemble the JSON expected and returned by the Github
+ API. The keys are symbols and the values stored in the ‘cdr’
+ (not the ‘cadr’) can be strings, integers, or lists of strings
+ and integers.
+
+ The Github API documentation is vague on how data has to be
+ transmitted and for a particular resource usually just talks
+ about "parameters". Generally speaking when the ‘METHOD’ is
+ "HEAD" or "GET", then they have to be transmitted as a query,
+ otherwise as a payload.
+
+ • Use ‘PARAMS’ to automatically transmit like ‘QUERY’ or
+ ‘PAYLOAD’ would depending on ‘METHOD’.
+ • Use ‘QUERY’ to explicitly transmit data as a query.
+ • Use ‘PAYLOAD’ to explicitly transmit data as a payload.
+ Instead of an alist, ‘PAYLOAD’ may also be a string, in
+ which case it gets encoded as UTF-8 but is otherwise
+ transmitted as-is.
+ • Use ‘HEADERS’ for those rare resources that require that
+ the data is transmitted as headers instead of as a query
+ or payload. When that is the case, then the Github API
+ documentation usually mentions it explicitly.
+
+ • If ‘SILENT’ is non-nil, then progress reports and the like are
+ not messaged.
+
+ • If ‘UNPAGINATE’ is t, then this function makes as many
+ requests as necessary to get all values. If ‘UNPAGINATE’ is a
+ natural number, then it gets at most that many pages. For any
+ other non-nil value it raises an error.
+
+ • If ‘NOERROR’ is non-nil, then no error is raised if the
+ request fails and ‘nil’ is returned instead. If ‘NOERROR’ is
+ ‘return’, then the error payload is returned instead of ‘nil’.
+
+ • If ‘READER’ is non-nil, then it is used to read and return
+ from the response buffer. The default is
+ ‘ghub--read-json-payload’. For the very few resources that do
+ not return JSON, you might want to use ‘ghub--decode-payload’.
+
+ • If ‘USERNAME’ is non-nil, then the request is made on behalf
+ of that user. It is better to specify the user using the Git
+ variable ‘github.user’ for "api.github.com", or
+ ‘github.HOST.user’ if connecting to a Github Enterprise
+ instance.
+
+ • Each package that uses Ghub should use its own token. If
+ ‘AUTH’ is ‘nil’ or unspecified, then the generic ‘ghub’ token
+ is used instead. This is only acceptable for personal
+ utilities. A package that is distributed to other users
+ should always use this argument to identify itself, using a
+ symbol matching its name.
+
+ Package authors who find this inconvenient should write a
+ wrapper around this function and possibly for the
+ method-specific functions as well.
+
+ Beside ‘nil’, some other symbols have a special meaning too.
+ ‘none’ means to make an unauthorized request. ‘basic’ means
+ to make a password based request. If the value is a string,
+ then it is assumed to be a valid token. ‘basic’ and an
+ explicit token string are only intended for internal and
+ debugging uses.
+
+ If ‘AUTH’ is a package symbol, then the scopes are specified
+ using the variable ‘AUTH-github-token-scopes’. It is an error
+ if that is not specified. See ‘ghub-github-token-scopes’ for
+ an example.
+
+ • If ‘HOST’ is non-nil, then connect to that Github instance.
+ This defaults to "api.github.com". When a repository is
+ connected to a Github Enterprise instance, then it is better
+ to specify that using the Git variable ‘github.host’ instead
+ of using this argument.
+
+ • If ‘FORGE’ is ‘gitlab’, then connect to Gitlab.com or,
+ depending on ‘HOST’, to another Gitlab instance. This is only
+ intended for internal use. Instead of using this argument you
+ should use function ‘glab-request’ and other ‘glab-*’
+ functions.
+
+ • If ‘CALLBACK’ and/or ‘ERRORBACK’ is non-nil, then this
+ function makes one or more asynchronous requests and calls
+ ‘CALLBACK’ or ‘ERRORBACK’ when finished. If no error
+ occurred, then it calls ‘CALLBACK’, unless that is ‘nil’.
+
+ If an error occurred, then it calls ‘ERRORBACK’, or if that is
+ nil, then ‘CALLBACK’. ‘ERRORBACK’ can also be ‘t’, in which
+ case it signals instead. ‘NOERROR’ is ignored for all
+ asynchronous requests.
+
+ Both callbacks are called with four arguments.
+
+ 1. For ‘CALLBACK’, the combined value of the retrieved
+ pages. For ‘ERRORBACK’, the error that occurred when
+ retrieving the last page.
+ 2. The headers of the last page as an alist.
+ 3. Status information provided by ‘url-retrieve’. Its
+ ‘:error’ property holds the same information as the first
+ argument to ‘ERRORBACK’.
+ 4. A ‘ghub--req’ struct, which can be passed to
+ ‘ghub-continue’ (which see) to retrieve the next page, if
+ any.
+
+ -- Function: ghub-continue args
+ If there is a next page, then this function retrieves that.
+
+ This function is only intended to be called from callbacks. If
+ there is a next page, then that is retrieved and the buffer that
+ the result will be loaded into is returned, or t if the process has
+ already completed. If there is no next page, then return nil.
+
+ Callbacks are called with four arguments (see ‘ghub-request’). The
+ forth argument is a ‘ghub--req’ struct, intended to be passed to
+ this function. A callback may use the struct’s ‘extra’ slot to
+ pass additional information to the callback that will be called
+ after the next request. Use the function ‘ghub-req-extra’ to get
+ and set the value of that slot.
+
+ As an example, using ‘ghub-continue’ in a callback like so:
+
+ (ghub-get "/users/tarsius/repos" nil
+ :callback (lambda (value _headers _status req)
+ (unless (ghub-continue req)
+ (setq my-value value))))
+
+ is equivalent to:
+
+ (ghub-get "/users/tarsius/repos" nil
+ :unpaginate t
+ :callback (lambda (value _headers _status _req)
+ (setq my-value value)))
+
+ To demonstrate how to pass information from one callback to the
+ next, here we record when we start fetching each page:
+
+ (ghub-get "/users/tarsius/repos" nil
+ :extra (list (current-time))
+ :callback (lambda (value _headers _status req)
+ (push (current-time) (ghub-req-extra req))
+ (unless (ghub-continue req)
+ (setq my-times (ghub-req-extra req))
+ (setq my-value value))))
+
+ -- Variable: ghub-response-headers
+ A select few Github API resources respond by transmitting data in
+ the response header instead of in the response body. Because there
+ are so few of these inconsistencies, ‘ghub-request’ always returns
+ the response body.
+
+ To access the response headers use this variable after
+ ‘ghub-request’ has returned.
+
+ -- Function: ghub-response-link-relations req headers payload
+ This function returns an alist of the link relations in ‘HEADERS’,
+ or if optional ‘HEADERS’ is nil, then those in
+ ‘ghub-response-headers’.
+
+ When accessing a Bitbucket instance then the link relations are in
+ ‘PAYLOAD’ instead of ‘HEADERS’, making their API merely RESTish and
+ forcing this function to append those relations to the value of
+ ‘ghub-response-headers’, for later use when this function is called
+ with ‘nil’ for ‘PAYLOAD’.
+
+\1f
+File: ghub.info, Node: Making GraphQL Requests, Next: Github Convenience Wrappers, Prev: Making REST Requests, Up: API
+
+3.3 Making GraphQL Requests
+===========================
+
+ -- Function: ghub-graphql graphql &optional variables &key username
+ auth host callback silent callback errorback value extra
+ This function makes a GraphQL request using ‘GRAPHQL’ and
+ ‘VARIABLES’ as inputs. ‘GRAPHQL’ is a GraphQL string. ‘VARIABLES’
+ is a JSON-like alist. The other arguments behave as for
+ ‘ghub-request’ (which see).
+
+ The response is returned as a JSON-like alist. Even if the
+ response contains ‘errors’, this function does not raise an error.
+ Cursor-handling is likewise left to the caller.
+
+ ‘ghub-graphql’ is a thin convenience wrapper around ‘ghub-request’,
+similar to ‘ghub-post’ and friends. While the latter only hard-code the
+value of the ‘METHOD’ argument, the former also hard-codes ‘RESOURCE’
+and constructs ‘PAYLOAD’ from ‘GRAPHQL’ and ‘VARIABLES’. It also drops
+‘UNPAGINATE’, ‘NOERROR’, ‘READER’ (internal functions expect alist-ified
+JSON) and ‘FORGE’ (only Github currently supports GraphQL).
+
+ ‘ghub-graphql’ does not account for the fact that pagination works
+differently in GraphQL than it does in REST, so users of this function
+have to deal with that themselves. Likewise error handling works
+differently and has to be done by the caller too.
+
+ An early attempt at implementing automatic unpaginating for GraphQL
+can be found in the ‘faithful-graphql’ branch, provided I haven’t
+deleted that by now. On that branch I try to do things as intended by
+the designers of GraphQL, using variables and fragments, and drowning in
+a sea of boilerplate.
+
+ The problem with that approach is that it only works for applications
+that fetch specific information on demand and actually want things to be
+paginated. I am convinced that GraphQL is very nice for web apps.
+
+ However the Forge package for which I have implemented all of this
+has very different needs. It wants to fetch "all the data" and "cache"
+it locally, so that it is available even when there is no internet
+connection. GraphQL was designed around the idea that you should be
+able to "ask for what you need and get exactly that". But when that
+boils down to "look, if I persist, then you are going to hand me over
+all the data anyway, so just caught it up already", then things start to
+fall apart. If Github’s GraphQL allowed pagination to be turned off
+completely, then teaching ‘ghub-graphql’ about error handling would be
+enough.
+
+ But it doesn’t and when doing things as intended, then that leads to
+huge amounts of repetitive boilerplate, which is so boring to write that
+doing it without introducing bugs left and right is near impossible; so
+I decided to give up on GraphQL variables, fragments and conditions, and
+instead implement something more powerful, though also more opinionated.
+
+ -- Function: ghub--graphql-vacuum query variables callback &optional
+ until &key narrow username auth host forge
+ This function is an opinionated alternative to ‘ghub-graphql’. It
+ relies on dark magic to get the job done.
+
+ It makes an initial request using ‘QUERY’. It then looks for
+ paginated edges in the returned data and makes more requests to
+ resolve them. In order to do so it automatically transforms the
+ initial ‘QUERY’ into another query suitable for that particular
+ edge. The data retrieved by subsequent requests is then injected
+ into the data of the original request before that is returned or
+ passed to the callback. If subsequently retrieved data features
+ new paginated edges, then those are followed recursively.
+
+ The end result is essentially the same as using ‘ghub-graphql’, if
+ only it were possible to say "do not paginate anything". The
+ implementation is much more complicated because it is not possible
+ to do that.
+
+ ‘QUERY’ is a GraphQL query expressed as an s-expression. The
+ bundled ‘gsexp’ library is used to turn that into a GraphQL query
+ string. Only a subset of the GraphQL features are supported;
+ fragments for example are not, and magical stuff happens to
+ variables. This is not documented yet, I am afraid. Look at
+ existing callers.
+
+ ‘VARIABLES’ is a JSON-like alist as for ‘ghub-graphql’.
+
+ ‘UNTIL’ is an alist ‘((EDGE-until . VALUE)...)’. When unpaginating
+ ‘EDGE’ try not to fetch beyond the element whose first field has
+ the value ‘VALUE’ and remove that element as well as all "lesser"
+ elements from the retrieved data if necessary. Look at
+ ‘forge--pull-repository’ for an example. This is only useful if
+ you "cache" the response locally and want to avoid fetching data
+ again that you already have.
+
+ Other arguments behave as for ‘ghub-graphql’ and ‘ghub-request’,
+ more or less. If ‘CALLBACK’ is nil, pretty-print the response.
+
+ Using ‘ghub--graphql-vacuum’, the following resource specific
+functions are implemented. These functions are not part of the public
+API yet and are very much subject to change.
+
+ -- Function: ghub-fetch-repository owner name callback &optional until
+ &key username auth host forge
+ This function asynchronously fetches forge data about the specified
+ repository. Once all data has been collected, ‘CALLBACK’ is called
+ with the data as the only argument.
+
+ -- Function: ghub-fetch-issue owner name callback &optional until &key
+ username auth host forge
+ This function asynchronously fetches forge data about the specified
+ issue. Once all data has been collected, ‘CALLBACK’ is called with
+ the data as the only argument.
+
+ -- Function: ghub-fetch-pullreq owner name callback &optional until
+ &key username auth host forge
+ This function asynchronously fetches forge data about the specified
+ pull-request. Once all data has been collected, ‘CALLBACK’ is
+ called with the data as the only argument.
+
+ Note that in order to avoid duplication all of these functions base
+their initial query on the query stored in ‘ghub-fetch-repository’. The
+latter two pass that query through ‘ghub--graphql-prepare-query’, which
+then uses ‘ghub--graphql-narrow-query’ to remove parts the caller is not
+interested in. These two functions are also used internally, when
+unpaginating, but as demonstrated here they can be useful even before
+making an initial request.
+
+\1f
+File: ghub.info, Node: Github Convenience Wrappers, Next: Non-Github Convenience Wrappers, Prev: Making GraphQL Requests, Up: API
+
+3.4 Github Convenience Wrappers
+===============================
+
+ -- Function: ghub-head resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: ghub-get resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ These functions are simple wrappers around ‘ghub-request’. Their
+ signature is identical to that of the latter, except that they do
+ not have an argument named ‘METHOD’. The HTTP method is instead
+ given by the second word in the function name.
+
+ As described in the documentation for ‘ghub-request’, it depends on
+ the used method whether the value of the ‘PARAMS’ argument is used
+ as the query or the payload. For the "HEAD" and "GET" methods it
+ is used as the query.
+
+ -- Function: ghub-put resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: ghub-post resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: ghub-patch resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: ghub-delete resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ These functions are simple wrappers around ‘ghub-request’. Their
+ signature is identical to that of the latter, except that they do
+ not have an argument named ‘METHOD’. The HTTP method is instead
+ given by the second word in the function name.
+
+ As described in the documentation for ‘ghub-request’, it depends on
+ the used method whether the value of the ‘PARAMS’ argument is used
+ as the query or the payload. For the "PUT", "POST", "PATCH" and
+ "DELETE" methods it is used as the payload.
+
+ -- Function: ghub-wait resource &optional duration &key username auth
+ host
+ Some API requests result in an immediate successful response even
+ when the requested action has not actually been carried out yet.
+ An example is the request for the creation of a new repository,
+ which doesn’t cause the repository to immediately become available.
+ The Github API documentation usually mentions this when describing
+ an affected resource.
+
+ If you want to do something with some resource right after making a
+ request for its creation, then you might have to wait for it to
+ actually be created. This function can be used to do so. It
+ repeatedly tries to access the resource until it becomes available
+ or until a timeout is reached. In the latter case it signals
+ ‘ghub-error’.
+
+ ‘RESOURCE’ specifies the resource that this function waits for.
+
+ ‘DURATION’ specifies the maximum number of seconds to wait for,
+ defaulting to 64 seconds. Emacs will block during that time, but
+ the user can abort using ‘C-g’.
+
+ The first attempt is made immediately and will often succeed. If
+ not, then another attempt is made after two seconds, and each
+ subsequent attempt is made after waiting as long as we already
+ waited between all preceding attempts combined.
+
+ See ‘ghub-request’’s documentation above for information about the
+ other arguments.
+
+\1f
+File: ghub.info, Node: Non-Github Convenience Wrappers, Prev: Github Convenience Wrappers, Up: API
+
+3.5 Non-Github Convenience Wrappers
+===================================
+
+‘ghub-request’ and ‘ghub-METHOD’ can be used to make a request for any
+of the supported forge types, but except when making a request for a
+‘github’ instance, then that requires the use of the FORGE argument.
+
+ To avoid that, functions named ‘FORGE-request’ and ‘FORGE-METHOD’ are
+also available. The following forms are equivalent, for example:
+
+ (ghub-get ... :auth 'PACKAGE :forge 'gitlab)
+ (glab-get ... :auth 'PACKAGE)
+
+ These forms would remain equivalent even if you did not specify a
+value for the AUTH arguments — but you should not do that if you plan to
+share your code with others (see *note Using Ghub in Your Own
+Package::). If you do omit AUTH, then the request is made on behalf of
+the ‘ghub’ package, *regardless* of the symbol prefix of the function
+you use to do so.
+
+ All ‘FORGE-request’ and ‘FORGE-METHOD’ functions, including but not
+limited to ‘ghub-METHOD’, are very simple wrappers around
+‘ghub-request’. They take fewer arguments than ‘ghub-request’ and
+instead pass constant values for the arguments METHOD and/or FORGE.
+
+ -- Function: buck-request resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: glab-request resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gogs-request resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ Wrappers around ‘ghub-request’ which hardcode the FORGE to either
+ ‘bitbucket’, ‘gitlab’, ‘gogs’ or ‘gitea’.
+
+ -- Function: buck-get resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: buck-put resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: buck-post resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: buck-delete resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: glab-head resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: glab-get resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: glab-put resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: glab-post resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: glab-patch resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: glab-delete resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gogs-get resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gogs-put resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gogs-post resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gogs-patch resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gogs-delete resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gtea-get resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gtea-put resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gtea-post resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gtea-patch resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ -- Function: gtea-delete resource &optional params &key query payload
+ headers unpaginate noerror reader username auth host callback
+ errorback
+ Wrappers around ‘FORGE-METHOD’ which hardcode the FORGE to either
+ ‘bitbucket’, ‘gitlab’, ‘gogs’ or ‘gitea’, and the METHOD to the
+ implied method.
+
+ Note that ‘buck-head’, ‘buck-patch’, ‘gogs-head’ and ‘gtea-head’ do
+ not exist because the respective APIs do not appear to support
+ these methods.
+
+\1f
+File: ghub.info, Node: Notes, Next: Function Index, Prev: API, Up: Top
+
+4 Notes
+*******
+
+* Menu:
+
+* Using Ghub in Personal Scripts::
+* Using Ghub in Your Own Package::
+* Forge Limitations and Notes::
+
+\1f
+File: ghub.info, Node: Using Ghub in Personal Scripts, Next: Using Ghub in Your Own Package, Up: Notes
+
+4.1 Using Ghub in Personal Scripts
+==================================
+
+You can of course use ‘ghub-request’ and its wrapper functions in your
+personal scripts. Unlike when you use Ghub in a package that you
+distribute for others to use, you don’t have to explicitly specify a
+package in personal scripts.
+
+ ;; This is perfectly acceptable in personal scripts ...
+ (ghub-get "/user")
+
+ ;; ... and actually equals to
+ (ghub-get "/user" nil :auth 'ghub)
+
+ ;; In packages you have to specify the package using AUTH.
+ (ghub-get "/user" nil :auth 'some-package)
+
+ When the ‘AUTH’ argument is not specified, then a request is made on
+behalf of the ‘ghub’ package itself. Like for any other package you
+have to create a dedicated token of course.
+
+\1f
+File: ghub.info, Node: Using Ghub in Your Own Package, Next: Forge Limitations and Notes, Prev: Using Ghub in Personal Scripts, Up: Notes
+
+4.2 Using Ghub in Your Own Package
+==================================
+
+Every package should use its own token. This allows you as the author
+of some package to only request access to API scopes that are actually
+needed, which in turn might make it easier for users to trust your
+package not to do unwanted things.
+
+ You have to tell ‘ghub-request’ on behalf of which package a request
+is being made by passing the symbol ‘PACKAGE’ as the value of its ‘AUTH’
+argument.
+
+ (ghub-request "GET" "/user" nil :auth 'PACKAGE)
+
+ Keep in mind that the users of your package will have to manually
+create a suitable token. To make that easier, you should not only link
+to this manual but also prominently mention the scopes the token needs;
+and explain what they are needed for.
+
+\1f
+File: ghub.info, Node: Forge Limitations and Notes, Prev: Using Ghub in Your Own Package, Up: Notes
+
+4.3 Forge Limitations and Notes
+===============================
+
+ • There are no default Gitea and Gogs instances so the variables
+ ‘gitea.host’ and ‘gogs.host’ are not taken into account.
+
+ • Gitea and Gogs do not support limiting a token to certain scopes.
+
+ • The Bitbucket API is fairly broken and my willingness to deal with
+ that is extremely limited unless someone pays me vast amounts of
+ money.
+
+ • The Gitlab API documentation is not always accurate, though I don’t
+ have an example at hand. It also isn’t structured well, making it
+ occasionally difficult to find the information one is looking for.
+
+ • Where one would use ‘user/repo’ when accessing another forge, one
+ has to use ‘user%2Frepo’ when accessing Gitlab, e.g.:
+
+ (glab-get "/projects/python-mode-devs%2Fpython-mode")
+
+\1f
+File: ghub.info, Node: Function Index, Next: Variable Index, Prev: Notes, Up: Top
+
+Appendix A Function Index
+*************************
+
+\0\b[index\0\b]
+* Menu:
+
+* buck-delete: Non-Github Convenience Wrappers.
+ (line 49)
+* buck-get: Non-Github Convenience Wrappers.
+ (line 40)
+* buck-post: Non-Github Convenience Wrappers.
+ (line 46)
+* buck-put: Non-Github Convenience Wrappers.
+ (line 43)
+* buck-request: Non-Github Convenience Wrappers.
+ (line 28)
+* ghub--graphql-vacuum: Making GraphQL Requests.
+ (line 56)
+* ghub-continue: Making REST Requests.
+ (line 125)
+* ghub-delete: Github Convenience Wrappers.
+ (line 31)
+* ghub-fetch-issue: Making GraphQL Requests.
+ (line 105)
+* ghub-fetch-pullreq: Making GraphQL Requests.
+ (line 111)
+* ghub-fetch-repository: Making GraphQL Requests.
+ (line 99)
+* ghub-get: Github Convenience Wrappers.
+ (line 9)
+* ghub-graphql: Making GraphQL Requests.
+ (line 6)
+* ghub-head: Github Convenience Wrappers.
+ (line 6)
+* ghub-patch: Github Convenience Wrappers.
+ (line 28)
+* ghub-post: Github Convenience Wrappers.
+ (line 25)
+* ghub-put: Github Convenience Wrappers.
+ (line 22)
+* ghub-request: Making REST Requests.
+ (line 6)
+* ghub-response-link-relations: Making REST Requests.
+ (line 174)
+* ghub-wait: Github Convenience Wrappers.
+ (line 44)
+* glab-delete: Non-Github Convenience Wrappers.
+ (line 67)
+* glab-get: Non-Github Convenience Wrappers.
+ (line 55)
+* glab-head: Non-Github Convenience Wrappers.
+ (line 52)
+* glab-patch: Non-Github Convenience Wrappers.
+ (line 64)
+* glab-post: Non-Github Convenience Wrappers.
+ (line 61)
+* glab-put: Non-Github Convenience Wrappers.
+ (line 58)
+* glab-request: Non-Github Convenience Wrappers.
+ (line 31)
+* gogs-delete: Non-Github Convenience Wrappers.
+ (line 82)
+* gogs-get: Non-Github Convenience Wrappers.
+ (line 70)
+* gogs-patch: Non-Github Convenience Wrappers.
+ (line 79)
+* gogs-post: Non-Github Convenience Wrappers.
+ (line 76)
+* gogs-put: Non-Github Convenience Wrappers.
+ (line 73)
+* gogs-request: Non-Github Convenience Wrappers.
+ (line 34)
+* gtea-delete: Non-Github Convenience Wrappers.
+ (line 97)
+* gtea-get: Non-Github Convenience Wrappers.
+ (line 85)
+* gtea-patch: Non-Github Convenience Wrappers.
+ (line 94)
+* gtea-post: Non-Github Convenience Wrappers.
+ (line 91)
+* gtea-put: Non-Github Convenience Wrappers.
+ (line 88)
+
+\1f
+File: ghub.info, Node: Variable Index, Prev: Function Index, Up: Top
+
+Appendix B Variable Index
+*************************
+
+\0\b[index\0\b]
+* Menu:
+
+* ghub-response-headers: Making REST Requests.
+ (line 165)
+* github.host: Github Configuration Variables.
+ (line 26)
+* github.HOST.user: Github Configuration Variables.
+ (line 17)
+* github.user: Github Configuration Variables.
+ (line 11)
+
+
+\1f
+Tag Table:
+Node: Top\7f773
+Node: Introduction\7f2243
+Node: Getting Started\7f2648
+Node: Basic Concepts Arguments and Variables\7f3675
+Node: Setting the Username\7f7655
+Ref: Setting your Githubcom Username\7f8143
+Ref: Setting your Gitlabcom Username\7f8499
+Ref: Setting your Github Enterprise Username\7f8965
+Ref: Setting your Username for Other Hosts and/or Forges\7f9698
+Node: Creating and Storing a Token\7f10504
+Ref: Creating a Token\7f10717
+Ref: Storing a Token\7f11628
+Node: Github Configuration Variables\7f13371
+Node: API\7f15082
+Node: Their APIs\7f15320
+Node: Making REST Requests\7f16045
+Node: Making GraphQL Requests\7f25458
+Node: Github Convenience Wrappers\7f32141
+Node: Non-Github Convenience Wrappers\7f35845
+Node: Notes\7f41349
+Node: Using Ghub in Personal Scripts\7f41556
+Node: Using Ghub in Your Own Package\7f42447
+Node: Forge Limitations and Notes\7f43382
+Node: Function Index\7f44355
+Node: Variable Index\7f50004
+\1f
+End Tag Table
+
+\1f
+Local Variables:
+coding: utf-8
+End:
--- /dev/null
+;;; glab.el --- Client library for the Gitlab API -*- lexical-binding:t -*-
+
+;; Copyright (C) 2016-2025 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Glab is a library that provides basic support for using the Gitlab API
+;; from Emacs packages. It abstracts access to API resources using only
+;; a handful of functions that are not resource-specific.
+
+;; This library is implemented on top of Ghub. Unlike Ghub, Glab does
+;; not support the guided creation of tokens because Gitlab lacks the
+;; features that would be necessary to implement that. Users have to
+;; create tokens through the web interface.
+
+;;; Code:
+
+(require 'ghub)
+
+(defconst glab-default-host "gitlab.com/api/v4"
+ "The default host that is used if `glab.host' is not set.")
+
+(cl-defun glab-head (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `HEAD' request for RESOURCE, with optional query PARAMS.
+Like calling `ghub-request' (which see) with \"HEAD\" as METHOD
+and `gitlab' as FORGE."
+ (ghub-request "HEAD" resource params :forge 'gitlab
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun glab-get (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `GET' request for RESOURCE, with optional query PARAMS.
+Like calling `ghub-request' (which see) with \"GET\" as METHOD
+and `gitlab' as FORGE."
+ (ghub-request "GET" resource params :forge 'gitlab
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun glab-put (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PUT\" as METHOD
+and `gitlab' as FORGE."
+ (ghub-request "PUT" resource params :forge 'gitlab
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun glab-post (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `POST' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"POST\" as METHOD
+and `gitlab' as FORGE."
+ (ghub-request "POST" resource params :forge 'gitlab
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun glab-patch (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PATCH' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PATCH\" as METHOD
+and `gitlab' as FORGE."
+ (ghub-request "PATCH" resource params :forge 'gitlab
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun glab-delete (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"DELETE\" as METHOD
+and `gitlab' as FORGE."
+ (ghub-request "DELETE" resource params :forge 'gitlab
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun glab-request (method resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a request for RESOURCE and return the response body.
+Like calling `ghub-request' (which see) with `gitlab' as FORGE."
+ (ghub-request method resource params :forge 'gitlab
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun glab-graphql (graphql &optional variables
+ &key username auth host
+ headers silent
+ callback errorback value extra)
+ "Make a GraphQL request using GRAPHQL and VARIABLES.
+Like calling `ghub-graphql' (which see) with `gitlab' as FORGE."
+ (ghub-graphql graphql variables :forge 'gitlab
+ :username username :auth auth :host host
+ :headers headers :silent silent
+ :callback callback :errorback errorback
+ :value value :extra extra))
+
+(cl-defun glab-repository-id (owner name &key username auth host)
+ "Return the id of the repository specified by OWNER, NAME and HOST."
+ (number-to-string
+ (cdr (assq 'id (glab-get (format "/projects/%s%%2F%s"
+ (string-replace "/" "%2F" owner)
+ name)
+ nil :username username :auth auth :host host)))))
+
+;;; _
+(provide 'glab)
+;;; glab.el ends here
--- /dev/null
+;;; gogs.el --- Client library for the Gogs API -*- lexical-binding:t -*-
+
+;; Copyright (C) 2016-2025 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Gogs is a library that provides basic support for using the Gogs API
+;; from Emacs packages. It abstracts access to API resources using only
+;; a handful of functions that are not resource-specific.
+
+;; This library is implemented on top of Ghub. Unlike Ghub, Gogs does
+;; not support the guided creation of tokens because Gogs lacks the
+;; features that would be necessary to implement that. Users have to
+;; create tokens through the web interface.
+
+;;; Code:
+
+(require 'ghub)
+
+(defconst gogs-default-host "localhost:3000/api/v1"
+ "The default Gogs host.")
+
+;; HEAD does not appear to be supported.
+
+(cl-defun gogs-get (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `GET' request for RESOURCE, with optional query PARAMS.
+Like calling `ghub-request' (which see) with \"GET\" as METHOD
+and `gogs' as FORGE."
+ (declare (obsolete ghub-get "4.3.1"))
+ (ghub-request "GET" resource params :forge 'gogs
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gogs-put (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PUT\" as METHOD
+and `gogs' as FORGE."
+ (declare (obsolete ghub-put "4.3.1"))
+ (ghub-request "PUT" resource params :forge 'gogs
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gogs-post (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `POST' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"POST\" as METHOD
+and `gogs' as FORGE."
+ (declare (obsolete ghub-post "4.3.1"))
+ (ghub-request "POST" resource params :forge 'gogs
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gogs-patch (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PATCH' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PATCH\" as METHOD
+and `gogs' as FORGE."
+ (declare (obsolete ghub-patch "4.3.1"))
+ (ghub-request "PATCH" resource params :forge 'gogs
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gogs-delete (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"DELETE\" as METHOD
+and `gogs' as FORGE."
+ (declare (obsolete ghub-delete "4.3.1"))
+ (ghub-request "DELETE" resource params :forge 'gogs
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gogs-request (method resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a request for RESOURCE and return the response body.
+Like calling `ghub-request' (which see) with `gogs' as FORGE."
+ (declare (obsolete ghub-request "4.3.1"))
+ (ghub-request method resource params :forge 'gogs
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gogs-repository-id (owner name &key username auth host)
+ "Return the id of the repository specified by OWNER, NAME and HOST."
+ (number-to-string
+ (cdr (assq 'id (ghub-get (format "/repos/%s/%s" owner name)
+ nil :forge 'gogs
+ :username username :auth auth :host host)))))
+
+;;; _
+(provide 'gogs)
+;;; gogs.el ends here
--- /dev/null
+;;; gsexp.el --- GraphQl as S-expressions -*- lexical-binding:t -*-
+
+;; Copyright (C) 2016-2025 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This library encodes S-expressions as GraphQl documents. Currently
+;; it is rather incomplete and only intended for use by `ghub-graphql'
+;; but I might fill the gaps and add documentation eventually. I am
+;; not in a hurry though.
+
+;;; Code:
+
+(require 'cl-lib)
+
+(defvar gsexp-one-variable-per-line nil)
+
+(defun gsexp-encode (document)
+ (gsexp--pp (gsexp--encode-field document)))
+
+(defun gsexp--encode-field (field)
+ (cond
+ ((stringp field)
+ field)
+ ((symbolp field)
+ (symbol-name field))
+ ((listp (car field))
+ (concat (format "%s: " (caar field))
+ (gsexp--encode-field (cons (car (cdar field))
+ (cdr field)))))
+ ((concat
+ (pcase (pop field)
+ (`[] "")
+ ((and (pred symbolp) op) (symbol-name op))
+ (`[,op] (symbol-name op))
+ (`[,op ,name] (format "%s %s" op name)))
+ (and (vectorp (car field))
+ (format " (%s%s)"
+ (if gsexp-one-variable-per-line "\n" "")
+ (mapconcat #'gsexp--encode-argument
+ (append (pop field) nil)
+ (if gsexp-one-variable-per-line ",\n" ","))))
+ (and field
+ (format " {\n%s\n}"
+ (mapconcat #'gsexp--encode-field field "\n")))))))
+
+(cl-defun gsexp--encode-argument ((argument value))
+ (format "%s: %s" argument (gsexp--encode-value value)))
+
+(defun gsexp--encode-value (value)
+ (cond
+ ((numberp value)
+ (number-to-string value))
+ ((symbolp value) ; including variables, enums, booleans and null
+ (symbol-name value))
+ ((stringp value)
+ (prin1-to-string value))
+ ((vectorp value)
+ (format "(%s)" (mapconcat #'gsexp--encode-value value "")))
+ ((listp value)
+ (format "{%s}" (mapconcat
+ (pcase-lambda (`(,name ,value))
+ (format "%s: %s" name (gsexp--encode-value value)))
+ value ", ")))
+ ((error "Invalid field value: %S" value))))
+
+(defun gsexp--pp (string)
+ (with-temp-buffer
+ (save-excursion
+ (insert string))
+ (while (< (point) (point-max))
+ (unless (and (bolp) (eolp))
+ (save-excursion
+ (let ((level (car (syntax-ppss (line-beginning-position)))))
+ (when (looking-at "\\s-*\\s)")
+ (cl-decf level))
+ (indent-line-to (* 2 level)))))
+ (forward-line 1))
+ (buffer-string)))
+
+;;; _
+(provide 'gsexp)
+;;; gsexp.el ends here
--- /dev/null
+;;; gtea.el --- Client library for the Gitea API -*- lexical-binding:t -*-
+
+;; Copyright (C) 2016-2025 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <emacs.ghubNOSPAMjonas.bernoulli.dev>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; SPDX-License-Identifier: GPL-3.0-or-later
+
+;; This file 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 file 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 file. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Gtea is a library that provides basic support for using the Gitea API
+;; from Emacs packages. It abstracts access to API resources using only
+;; a handful of functions that are not resource-specific.
+
+;; This library is implemented on top of Ghub. Unlike Ghub, Gtea does
+;; not support the guided creation of tokens because Gitea lacks the
+;; features that would be necessary to implement that. Users have to
+;; create tokens through the web interface.
+
+;;; Code:
+
+(require 'ghub)
+
+(defconst gtea-default-host "localhost:3000/api/v1"
+ "The default Gitea host.")
+
+;; HEAD does not appear to be supported.
+
+(cl-defun gtea-get (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `GET' request for RESOURCE, with optional query PARAMS.
+Like calling `ghub-request' (which see) with \"GET\" as METHOD
+and `gitea' as FORGE."
+ (declare (obsolete ghub-get "4.3.1"))
+ (ghub-request "GET" resource params :forge 'gitea
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gtea-put (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PUT' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PUT\" as METHOD
+and `gitea' as FORGE."
+ (declare (obsolete ghub-put "4.3.1"))
+ (ghub-request "PUT" resource params :forge 'gitea
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gtea-post (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `POST' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"POST\" as METHOD
+and `gitea' as FORGE."
+ (declare (obsolete ghub-post "4.3.1"))
+ (ghub-request "POST" resource params :forge 'gitea
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gtea-patch (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `PATCH' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"PATCH\" as METHOD
+and `gitea' as FORGE."
+ (declare (obsolete ghub-patch "4.3.1"))
+ (ghub-request "PATCH" resource params :forge 'gitea
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gtea-delete (resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a `DELETE' request for RESOURCE, with optional payload PARAMS.
+Like calling `ghub-request' (which see) with \"DELETE\" as METHOD
+and `gitea' as FORGE."
+ (declare (obsolete ghub-delete "4.3.1"))
+ (ghub-request "DELETE" resource params :forge 'gitea
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gtea-request (method resource &optional params
+ &key query payload headers
+ silent unpaginate noerror reader
+ username auth host
+ callback errorback extra)
+ "Make a request for RESOURCE and return the response body.
+Like calling `ghub-request' (which see) with `gitea' as FORGE."
+ (declare (obsolete ghub-request "4.3.1"))
+ (ghub-request method resource params :forge 'gitea
+ :query query :payload payload :headers headers
+ :silent silent :unpaginate unpaginate
+ :noerror noerror :reader reader
+ :username username :auth auth :host host
+ :callback callback :errorback errorback :extra extra))
+
+(cl-defun gtea-repository-id (owner name &key username auth host)
+ "Return the id of the repository specified by OWNER, NAME and HOST."
+ (number-to-string
+ (cdr (assq 'id (ghub-get (format "/repos/%s/%s" owner name)
+ nil :forge 'gitea
+ :username username :auth auth :host host)))))
+
+;;; _
+(provide 'gtea)
+;;; gtea.el ends here