+++ /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). Minuscule client library for the Github API.
+++ /dev/null
-;;; ghub-autoloads.el --- automatically extracted autoloads
-;;
-;;; Code:
-
-(add-to-list 'load-path (directory-file-name
- (or (file-name-directory #$) (car load-path))))
-
-\f
-;;;### (autoloads nil "ghub" "ghub.el" (0 0 0 0))
-;;; Generated autoloads from ghub.el
-
-(autoload 'ghub-create-token "ghub" "\
-Create, store and return a new token.
-
-HOST is the Github instance, usually \"api.github.com\".
-USERNAME is the name of a user on that instance.
-PACKAGE is the package that will use the token.
-SCOPES are the scopes the token is given access to.
-
-\(fn HOST USERNAME PACKAGE SCOPES)" t nil)
-
-(autoload 'ghub-token-scopes "ghub" "\
-Return and echo the scopes of the specified token.
-This is intended for debugging purposes only. The user
-has to provide several values including their password.
-
-\(fn HOST USERNAME PACKAGE)" t nil)
-
-(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+'.
-
-\(fn)" t nil)
-
-(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ghub" '("auth-source-netrc-parse-next-interesting@save-match-data" "ghub-")))
-
-;;;***
-\f
-;;;### (autoloads nil "glab" "glab.el" (0 0 0 0))
-;;; Generated autoloads from glab.el
-
-(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "glab" '("glab-default-host")))
-
-;;;***
-\f
-;;;### (autoloads nil nil ("ghub-graphql.el" "ghub-pkg.el") (0 0
-;;;;;; 0 0))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; coding: utf-8
-;; End:
-;;; ghub-autoloads.el ends here
+++ /dev/null
-;;; ghub-graphql.el --- access Github API using GrapthQL -*- lexical-binding: t -*-
-
-;; Copyright (C) 2016-2018 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
-;; Homepage: https://github.com/magit/ghub
-
-;; This file is not part of GNU Emacs.
-
-;; 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, 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.
-
-;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
-
-;;; Code:
-
-(require 'ghub)
-
-(cl-defun ghub-graphql (graphql &optional variables
- &key username auth host
- 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 (stringp graphql))
- (cl-assert (not (stringp variables)))
- (ghub-request "POST" "/graphql" nil :payload
- (json-encode `(("query" . ,graphql)
- ,@(and variables `(("variables" ,@variables)))))
- :silent silent
- :username username :auth auth :host host
- :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 ($owner:String!, $name:String!) {
- repository(owner:$owner, name:$name) { id }
- }"
- `((owner . ,owner)
- (name . ,name))
- :username username :auth auth :host host)
- .data.repository.id))
-
-;;; _
-(provide 'ghub-graphql)
-;;; ghub-graphql.el ends here
+++ /dev/null
-(define-package "ghub" "20180821.1430" "minuscule client library for the Github API"
- '((emacs "24.4")
- (let-alist "1.0.5"))
- :keywords
- '("tools")
- :authors
- '(("Jonas Bernoulli" . "jonasNOSPAMbernoul.li"))
- :maintainer
- '("Jonas Bernoulli" . "jonasNOSPAMbernoul.li")
- :url "https://github.com/magit/ghub")
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
+++ /dev/null
-;;; ghub.el --- minuscule client library for the Github API -*- lexical-binding: t -*-
-
-;; Copyright (C) 2016-2018 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-;; Package-Requires: ((emacs "24.4") (let-alist "1.0.5"))
-
-;; This file is not part of GNU Emacs.
-
-;; 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, 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.
-
-;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
-
-;;; Commentary:
-
-;; Ghub is a library that provides basic support for using the Github API
-;; from Emacs packages. It abstracts access to API resources using only
-;; a handful of functions that are not resource-specific.
-
-;; Ghub handles the creation, storage and use of access tokens using a
-;; setup wizard to make it easier for users to get started and to reduce
-;; the support burden imposed on package maintainers. It also comes with
-;; a comprehensive manual to address the cases when things don't just
-;; work as expected or in case you don't want to use the wizard.
-
-;; 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 the Github API could be
-;; configured the same way.
-
-;;; Code:
-
-(require 'auth-source)
-(require 'cl-lib)
-(require 'json)
-(require 'let-alist)
-(require 'url)
-(require 'url-auth)
-(require 'url-http)
-
-(eval-when-compile (require 'subr-x))
-
-(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.
-
-The token that is created based on the value of this variable
-is used when `ghub-request' (or one of its wrappers) is called
-without providing a value for AUTH. Packages should always
-identify themselves using that argument, but when you use Ghub
-directly in private tools, then that is not necessary and the
-request is made on behalf of the `ghub' package itself, aka on
-behalf of some private tool.
-
-By default the only requested scope is `repo' because that is
-sufficient as well as required for most common uses. This and
-other scopes are documented at URL `https://magit.vc/goto/2e586d36'.
-
-If your private tools need other scopes, then you have to add
-them here *before* creating the token. Alternatively you can
-edit the scopes of an existing token using the web interface
-at URL `https://github.com/settings/tokens'.")
-
-(defvar ghub-override-system-name nil
- "If non-nil, the string used to identify the local machine.
-If this is nil, then the value returned by `system-name' is
-used instead.")
-
-;;; Request
-;;;; Object
-
-(cl-defstruct (ghub--req
- (:constructor ghub--make-req)
- (:copier nil))
- (url nil :read-only nil)
- (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)
- (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 header 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 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 make one or more
- asynchronous requests and call CALLBACK or ERRORBACK when
- finished. If an error occurred, then call ERRORBACK, or if
- that is nil, then CALLBACK. When no error occurred then call
- CALLBACK. When making asynchronous requests, then no errors
- are signaled, regardless of the value of NOERROR.
-
-Both callbacks are called with four arguments.
- 1. For CALLBACK, the combined value of the retrieved pages.
- For ERRORBACK, the error that occured 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 "https://" host resource
- (and query (concat "?" (ghub--url-encode-params query)))))
- :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
- :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 callbacks 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))
- (or (ghub--retrieve nil req) t)))
-
-(cl-defun ghub-wait (resource &optional duration &key username auth host)
- "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-get resource nil
- :noerror t
- :username username
- :auth auth
- :host host))
- (message "Waited (%3ss of %ss) for %s..." total duration resource)
- (if (= total duration)
- (error "Github is taking too long to create %s" 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 (&optional headers)
- "Return an alist of link relations in HEADERS.
-If optional HEADERS is nil, then return those
-in `ghub-response-headers'."
- (let ((rels (cdr (assoc "Link" (or headers ghub-response-headers)))))
- (and rels (mapcar (lambda (elt)
- (pcase-let ((`(,url ,rel) (split-string elt "; ")))
- (cons (intern (substring rel 5 -1))
- (substring url 1 -1))))
- (split-string rels ", ")))))
-
-;;;; 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)
- ;; When this function has already been called, then it is a
- ;; no-op. Otherwise it sets `url-registered-auth-schemes' among
- ;; other things. If we didn't ensure that it has been run, then
- ;; `url-retrieve-synchronously' would do it, which would cause
- ;; the value that we let-bind below to be overwritten, and the
- ;; "default" value to be lost outside the let-binding.
- (url-do-setup)
- (with-current-buffer
- (let ((url-registered-auth-schemes
- '(("basic" ghub--basic-auth-errorback . 10))))
- (url-retrieve-synchronously url silent))
- (funcall handler (car url-callback-arguments) req)))))
-
-(defun ghub--handle-response (status req)
- (let ((buffer (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 (nconc (ghub--req-value req) payload))
- (next (cdr (assq 'next (ghub-response-link-relations
- headers)))))
- (when (numberp unpaginate)
- (cl-decf unpaginate))
- (setf (ghub--req-url req)
- (url-generic-parse-url next))
- (setf (ghub--req-value req) value)
- (setf (ghub--req-unpaginate req) unpaginate)
- (or (and next
- unpaginate
- (or (eq unpaginate t)
- (> unpaginate 0))
- (ghub-continue req))
- (let ((callback (ghub--req-callback req))
- (errorback (ghub--req-errorback req))
- (err (plist-get status :error)))
- (cond ((and err errorback)
- (funcall errorback err headers status req))
- (callback
- (funcall callback value headers status req))
- (t value))))))
- (when (buffer-live-p buffer)
- (kill-buffer buffer)))))
-
-(defun ghub--handle-response-headers (status req)
- (goto-char (point-min))
- (forward-line 1)
- (let (headers)
- (while (re-search-forward "^\\([^:]*\\): \\(.+\\)"
- url-http-end-of-headers t)
- (push (cons (match-string 1)
- (match-string 2))
- headers))
- (setq headers (nreverse headers))
- (unless url-http-end-of-headers
- (error "BUG: missing headers %s" (plist-get status :error)))
- (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)
- (let ((noerror (ghub--req-noerror req))
- (err (plist-get status :error)))
- (if err
- (if noerror
- (if (eq noerror 'return)
- payload
- (setcdr (last err) (list payload))
- nil)
- (ghub--signal-error err payload))
- payload)))
-
-(defun ghub--signal-error (err &optional payload)
- (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))
- payload)))
- (signal 'ghub-error data))
- (signal symb data))))
-
-(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
- (let ((json-object-type 'alist)
- (json-array-type 'list)
- (json-key-type 'symbol)
- (json-false nil)
- (json-null nil))
- (json-read-from-string raw))
- (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 (json-encode-list 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)) "="
- (if (integerp val)
- (number-to-string val)
- (url-hexify-string val)))))
- params "&"))
-
-;;; Authentication
-;;;; API
-
-;;;###autoload
-(defun ghub-create-token (host username package scopes)
- "Create, store and return a new token.
-
-HOST is the Github instance, usually \"api.github.com\".
-USERNAME is the name of a user on that instance.
-PACKAGE is the package that will use the token.
-SCOPES are the scopes the token is given access to."
- (interactive
- (pcase-let ((`(,host ,username ,package)
- (ghub--read-triplet)))
- (list host username package
- (split-string
- (read-string
- "Scopes (separated by commas): "
- (mapconcat #'symbol-name
- (symbol-value
- (intern (format "%s-github-token-scopes" package)))
- ","))
- "," t "[\s\t]+"))))
- (let ((user (ghub--ident username package)))
- (cl-destructuring-bind (save token)
- (ghub--auth-source-get (list :save-function :secret)
- :create t :host host :user user
- :secret
- (cdr (assq 'token
- (ghub-post
- "/authorizations"
- `((scopes . ,scopes)
- (note . ,(ghub--ident-github package)))
- :username username :auth 'basic :host host))))
- ;; Build-in back-ends return a function that does the actual
- ;; saving, while for some third-party back-ends ":create t"
- ;; is enough.
- (when (functionp save)
- (funcall save))
- ;; If the Auth-Source cache contains the information that there
- ;; is no value, then setting the value does not invalidate that
- ;; now incorrect information.
- (auth-source-forget (list :host host :user user))
- token)))
-
-;;;###autoload
-(defun ghub-token-scopes (host username package)
- "Return and echo the scopes of the specified token.
-This is intended for debugging purposes only. The user
-has to provide several values including their password."
- (interactive (ghub--read-triplet))
- (let ((scopes
- (cdr (assq 'scopes (ghub--get-token-plist host username package)))))
- (when (called-interactively-p 'any)
- ;; Also show the input values to make it easy for package
- ;; authors to verify that the user has done it correctly.
- (message "Scopes for %s@%s: %S"
- (ghub--ident username package)
- host scopes))
- scopes))
-
-;;;###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)
- (if (eq forge 'gitlab)
- (error "Gitlab does not support basic authentication")
- (cons (cons "Authorization" (ghub--basic-auth host username))
- headers))
- (cons (ghub--auth host auth username forge) headers)))))
-
-(defun ghub--auth (host auth &optional username forge)
- (unless username
- (setq username (ghub--username host)))
- (if (eq auth 'basic)
- (if (eq forge 'gitlab)
- (error "Gitlab does not support basic authentication")
- (cons "Authorization" (ghub--basic-auth host username)))
- (cons (if (eq forge 'gitlab)
- "Private-Token"
- "Authorization")
- (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 (concat "https://" host))))
- (setf (url-user url) username)
- (url-basic-auth url t)))
-
-(defun ghub--basic-auth-errorback (url &optional prompt _overwrite _realm _args)
- ;; This gets called twice. Do nothing the first time,
- ;; when PROMPT is nil. See `url-get-authentication'.
- (when prompt
- (if (assoc "X-GitHub-OTP" (ghub--handle-response-headers nil nil))
- (progn
- (setq url-http-extra-headers
- `(("Content-Type" . "application/json")
- ("X-GitHub-OTP" . ,(ghub--read-2fa-code))
- ;; Without "Content-Type" and "Authorization".
- ;; The latter gets re-added from the return value.
- ,@(cddr url-http-extra-headers)))
- ;; Return the cached values, they are correct.
- (url-basic-auth url nil nil nil))
- ;; Remove the invalid cached values and fail, which
- ;; is better than the invalid values sticking around.
- (setq url-http-real-basic-auth-storage
- (cl-delete (format "%s:%d" (url-host url) (url-port url))
- url-http-real-basic-auth-storage
- :test #'equal :key #'car))
- nil)))
-
-(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. See #24, #64.
- (auth-source-forget (list :host host :user user :max 1))
- (and (not nocreate)
- (if (eq forge 'gitlab)
- (error
- (concat
- "Required Gitlab token does not exist. See "
- "https://magit.vc/manual/ghub/Gitlab-Support.html "
- "for instructions."))
- (ghub--confirm-create-token host username package)))))))
- (if (functionp token) (funcall token) token)))
-
-(defun ghub--host (&optional forge)
- (if (eq forge 'gitlab)
- (or (ignore-errors (car (process-lines "git" "config" "gitlab.host")))
- (bound-and-true-p glab-default-host))
- (or (ignore-errors (car (process-lines "git" "config" "github.host")))
- ghub-default-host)))
-
-(defun ghub--username (host &optional forge)
- (let ((var (cond ((string-prefix-p "api.github.com" host) "github.user")
- ((string-prefix-p "gitlab.com/api" host) "gitlab.user")
- ((eq forge 'gitlab) (format "gitlab.%s.user" host))
- (t (format "github.%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))))
- (or (and user (progn (call-process "git" nil nil nil
- "config" "--global" var user)
- user))
- (user-error "Abort")))))))
-
-(defun ghub--ident (username package)
- (format "%s^%s" username package))
-
-(defun ghub--ident-github (package)
- (format "Emacs package %s @ %s"
- package
- (or ghub-override-system-name (system-name))))
-
-(defun ghub--package-scopes (package)
- (let ((var (intern (format "%s-github-token-scopes" package))))
- (if (boundp var)
- (symbol-value var)
- (error "%s fails to define %s" package var))))
-
-(defun ghub--confirm-create-token (host username package)
- (let* ((ident (ghub--ident-github package))
- (scopes (ghub--package-scopes package))
- (max-mini-window-height 40))
- (if (let ((message-log-max nil))
- (yes-or-no-p
- (format
- "Such a Github API token is not available:
-
- Host: %s
- User: %s
- Package: %s
-
- Scopes requested in `%s-github-token-scopes':\n%s
- Store on Github as:\n %S
- Store locally according to option `auth-sources':\n %S
-%s
-If in doubt, then abort and first view the section of the Ghub
-documentation called \"Manually Creating and Storing a Token\".
-
-Otherwise confirm and then provide your Github username and
-password at the next two prompts. Depending on the backend
-you might have to provide a passphrase and confirm that you
-really want to save the token.
-
-Create and store such a token? "
- host username package package
- (mapconcat (lambda (scope) (format " %s" scope)) scopes "\n")
- ident auth-sources
- (if (and (stringp (car auth-sources))
- (not (string-suffix-p ".gpg" (car auth-sources))))
- (format "
-WARNING: The token will be stored unencrypted in %S.
- If you don't want that, you have to abort and customize
- the `auth-sources' option.\n" (car auth-sources))
- ""))))
- (progn
- (when (ghub--get-token-id host username package)
- (if (yes-or-no-p
- (format
- "A token named %S\nalready exists on Github. Replace it?"
- ident))
- (ghub--delete-token host username package)
- (user-error "Abort")))
- (ghub-create-token host username package scopes))
- (user-error "Abort"))))
-
-(defun ghub--get-token-id (host username package)
- (let ((ident (ghub--ident-github package)))
- (cl-some (lambda (x)
- (let-alist x
- (and (equal .app.name ident) .id)))
- (ghub-get "/authorizations"
- '((per_page . 100))
- :unpaginate t
- :username username :auth 'basic :host host))))
-
-(defun ghub--get-token-plist (host username package)
- (ghub-get (format "/authorizations/%s"
- (ghub--get-token-id host username package))
- nil :username username :auth 'basic :host host))
-
-(defun ghub--delete-token (host username package)
- (ghub-delete (format "/authorizations/%s"
- (ghub--get-token-id host username package))
- nil :username username :auth 'basic :host host))
-
-(defun ghub--read-triplet ()
- (let ((host (read-string "Host: " (ghub--host))))
- (list host
- (read-string "Username: " (ghub--username host))
- (intern (read-string "Package: " "ghub")))))
-
-(defvar ghub--2fa-cache nil)
-
-(defun ghub--read-2fa-code ()
- (let ((code (read-number "Two-factor authentication code: "
- (and ghub--2fa-cache
- (< (float-time (time-subtract
- (current-time)
- (cdr ghub--2fa-cache)))
- 25)
- (car ghub--2fa-cache)))))
- (setq ghub--2fa-cache (cons code (current-time)))
- (number-to-string code)))
-
-(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)))
-
-(advice-add 'auth-source-netrc-parse-next-interesting :around
- 'auth-source-netrc-parse-next-interesting@save-match-data)
-(defun auth-source-netrc-parse-next-interesting@save-match-data (fn)
- "Save match-data for the benefit of caller `auth-source-netrc-parse-one'.
-Without wrapping this function in `save-match-data' the caller
-won't see the secret from a line that is followed by a commented
-line."
- (save-match-data (funcall fn)))
-
-;;; _
-(provide 'ghub)
-(require 'ghub-graphql)
-;;; ghub.el ends here
+++ /dev/null
-This is ghub.info, produced by makeinfo version 6.5 from ghub.texi.
-
- Copyright (C) 2017-2018 Jonas Bernoulli <jonasNOSPAMbernoul.li>
-
- 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). Minuscule 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 a library that provides basic support for using the Github API
-from Emacs packages. It abstracts access to API resources using only a
-handful of functions that are not resource-specific.
-
- Ghub handles the creation, storage and use of access tokens using a
-setup wizard to make it easier for users to get started and to reduce
-the support burden imposed on package maintainers. It also comes with a
-comprehensive manual to address the cases when things don’t just work as
-expected or in case you don’t want to use the wizard.
-
- 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 the Github API could be configured the
-same way.
-
-This manual is for Ghub version 2.0.1 (v2.0.1-10-g85b5ae3+1).
-
- Copyright (C) 2017-2018 Jonas Bernoulli <jonasNOSPAMbernoul.li>
-
- 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::
-* Using Ghub in Personal Scripts::
-* Using Ghub in a Package::
-* API::
-* Gitlab Support::
-
-— The Detailed Node Listing —
-
-Getting Started
-
-* Setting the Username::
-* Interactively Creating and Storing a Token::
-* Manually Creating and Storing a Token::
-* How Ghub uses Auth-Source::
-
-API
-
-* Making Requests::
-* Authentication::
-* Configuration Variables::
-
-
-\1f
-File: ghub.info, Node: Introduction, Next: Getting Started, Prev: Top, Up: Top
-
-1 Introduction
-**************
-
-Ghub is a library that provides basic support for using the Github API
-from Emacs packages. It abstracts access to API resources using only a
-handful of functions that are not resource-specific.
-
- Ghub handles the creation, storage and use of access tokens using a
-setup wizard to make it easier for users to get started and to reduce
-the support burden imposed on package maintainers. It also comes with a
-comprehensive manual to address the cases when things don’t just work as
-expected or in case you don’t want to use the wizard.
-
- 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 the Github API could be configured the
-same way.
-
- Fancier interfaces can be implemented on top of Ghub, and one such
-wrapper — named simply Ghub+ — has already been implemented. The
-benefit of basing various opinionated interfaces on top of a single
-library that provides only the core functionality is that choosing the
-programming interface no longer dictates how access tokens are handled.
-Users can then use multiple packages that access the Github API without
-having to learn the various incompatible ways packages expect the
-appropriate token to be made available to them.
-
- Ghub uses the built-in ‘auth-source’ library to store access tokens.
-That library is very flexible and supports multiple backends, which
-means that it is up to the user how secrets are stored. They can, among
-other things, choose between storing secrets in plain text for ease of
-use, or encrypted for better security.
-
- Previously (as in until this library is widely adopted) it was up to
-package authors to decide if things should be easy or secure. (Note
-that ‘auth-source’ defaults to "easy" — you have been warned.)
-
- Ghub expects package authors to use a dedicated access token instead
-of sharing a single token between all packages that rely on it. That
-means that users cannot configure Ghub once and later start using a new
-package without any additional setup. But Ghub helps with that.
-
- When the user invokes some command that ultimately results in
-‘ghub-request’ being called and the appropriate token is not available
-yet, then the user is guided through the process of creating and storing
-a new token, and at the end of that process the request is carried out
-as if the token had been available to begin with.
-
-\1f
-File: ghub.info, Node: Getting Started, Next: Using Ghub in Personal Scripts, Prev: Introduction, Up: Top
-
-2 Getting Started
-*****************
-
-Each package that uses Ghub uses its own token. Despite that, chances
-are good that after successfully configuring one package you can just
-start using another package pretty much instantly.
-
- If the necessary token is not available when a package makes an API
-request, then a setup wizard pops up, and after answering a few
-questions you are good to go. Even the request that caused the wizard
-to be summoned should succeed and for most users this should be true
-even when configuring the very first token.
-
- However, in some situations some manual configuration is necessary
-*before* using the wizard, or the wizard cannot be used at all:
-
- • If you don’t want to use the wizard then you don’t have to and can
- create tokens manually as described in *note Manually Creating and
- Storing a Token::.
-
- • If you want to access Gitlab.com or another Gitlab instance, then
- you have to create the token manually as describe in *note Manually
- Creating and Storing a Token::. Also see *note Gitlab Support::.
-
- • If you want to access a Github Enterprise instance, then you have
- to tell Ghub about that before the wizard makes its appearance by
- setting the Git variable ‘github.host’. You also have to tell Ghub
- your username for that instance using the variable
- ‘github.HOST.user’ even if it is the same as on Github.com.
-
- • If the variable ‘github.user’ (or ‘github.HOST.user’ for an
- Enterprise instance) is unset when the wizard is first summoned,
- then you are asked to provide your username. That value is then
- stored *globally* to avoid having to ask you that question once per
- repository. If you have multiple accounts on Github.com (or an
- Enterprise instance), then you have to explicitly tell Ghub about
- that. This can be done by setting the repository-local values of
- the appropriate variable *before* the wizard is invoked.
-
- • You might forget to do the above, which is why it is important to
- carefully read the output of the wizard. If it turns out that you
- forgot to set a variable, then you must abort, set the variable,
- and repeat the request to trigger the wizard again.
-
- • The setup wizard should work even if you have enabled two-factor
- authentication. However if your Github Enterprise instance
- enforces Single Sign-On as an additional security measure, then you
- are out of luck and have to create the token manually as described
- in *note Manually Creating and Storing a Token::.
-
- The variables mentioned above — and others — are documented in *note
-Configuration Variables:: and the setup wizard is documented in *note
-Interactively Creating and Storing a Token::.
-
-* Menu:
-
-* Setting the Username::
-* Interactively Creating and Storing a Token::
-* Manually Creating and Storing a Token::
-* How Ghub uses Auth-Source::
-
-\1f
-File: ghub.info, Node: Setting the Username, Next: Interactively Creating and Storing a Token, Up: Getting Started
-
-2.1 Setting the Username
-========================
-
-If you haven’t set the Git variable ‘github.user’ yet when making a
-request, then you will be asked:
-
- Git variable `github.user' is unset. Set to:
-
- You are expected to provide your Github username here. The provided
-value will be saved globally (using ‘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, *before* making a request:
-
- cd /path/to/repo
- git config github.user USERNAME
-
- For Github Enterprise instances you have to specify where the API can
-be accessed *before* you try to access it 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:
-
- # Do this once
- git config --global github.example.com/api/v3.user EMPLOYEE
-
- # Do this for every corporate repository
- cd /path/to/repo
- git config github.host example.com/api/v3
-
- If you do not set ‘github.example.com/api/v3.user’, then you will be
-asked to provide the value when trying to make a request, but you do
-have to manually set ‘github.host’, or Ghub assumes that you are trying
-to access ‘api.github.com’.
-
-\1f
-File: ghub.info, Node: Interactively Creating and Storing a Token, Next: Manually Creating and Storing a Token, Prev: Setting the Username, Up: Getting Started
-
-2.2 Interactively Creating and Storing a Token
-==============================================
-
-Ghub uses a different token for every package as well as for every
-machine from which you access the Github API (and obviously also for
-every Github instance and user). This allows packages to only request
-the scopes that they actually need and also gives users the opportunity
-to refuse access to certain scopes if they expect to not use the
-features that need them.
-
- Usually you don’t have to worry about creating and storing a token
-yourself and can just make a request. Note however that you don’t have
-to use the setup wizard described below. Alternatively you can perform
-the setup manually as described in the next section.
-
- If you make a request and the required token is not available yet,
-then the setup wizard will first ask you something like this:
-
- Such a Github API token is not available:
-
- Host: api.github.com
- User: USERNAME
- Package: PACKAGE
-
- Scopes requested in `PACKAGE-github-token-scopes':
- repo
- Store on Github as:
- "Emacs package PACKAGE @ LOCAL-MACHINE"
- Store locally according to option `auth-sources':
- ("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")
-
- If in doubt, then abort and first view the section of the Ghub
- documentation called "Manually Creating and Storing a Token".
-
- Create and store such a token? (yes or no)
-
- If you don’t have any doubts, then answer "yes". Lets address some
-of the doubts that you might have:
-
- • ‘Host’ usually is "api.github.com" and that is usually what you
- want. If you are trying to access a Github Enterprise instance,
- then it should be something else and you have to set the value
- manually before the setup wizard is summoned, as described in the
- parent section.
-
- • ‘User’ should be your Github.com (or Github Enterprise instance)
- username. If it is something else and it doesn’t look like a
- simple typo, then you should read the parent section again. In
- either case you have to abort.
-
- • ‘Package’ should be the name of the package you are using to access
- the Github API.
-
- If it is ‘ghub’, then the package author disregarded that
- convention and you should probably report a bug in the issue
- tracker of that package.
-
- Or you yourself are using ‘ghub-request’ or one of its wrappers
- directly, in which case this is expected and perfectly fine. In
- that case you might however want to abort and change the value of
- the variable ‘ghub-github-token-scopes’ before triggering the
- wizard again.
-
- • Each ‘PACKAGE’ has to specify the tokens that it needs using a
- variable named ‘PACKAGE-github-token-scopes’. The doc-string of
- that variable should document why the various scopes are needed.
-
- The meaning of the various scopes are documented at
- <https://magit.vc/goto/f63aeb0a>.
-
- • The value of ‘auth-sources’ is shown. The default value causes
- secrets to be stored in plain text. Because this might be
- unexpected, Ghub additionally displays a warning when appropriate.
-
- WARNING: The token will be stored unencrypted in "~/.authinfo".
- If you don't want that, you have to abort and customize
- the `auth-sources' option.\n\n" (car auth-sources))
-
- Whether that is something that needs fixing, is up to you. If your
- answer is yes, then you should abort and see *note How Ghub uses
- Auth-Source:: for instructions on how to save the token more
- securely.
-
- • When creating a token it is necessary to provide a token
- description. Ghub uses descriptions that have the form "Emacs
- package PACKAGE @ LOCAL-MACHINE".
-
- Github uses the token description to identify the token, not merely
- as something useful to humans. Token descriptions therefore have
- to be unique and in rare cases you get an additional prompt, asking
- you something like:
-
- A token named "Emacs package PACKAGE @ LOCAL-MACHINE"
- already exists on Github. Replace it?
-
- You might see this message when you have lost the old token and
- want to replace it with a new one, in which case you should
- obviously just proceed.
-
- Or two of your computers have the same hostname, which is bad
- practice because it gains you nothing but leads to issues such as
- this. Or you are dual-booting on this machine and use the same
- hostname in all operating systems, which is a somewhat reasonable
- thing to do, but never-the-less leads to issues like this.
-
- In either case you will have to use something other than the value
- returned by ‘system-name’ to identify the current machine or
- operating system. Or you can continue to identify different things
- using the same identifier, in which case you have to manually
- distribute the token.
-
- The former is recommended and also easier to do, using the variable
- ‘ghub-override-system-name’. See *note Configuration Variables::
- for details.
-
- After the above prompt you are also asked for you username and
-password. If you have enabled two-factor authentication, then you also
-have to provide the authentication code at least twice. If you make
-sure the code is still good for a while when asked for it first, then
-you can just press ‘RET’ at the later prompt(s).
-
-\1f
-File: ghub.info, Node: Manually Creating and Storing a Token, Next: How Ghub uses Auth-Source, Prev: Interactively Creating and Storing a Token, Up: Getting Started
-
-2.3 Manually Creating and Storing a Token
-=========================================
-
-If you cannot or don’t want to use the wizard then you have to (1)
-figure out what scopes a package wants, (2) create such a token using
-the web interface and (3) store the token where Ghub expects to find it.
-
- A package named ‘PACKAGE’ has to specify the scopes that it wants in
-the variable named ‘PACKAGE-ghub-token-scopes’. The doc-string of such
-variables should document what the various scopes are needed for.
-
- To create or edit a token go to <https://github.com/settings/tokens>.
-For Gitlab.com use <https://gitlab.com/profile/personal_access_tokens>.
-
- Finally store the token in a place where Ghub looks for it, as
-described in *note How Ghub uses Auth-Source::.
-
- If you store the token in a file like ‘~/.authinfo’, then note that
-‘auth-source’’s parsing of that file is brittle. Make sure the file
-ends with a newline character, that there are no empty or invalid lines,
-and that all comments are prefixed with ‘#’.
-
-\1f
-File: ghub.info, Node: How Ghub uses Auth-Source, Prev: Manually Creating and Storing a Token, Up: Getting Started
-
-2.4 How Ghub uses Auth-Source
-=============================
-
-Please see *note (auth)Top:: for all the gory details about Auth-Source.
-Some Ghub-specific information and important notes follow.
-
- The variable ‘auth-sources’ controls how and where Auth-Source stores
-new secrets and where it looks for known secrets. The default value is
-‘("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")’, which means that it
-looks in all of these files in order to find secrets and that it stores
-new secrets in ‘~/.authinfo’ because that is the first element of the
-list. It doesn’t matter which files already do or don’t exist when
-storing a new secret, the first file is always used.
-
- Secrets are stored in ‘~/.authinfo’ in plain text. If you don’t want
-that (good choice), then you have to customize ‘auth-sources’, e.g. by
-flipping the positions of the first two elements.
-
- Auth-Source also supports storing secrets in various key-chains.
-Refer to its documentation for more information.
-
- Some 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 username is "ziggy",the package is named "stardust",
-and you want to access *Github.com* an entry in one of the three
-mentioned files would then look like this:
-
- machine api.github.com login ziggy^stardust password 012345abcdef...
-
- Assuming your username is "ziggy",the package is named "stardust",
-and you want to access *Gitlab.com* an entry in one of the three
-mentioned files would then look like this:
-
- machine gitlab.com/api/v4 login ziggy^stardust password 012345abcdef...
-
-\1f
-File: ghub.info, Node: Using Ghub in Personal Scripts, Next: Using Ghub in a Package, Prev: Getting Started, Up: Top
-
-3 Using Ghub in Personal Scripts
-********************************
-
-You can use ‘ghub-request’ and its wrapper functions in your personal
-scripts, of course. Unlike when you use Ghub from a package that you
-distribute for others to use, you don’t have to specify a package in
-personal scripts.
-
- ;; This is perfectly acceptable in personal scripts ...
- (ghub-get "/user")
-
- ;; ... and actually equal to
- (ghub-get "/user" nil :auth 'ghub)
-
- ;; In packages you have to specify the package using AUTH.
- (ghub-get "/user" nil :auth 'foobar)
-
- When you do not specify the ‘AUTH’ argument, then a request is made
-on behalf of the ‘ghub’ package itself. Like for any package that uses
-Ghub, ‘ghub’ has to declare what scopes it needs, using, in this case,
-the variable ‘ghub-github-token-scopes’.
-
- The default value of that variable is ‘(repo)’ and you might want to
-add additional scopes. You can later add additional scopes to an
-existing token, using the web interface at
-<https://github.com/settings/tokens>.
-
- If you do that, then you might want to also set the variable
-accordingly, but note that Ghub only consults that when *creating* a new
-token. If you want to know a token’s effective scopes use the command
-‘ghub-token-scopes’, described in the next section.
-
-\1f
-File: ghub.info, Node: Using Ghub in a Package, Next: API, Prev: Using Ghub in Personal Scripts, Up: Top
-
-4 Using Ghub in a 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.
-
- The scopes used by ‘PACKAGE’ have to be defined using the variable
-‘PACKAGE-github-token-scopes’, and 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)
-
- -- Variable: PACKAGE-github-token-scopes
-
- This variable defines the token scopes requested by the package
- named ‘PACKAGE’. The doc-string should explain what the various
- scopes are needed for to prevent users from giving ‘PACKAGE’ fewer
- permissions than it absolutely needs and also to give them greater
- confidence that ‘PACKAGE’ is only requesting the permissions that
- it actually needs.
-
- The value of this variable does not necessarily correspond to the
- scopes that the respective token actually gives access to. There
- is nothing that prevents users from changing the value *after*
- creating the token or from editing the token’s scopes later on.
-
- So it is pointless to check the value of this variable before
- making a request. You also should not query the API to reliably
- determine the supported tokens before making a query. Doing the
- latter would mean that every request becomes two requests and that
- the first request would have to be done using the user’s password
- instead of a token.
-
- -- Command: ghub-token-scopes
-
- Because we cannot be certain that the user hasn’t messed up the
- scopes, Ghub provides this command to make it easy to debug such
- issues without having to rely on users being thoughtful enough to
- correctly determine the used scopes manually.
-
- Just tell users to run ‘M-x ghub-token-scopes’ and to provide the
- correct values for the ‘HOST’, ‘USERNAME’ and ‘PACKAGE’ when
- prompted, and to then post the output.
-
- It is to be expected that users will occasionally mess that up so
- this command outputs not only the scopes but also the user input so
- that you can have greater confidence in the validity of the user’s
- answer.
-
- Scopes for USERNAME^PACKAGE@HOST: (SCOPE...)
-
-\1f
-File: ghub.info, Node: API, Next: Gitlab Support, Prev: Using Ghub in a Package, Up: Top
-
-5 API
-*****
-
-This section describes the Ghub API. In other words it describes the
-public functions and variables provided by the Ghub library and not the
-Github API that can be accessed by using those functions. The latter is
-documented at <https://developer.github.com/v3>.
-
-* Menu:
-
-* Making Requests::
-* Authentication::
-* Configuration Variables::
-
-\1f
-File: ghub.info, Node: Making Requests, Next: Authentication, Up: API
-
-5.1 Making 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 header is 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 make 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 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.
-
- 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 an error
- occurred, then it calls ‘ERRORBACK’, or if that is ‘nil’, then
- ‘CALLBACK’. When no error occurred then it calls ‘CALLBACK’.
- When making asynchronous requests, then no errors are
- signaled, regardless of the value of ‘NOERROR’.
-
- Both callbacks are called with four arguments.
-
- • For ‘CALLBACK’, the combined value of the retrieved
- pages. For ‘ERRORBACK’, the error that occured when
- retrieving the last page.
-
- • The headers of the last page as an alist.
-
- • Status information provided by ‘url-retrieve’. Its
- ‘:error’ property holds the same information as the first
- argument to ‘ERRORBACK’.
-
- • 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 retrieve 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 callbacks 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 headers
-
- This function returns an alist of the link relations in ‘HEADERS’,
- or if optional ‘HEADERS’ is nil, then those in
- ‘ghub-response-headers’.
-
- -- Variable: ghub-override-system-name
-
- If non-nil, the value of this variable is used to override the
- value returned by ‘system-name’ for the purpose of identifying the
- local machine, which is necessary because Ghub uses separate tokens
- for each machine. Also see *note Configuration Variables::.
-
- -- Variable: ghub-github-token-scopes
- -- Variable: PACKAGE-github-token-scopes
-
- Such a variable defines the token scopes requested by the
- respective package ‘PACKAGE’ given by the first word in the
- variable name. ‘ghub’ itself is treated like any other package.
- Also see *note Using Ghub in a Package::.
-
- -- 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 the timeout exceeds. 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.
-
- -- Function: ghub-graphql graphql &optional variables &key username
- auth host callback
-
- 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.
-
-\1f
-File: ghub.info, Node: Authentication, Next: Configuration Variables, Prev: Making Requests, Up: API
-
-5.2 Authentication
-==================
-
- -- Command: ghub-create-token
-
- This command creates a new token using the values it reads from the
- user and then stores it according to the variable ‘auth-sources’.
- It can also be called non-interactively, but you shouldn’t do that
- yourself.
-
- This is useful if you want to fully setup things before attempting
- to make the initial request, if you want to provide fewer than the
- requested scopes or customize ‘auth-sources’ first, or if something
- has gone wrong when using the wizard that is used when making a
- request without doing this first. (Note that instead of using this
- command you can also just repeat the initial request after making
- the desired adjustments — that is easier.)
-
- This command reads, in order, the ‘HOST’ (Github instance), the
- ‘USERNAME’, the ‘PACKAGE’, and the ‘SCOPES’ in the minibuffer,
- providing reasonable default choices. ‘SCOPES’ defaults to the
- scopes that ‘PACKAGE’ requests using the variable
- ‘PACKAGE-github-token-scopes’.
-
- -- Command: ghub-token-scopes
-
- Users are free to give a token access to fewer scopes than what the
- respective package requested. That can, of course, lead to issues,
- and package maintainers have to be able to quickly determine if
- such a (mis-)configuration is the root cause when users report
- issues.
-
- This command reads the required values in the minibuffer and then
- shows a message containing these values along with the scopes of
- the respective token. It also returns the scopes (only) when
- called non-interactively. Also see *note Using Ghub in a
- Package::.
-
-\1f
-File: ghub.info, Node: Configuration Variables, Prev: Authentication, Up: API
-
-5.3 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.)
-
- -- Variable: ghub-override-system-name
-
- Ghub uses a different token for each quadruple ‘(USERNAME PACKAGE
- HOST LOCAL-MACHINE)’. Theoretically it could reuse tokens to some
- extent but that would be more difficult to implement, less
- flexible, and less secure (though slightly more convenient).
-
- A token is identified on the respective Github instance (Github.com
- or a Github Enterprise instance) using the pair ‘(PACKAGE .
- LOCAL-MACHINE)’, or more precisely the string "Emacs package
- PACKAGE @ LOCAL-MACHINE". ‘USERNAME’ and ‘HOST’ do not have to be
- encoded because the token is stored for ‘USERNAME’ on ‘HOST’ and
- cannot be used by another user and/or on another instance.
-
- There is one potential problem though; for any given ‘(PACKAGE .
- LOCAL-MACHINE)’ there can only be one token identified by "Emacs
- package PACKAGE @ LOCAL-MACHINE"; Github does not allow multiple
- tokens with the same description because it uses the description as
- the identifier (it could use some hash instead, but alas it does
- not).
-
- If you have multiple machines and some of them have the same name,
- then you should probably change that as this is not how things
- ought to be. However if you dual-boot, then it might make sense to
- give that machine the same name regardless of what operating system
- you have booted into.
-
- You could use the same token on both operating systems, but setting
- that up might be somewhat difficult because it is not possible to
- download an existing token from Github. You could, of course,
- locally copy the token, but that is inconvenient and would make it
- harder to only revoke the token used on your infected Windows
- installation without also revoking it for your totally safe *BSD
- installation.
-
- Alternatively you can set this variable to a unique value, that
- will then be used to identify the local machine instead of the
- value returned by ‘system-name’.
-
-\1f
-File: ghub.info, Node: Gitlab Support, Prev: API, Up: Top
-
-6 Gitlab Support
-****************
-
-Support for Gitlab.com and other Gitlab instances is implemented in the
-library ‘glab.el’. This library is build on top of ‘ghub.el’ and is
-maintained in the same repository, but it is distributed as a separate
-package.
-
- When accessing Gitlab.com or another Gitlab instance, use
-‘glab-request’ instead of ‘ghub-request’, ‘glab-get’ instead of
-‘ghub-get’, etc. Likewise use the Git variables in the ‘gitlab’ group
-instead of those in the ‘github’ group, i.e. ‘gitlab.user’,
-‘gitlab.HOST.user’ and ‘gitlab.host’.
-
- The Gitlab API cannot be used to create tokens, so Glab cannot
-provide a setup wizard like Ghub does. As a consequence, if the user
-makes a request and the necessary token cannot be found, then that
-results in an error.
-
- You have to manually create and store the necessary tokens. Tokens
-can be created at <https://gitlab.com/profile/personal_access_tokens>,
-or the equivalent URL for another Gitlab instance. To store the token
-locally, follow the instructions in *note Manually Creating and Storing
-a Token:: and *note How Ghub uses Auth-Source::.
-
- Packages that use Glab can define ‘PACKAGE-gitlab-token-scopes’ for
-documentation purposes. But unlike ‘PACKAGE-github-token-scopes’, which
-is used by the setup wizard, this is optional.
-
- And a random hint: where you would use ‘user/repo’ when accessing
-Github, you have to use ‘user%2Frepo’ when accessing Gitlab, e.g.:
-
- (glab-get "/projects/python-mode-devs%2Fpython-mode")
-
-
-\1f
-Tag Table:
-Node: Top\7f763
-Node: Introduction\7f2838
-Node: Getting Started\7f5562
-Node: Setting the Username\7f8620
-Node: Interactively Creating and Storing a Token\7f10045
-Node: Manually Creating and Storing a Token\7f15710
-Node: How Ghub uses Auth-Source\7f16933
-Node: Using Ghub in Personal Scripts\7f18866
-Node: Using Ghub in a Package\7f20322
-Node: API\7f22940
-Node: Making Requests\7f23391
-Node: Authentication\7f37080
-Node: Configuration Variables\7f38925
-Node: Gitlab Support\7f42645
-\1f
-End Tag Table
-
-\1f
-Local Variables:
-coding: utf-8
-End:
+++ /dev/null
-;;; glab.el --- minuscule client library for the Gitlab API -*- lexical-binding: t -*-
-
-;; Copyright (C) 2016-2018 Jonas Bernoulli
-
-;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
-;; Homepage: https://github.com/magit/ghub
-;; Keywords: tools
-
-;; This file is not part of GNU Emacs.
-
-;; 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, 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.
-
-;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
-
-;;; 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-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" owner name)
- nil :username username :auth auth :host host)))))
-
-;;; _
-(provide 'glab)
-;;; glab.el ends here
--- /dev/null
+;;; buck.el --- minuscule client library for the Bitbucket API -*- lexical-binding: t -*-
+
+;; Copyright (C) 2016-2018 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; This file is not part of GNU Emacs.
+
+;; 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, 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.
+
+;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
+
+;;; 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). Minuscule client library for the Github API.
--- /dev/null
+;;; ghub-autoloads.el --- automatically extracted autoloads
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+ (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "buck" "buck.el" (0 0 0 0))
+;;; Generated autoloads from buck.el
+
+(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "buck" '("buck-default-host")))
+
+;;;***
+\f
+;;;### (autoloads nil "ghub" "ghub.el" (0 0 0 0))
+;;; Generated autoloads from ghub.el
+
+(autoload 'ghub-create-token "ghub" "\
+Create, store and return a new token.
+
+HOST is the Github instance, usually \"api.github.com\".
+USERNAME is the name of a user on that instance.
+PACKAGE is the package that will use the token.
+SCOPES are the scopes the token is given access to.
+
+\(fn HOST USERNAME PACKAGE SCOPES)" t nil)
+
+(autoload 'ghub-token-scopes "ghub" "\
+Return and echo the scopes of the specified token.
+This is intended for debugging purposes only. The user
+has to provide several values including their password.
+
+\(fn HOST USERNAME PACKAGE)" t nil)
+
+(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+'.
+
+\(fn)" t nil)
+
+(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ghub" '("auth-source-netrc-parse-next-interesting@save-match-data" "ghub-")))
+
+;;;***
+\f
+;;;### (autoloads nil "ghub-graphql" "ghub-graphql.el" (0 0 0 0))
+;;; Generated autoloads from ghub-graphql.el
+
+(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "ghub-graphql" '("ghub-")))
+
+;;;***
+\f
+;;;### (autoloads nil "glab" "glab.el" (0 0 0 0))
+;;; Generated autoloads from glab.el
+
+(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "glab" '("glab-default-host")))
+
+;;;***
+\f
+;;;### (autoloads nil "gogs" "gogs.el" (0 0 0 0))
+;;; Generated autoloads from gogs.el
+
+(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "gogs" '("gogs-default-host")))
+
+;;;***
+\f
+;;;### (autoloads nil "gtea" "gtea.el" (0 0 0 0))
+;;; Generated autoloads from gtea.el
+
+(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "gtea" '("gtea-default-host")))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("ghub-pkg.el") (0 0 0 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; ghub-autoloads.el ends here
--- /dev/null
+;;; ghub-graphql.el --- access Github API using GrapthQL -*- lexical-binding: t -*-
+
+;; Copyright (C) 2016-2018 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Homepage: https://github.com/magit/ghub
+
+;; This file is not part of GNU Emacs.
+
+;; 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, 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.
+
+;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
+
+;;; Code:
+
+(require 'dash)
+(require 'ghub)
+(require 'graphql)
+(require 'treepy)
+
+(eval-when-compile
+ (require 'subr-x))
+
+;;; Api
+
+(cl-defun ghub-graphql (graphql &optional variables
+ &key username auth host
+ 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 (stringp graphql))
+ (cl-assert (not (stringp variables)))
+ (ghub-request "POST" "/graphql" nil :payload
+ (json-encode `(("query" . ,graphql)
+ ,@(and variables `(("variables" ,@variables)))))
+ :silent silent
+ :username username :auth auth :host host
+ :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 ($owner:String!, $name:String!) {
+ repository(owner:$owner, name:$name) { id }
+ }"
+ `((owner . ,owner)
+ (name . ,name))
+ :username username :auth auth :host host)
+ .data.repository.id))
+
+;;; Api (drafts)
+
+(defconst ghub-fetch-repository
+ '(query
+ (repository
+ [(owner $owner String!)
+ (name $name String!)]
+ name
+ id
+ createdAt
+ updatedAt
+ nameWithOwner
+ description
+ (defaultBranchRef name)
+ isArchived
+ isFork
+ isLocked
+ isMirror
+ isPrivate
+ hasIssuesEnabled
+ hasWikiEnabled
+ (licenseInfo name)
+ (stargazers totalCount)
+ (watchers totalCount)
+ (assignableUsers [(:edges t)]
+ id
+ login
+ name)
+ (issues [(:edges t)
+ (:singular issue number)
+ (orderBy ((field . UPDATED_AT) (direction . DESC)))]
+ number
+ state
+ (author login)
+ title
+ createdAt
+ updatedAt
+ closedAt
+ locked
+ (milestone id)
+ body
+ (assignees [(:edges t)]
+ id)
+ (comments [(:edges t)]
+ databaseId
+ (author login)
+ createdAt
+ updatedAt
+ body)
+ (labels [(:edges t)]
+ id))
+ (labels [(:edges t)
+ (:singular label id)]
+ id
+ name
+ color
+ description)
+ (pullRequests [(:edges t)
+ (:singular pullRequest number)
+ (orderBy ((field . UPDATED_AT) (direction . DESC)))]
+ number
+ state
+ (author login)
+ title
+ createdAt
+ updatedAt
+ closedAt
+ mergedAt
+ locked
+ maintainerCanModify
+ isCrossRepository
+ (milestone id)
+ body
+ (baseRef name
+ (repository nameWithOwner))
+ (headRef name
+ (repository (owner login)
+ nameWithOwner))
+ (assignees [(:edges t)]
+ id)
+ (comments [(:edges t)]
+ databaseId
+ (author login)
+ createdAt
+ updatedAt
+ body)
+ (labels [(:edges t)]
+ id)))))
+
+(cl-defun ghub-fetch-repository (owner name callback
+ &optional until
+ &key username auth host forge)
+ "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 ghub-fetch-repository
+ `((owner . ,owner)
+ (name . ,name))
+ callback until
+ :narrow '(repository)
+ :username username
+ :auth auth
+ :host host
+ :forge forge))
+
+(cl-defun ghub-fetch-issue (owner name number callback
+ &optional until
+ &key username auth host forge)
+ "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-prepare-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))
+
+(cl-defun ghub-fetch-pullreq (owner name number callback
+ &optional until
+ &key username auth host forge)
+ "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-prepare-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))
+
+;;; Internal
+
+(cl-defstruct (ghub--graphql-req
+ (:include ghub--req)
+ (:constructor ghub--make-graphql-req)
+ (:copier nil))
+ (query nil :read-only t)
+ (variables nil :read-only t)
+ (until nil :read-only t)
+ (pages 0 :read-only nil))
+
+(cl-defun ghub--graphql-vacuum (query variables callback
+ &optional until
+ &key narrow username auth host forge)
+ "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 (concat "https://" host "/graphql"))
+ :method "POST"
+ :headers (ghub--headers nil host auth username forge)
+ :handler 'ghub--graphql-handle-response
+ :query query
+ :variables variables
+ :until until
+ :callback (if narrow
+ (lambda (data)
+ (let ((path narrow) key)
+ (while (setq key (pop path))
+ (setq data (cdr (assq key data)))))
+ (funcall callback data))
+ callback))))
+
+(cl-defun ghub--graphql-retrieve (req &optional lineage cursor)
+ (let ((p (cl-incf (ghub--graphql-req-pages req))))
+ (when (> p 1)
+ (message "Fetching page %s..." p)))
+ (ghub--retrieve
+ (let ((json-false nil))
+ (ghub--encode-payload
+ `((query . ,(ghub--graphql-encode
+ (ghub--graphql-prepare-query
+ (ghub--graphql-req-query req)
+ lineage cursor)))
+ (variables . ,(ghub--graphql-req-variables req)))))
+ req))
+
+(defun ghub--graphql-prepare-query (query &optional lineage cursor)
+ (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 (vectorp node)
+ (let ((alist (cl-coerce node 'list))
+ vars)
+ (when (assq :edges alist)
+ (push (list 'first 100) 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 (cl-coerce vars 'vector))))))
+ (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 ((buffer (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)))
+ (data (assq 'data payload))
+ (value (ghub--req-value req)))
+ (setf (ghub--req-value req) value)
+ (if (or err errors)
+ (if-let ((errorback (ghub--req-errorback req)))
+ (funcall errorback (or err errors) headers status req)
+ (ghub--signal-error (or err errors)))
+ (ghub--graphql-walk-response value data req))))
+ (when (buffer-live-p buffer)
+ (kill-buffer buffer)))))
+
+(defun ghub--graphql-walk-response (loc data req)
+ (if (not loc)
+ (setf (ghub--req-value req)
+ (setq 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"))))
+ (setq loc (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
+ (--take-while
+ (or (string> (cdr (assq 'updatedAt it)) until)
+ (setq cursor nil))
+ nodes)
+ nodes)))
+ (if cursor
+ (progn
+ (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))))))))
+ (if (not (treepy-end-p loc))
+ (setq loc (treepy-next loc))
+ (funcall (ghub--req-callback req)
+ (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)
+ (let (key)
+ (while (setq 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 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))
+ (cl-find-if (lambda (c)
+ (and (listp c)
+ (vectorp (cadr c))
+ (eq (cadr (assq :singular
+ (cl-coerce (cadr c)
+ 'list)))
+ (car lineage))))
+ (cdr 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--graphql-encode (g)
+ (if (symbolp g)
+ (symbol-name g)
+ (let* ((object (car g))
+ (args (and (vectorp (cadr g))
+ (cl-coerce (cadr g) 'list)))
+ (fields (if args (cddr g) (cdr g))))
+ (concat
+ (graphql--encode-object object)
+ (and args
+ (format " (\n%s)"
+ (mapconcat (pcase-lambda (`(,key ,val))
+ (graphql--encode-argument key val))
+ args ",\n")))
+ (and fields
+ (format " {\n%s\n}"
+ (mapconcat #'ghub--graphql-encode fields "\n")))))))
+
+(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)))
+
+;;; _
+(provide 'ghub-graphql)
+;;; ghub-graphql.el ends here
--- /dev/null
+(define-package "ghub" "20181110.1845" "Minuscule client libraries for Git forge APIs."
+ '((emacs "25.1")
+ (dash "2.14.1")
+ (graphql "0.1.1")
+ (let-alist "1.0.5")
+ (treepy "1.0.0")))
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; ghub.el --- minuscule client libraries for Git forge APIs -*- lexical-binding: t -*-
+
+;; Copyright (C) 2016-2018 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; This file is not part of GNU Emacs.
+
+;; 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, 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.
+
+;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
+
+;;; 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 'json)
+(require 'let-alist)
+(require 'url)
+(require 'url-auth)
+(require 'url-http)
+
+(eval-when-compile
+ (require 'subr-x))
+
+(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.
+
+The token that is created based on the value of this variable
+is used when `ghub-request' (or one of its wrappers) is called
+without providing a value for AUTH. Packages should always
+identify themselves using that argument, but when you use Ghub
+directly in private tools, then that is not necessary and the
+request is made on behalf of the `ghub' package itself, aka on
+behalf of some private tool.
+
+By default the only requested scope is `repo' because that is
+sufficient as well as required for most common uses. This and
+other scopes are documented at URL `https://magit.vc/goto/2e586d36'.
+
+If your private tools need other scopes, then you have to add
+them here *before* creating the token. Alternatively you can
+edit the scopes of an existing token using the web interface
+at URL `https://github.com/settings/tokens'.")
+
+(defvar ghub-override-system-name nil
+ "If non-nil, the string used to identify the local machine.
+If this is nil, then the value returned by `system-name' is
+used instead.")
+
+;;; 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)
+ (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 header 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 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 make one or more
+ asynchronous requests and call CALLBACK or ERRORBACK when
+ finished. If an error occurred, then call ERRORBACK, or if
+ that is nil, then CALLBACK. When no error occurred then call
+ CALLBACK. When making asynchronous requests, then no errors
+ are signaled, regardless of the value of NOERROR.
+
+Both callbacks are called with four arguments.
+ 1. For CALLBACK, the combined value of the retrieved pages.
+ For ERRORBACK, the error that occured 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 "https://" 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
+ :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)
+ "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-get resource nil
+ :noerror t
+ :username username
+ :auth auth
+ :host host))
+ (message "Waited (%3ss of %ss) for %s..." total duration resource)
+ (if (= total duration)
+ (error "Github is taking too long to create %s" 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 (cl-mapcan (lambda (key)
+ (when-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)))
+ (when-let ((rels (cdr (assoc "Link" (or headers ghub-response-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)
+ "Return the id of the specified repository."
+ (let ((fn (intern (format "%s-repository-id" (or forge 'ghub)))))
+ (funcall (if (eq fn 'ghub-repository-id) 'ghub--repository-id fn)
+ owner name :username username :auth auth :host host)))
+
+;;;; 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)
+ ;; When this function has already been called, then it is a
+ ;; no-op. Otherwise it sets `url-registered-auth-schemes' among
+ ;; other things. If we didn't ensure that it has been run, then
+ ;; `url-retrieve-synchronously' would do it, which would cause
+ ;; the value that we let-bind below to be overwritten, and the
+ ;; "default" value to be lost outside the let-binding.
+ (url-do-setup)
+ (with-current-buffer
+ (let ((url-registered-auth-schemes
+ '(("basic" ghub--basic-auth-errorback . 10))))
+ (url-retrieve-synchronously url silent))
+ (funcall handler (car url-callback-arguments) req)))))
+
+(defun ghub--handle-response (status req)
+ (let ((buffer (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))
+ (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 ((callback (ghub--req-callback req))
+ (errorback (ghub--req-errorback req))
+ (err (plist-get status :error)))
+ (cond ((and err errorback)
+ (funcall errorback err headers status req))
+ (callback
+ (funcall callback value headers status req))
+ (t value))))))
+ (when (buffer-live-p buffer)
+ (kill-buffer buffer)))))
+
+(defun ghub--handle-response-headers (status req)
+ (goto-char (point-min))
+ (forward-line 1)
+ (let (headers)
+ (while (re-search-forward "^\\([^:]*\\): \\(.+\\)"
+ url-http-end-of-headers t)
+ (push (cons (match-string 1)
+ (match-string 2))
+ headers))
+ (setq headers (nreverse headers))
+ (unless url-http-end-of-headers
+ (error "BUG: missing headers %s" (plist-get status :error)))
+ (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)
+ (let ((noerror (ghub--req-noerror req))
+ (err (plist-get status :error)))
+ (if err
+ (if noerror
+ (if (eq noerror 'return)
+ payload
+ (setcdr (last err) (list payload))
+ nil)
+ (ghub--signal-error err payload))
+ payload)))
+
+(defun ghub--signal-error (err &optional payload)
+ (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))
+ payload)))
+ (signal 'ghub-error data))
+ (signal symb data))))
+
+(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
+ (let ((json-object-type 'alist)
+ (json-array-type 'list)
+ (json-key-type 'symbol)
+ (json-false nil)
+ (json-null nil))
+ (json-read-from-string raw))
+ (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 (json-encode-list 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)) "="
+ (if (integerp val)
+ (number-to-string val)
+ (url-hexify-string val)))))
+ params "&"))
+
+;;; Authentication
+;;;; API
+
+;;;###autoload
+(defun ghub-create-token (host username package scopes)
+ "Create, store and return a new token.
+
+HOST is the Github instance, usually \"api.github.com\".
+USERNAME is the name of a user on that instance.
+PACKAGE is the package that will use the token.
+SCOPES are the scopes the token is given access to."
+ (interactive
+ (pcase-let ((`(,host ,username ,package)
+ (ghub--read-triplet)))
+ (list host username package
+ (split-string
+ (read-string
+ "Scopes (separated by commas): "
+ (mapconcat #'symbol-name
+ (symbol-value
+ (intern (format "%s-github-token-scopes" package)))
+ ","))
+ "," t "[\s\t]+"))))
+ (let ((user (ghub--ident username package)))
+ (cl-destructuring-bind (save token)
+ (ghub--auth-source-get (list :save-function :secret)
+ :create t :host host :user user
+ :secret
+ (cdr (assq 'token
+ (ghub-post
+ "/authorizations"
+ `((scopes . ,scopes)
+ (note . ,(ghub--ident-github package)))
+ :username username :auth 'basic :host host))))
+ ;; Build-in back-ends return a function that does the actual
+ ;; saving, while for some third-party back-ends ":create t"
+ ;; is enough.
+ (when (functionp save)
+ (funcall save))
+ ;; If the Auth-Source cache contains the information that there
+ ;; is no value, then setting the value does not invalidate that
+ ;; now incorrect information.
+ (auth-source-forget (list :host host :user user))
+ token)))
+
+;;;###autoload
+(defun ghub-token-scopes (host username package)
+ "Return and echo the scopes of the specified token.
+This is intended for debugging purposes only. The user
+has to provide several values including their password."
+ (interactive (ghub--read-triplet))
+ (let ((scopes
+ (cdr (assq 'scopes (ghub--get-token-plist host username package)))))
+ (when (called-interactively-p 'any)
+ ;; Also show the input values to make it easy for package
+ ;; authors to verify that the user has done it correctly.
+ (message "Scopes for %s@%s: %S"
+ (ghub--ident username package)
+ host scopes))
+ scopes))
+
+;;;###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)))))
+
+(defun ghub--auth (host auth &optional username forge)
+ (unless username
+ (setq username (ghub--username host)))
+ (if (eq auth 'basic)
+ (cl-ecase forge
+ ((nil github gitea gogs bitbucket)
+ (cons "Authorization" (ghub--basic-auth host username)))
+ (gitlab
+ (error "Gitlab does not support basic authentication")))
+ (cons (cl-ecase forge
+ ((nil github gitea gogs bitbucket)
+ "Authorization")
+ (gitlab
+ "Private-Token"))
+ (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 (concat "https://" host))))
+ (setf (url-user url) username)
+ (url-basic-auth url t)))
+
+(defun ghub--basic-auth-errorback (url &optional prompt _overwrite _realm _args)
+ ;; This gets called twice. Do nothing the first time,
+ ;; when PROMPT is nil. See `url-get-authentication'.
+ (when prompt
+ (if (assoc "X-GitHub-OTP" (ghub--handle-response-headers nil nil))
+ (progn
+ (setq url-http-extra-headers
+ `(("Content-Type" . "application/json")
+ ("X-GitHub-OTP" . ,(ghub--read-2fa-code))
+ ;; Without "Content-Type" and "Authorization".
+ ;; The latter gets re-added from the return value.
+ ,@(cddr url-http-extra-headers)))
+ ;; Return the cached values, they are correct.
+ (url-basic-auth url nil nil nil))
+ ;; Remove the invalid cached values and fail, which
+ ;; is better than the invalid values sticking around.
+ (setq url-http-real-basic-auth-storage
+ (cl-delete (format "%s:%d" (url-host url) (url-port url))
+ url-http-real-basic-auth-storage
+ :test #'equal :key #'car))
+ nil)))
+
+(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. See #24, #64.
+ (auth-source-forget (list :host host :user user :max 1))
+ (and (not nocreate)
+ (cl-ecase forge
+ ((nil github)
+ (ghub--confirm-create-token host username package))
+ ((gitlab gitea gogs bitbucket)
+ (error "Required %s token (%S for %S) does not exist.
+See https://magit.vc/manual/ghub/Support-for-Other-Forges.html for instructions."
+ (capitalize (symbol-name forge))
+ user host))))))))
+ (if (functionp token) (funcall token) token)))
+
+(defun 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)))))
+
+(defun 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"))
+ (error "gitea.host is set but always ignored"))
+ (format "gitea.%s.user" host))
+ (gogs
+ (when (zerop (call-process "git" nil nil nil "config" "gogs.host"))
+ (error "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))))
+ (or (and user (progn (call-process "git" nil nil nil
+ "config" "--global" var user)
+ user))
+ (user-error "Abort")))))))
+
+(defun ghub--ident (username package)
+ (format "%s^%s" username package))
+
+(defun ghub--ident-github (package)
+ (format "Emacs package %s @ %s"
+ package
+ (or ghub-override-system-name (system-name))))
+
+(defun ghub--package-scopes (package)
+ (let ((var (intern (format "%s-github-token-scopes" package))))
+ (if (boundp var)
+ (symbol-value var)
+ (error "%s fails to define %s" package var))))
+
+(defun ghub--confirm-create-token (host username package)
+ (let* ((ident (ghub--ident-github package))
+ (scopes (ghub--package-scopes package))
+ (max-mini-window-height 40))
+ (if (let ((message-log-max nil))
+ (yes-or-no-p
+ (format
+ "Such a Github API token is not available:
+
+ Host: %s
+ User: %s
+ Package: %s
+
+ Scopes requested in `%s-github-token-scopes':\n%s
+ Store on Github as:\n %S
+ Store locally according to option `auth-sources':\n %S
+%s
+If in doubt, then abort and first view the section of the Ghub
+documentation called \"Manually Creating and Storing a Token\".
+
+Otherwise confirm and then provide your Github username and
+password at the next two prompts. Depending on the backend
+you might have to provide a passphrase and confirm that you
+really want to save the token.
+
+Create and store such a token? "
+ host username package package
+ (mapconcat (lambda (scope) (format " %s" scope)) scopes "\n")
+ ident auth-sources
+ (if (and (stringp (car auth-sources))
+ (not (string-suffix-p ".gpg" (car auth-sources))))
+ (format "
+WARNING: The token will be stored unencrypted in %S.
+ If you don't want that, you have to abort and customize
+ the `auth-sources' option.\n" (car auth-sources))
+ ""))))
+ (condition-case ghub--create-token-error
+ ;; Naively attempt to create the token since the user told us to
+ (ghub-create-token host username package scopes)
+ ;; The API _may_ respond with the fact that a token of the name
+ ;; we wanted already exists. At this point we're out of luck. We
+ ;; don't have a token (otherwise why would we be here?) and, if
+ ;; the user is using SMS 2FA, we have no way of telling GitHub
+ ;; to send a new 2FA code to the user other than sending a POST
+ ;; to /authorizations which is ugly.
+ ;;
+ ;; If they are not using SMS 2FA then we could try to delete the
+ ;; existing token (which will require them to hand us another
+ ;; OTP for the delete request) and then call create again,
+ ;; possibly requiring _another_ OTP if they don't do things fast
+ ;; enough, but this is only because non-SMS 2FA doesn't require
+ ;; any action on GitHub's part.
+ ;;
+ ;; GitHub does hand us a header that indicates what type of 2FA
+ ;; is in use, but it's not currently available in this location
+ ;; and would make the following code which is already quite
+ ;; complicated even more complicated. So in the interest of
+ ;; simplicity it's better to error out here and ask the user to
+ ;; take action. This situation should almost never arise anyway.
+ (ghub-http-error
+ (if (string-equal (let-alist (nth 3 ghub--create-token-error)
+ (car .errors.code))
+ "already_exists")
+ (error "\
+A token named %S already exists on Github. \
+Please visit https://github.com/settings/tokens and delete it." ident))))
+ (user-error "Abort"))))
+
+(defun ghub--get-token-id (host username package)
+ (let ((ident (ghub--ident-github package)))
+ (cl-some (lambda (x)
+ (let-alist x
+ (and (equal .app.name ident) .id)))
+ (ghub-get "/authorizations"
+ '((per_page . 100))
+ :unpaginate t
+ :username username :auth 'basic :host host))))
+
+(defun ghub--get-token-plist (host username package)
+ (ghub-get (format "/authorizations/%s"
+ (ghub--get-token-id host username package))
+ nil :username username :auth 'basic :host host))
+
+(defun ghub--delete-token (host username package)
+ (ghub-delete (format "/authorizations/%s"
+ (ghub--get-token-id host username package))
+ nil :username username :auth 'basic :host host))
+
+(defun ghub--read-triplet ()
+ (let ((host (read-string "Host: " (ghub--host))))
+ (list host
+ (read-string "Username: " (ghub--username host))
+ (intern (read-string "Package: " "ghub")))))
+
+(defvar ghub--2fa-cache nil)
+
+(defun ghub--read-2fa-code ()
+ (let ((code (read-number "Two-factor authentication code: "
+ (and ghub--2fa-cache
+ (< (float-time (time-subtract
+ (current-time)
+ (cdr ghub--2fa-cache)))
+ 25)
+ (car ghub--2fa-cache)))))
+ (setq ghub--2fa-cache (cons code (current-time)))
+ (number-to-string code)))
+
+(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)))
+
+(advice-add 'auth-source-netrc-parse-next-interesting :around
+ 'auth-source-netrc-parse-next-interesting@save-match-data)
+(defun auth-source-netrc-parse-next-interesting@save-match-data (fn)
+ "Save match-data for the benefit of caller `auth-source-netrc-parse-one'.
+Without wrapping this function in `save-match-data' the caller
+won't see the secret from a line that is followed by a commented
+line."
+ (save-match-data (funcall fn)))
+
+;;; _
+(provide 'ghub)
+(require 'ghub-graphql)
+;;; ghub.el ends here
--- /dev/null
+This is ghub.info, produced by makeinfo version 6.5 from ghub.texi.
+
+ Copyright (C) 2017-2018 Jonas Bernoulli <jonasNOSPAMbernoul.li>
+
+ 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). Minuscule 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 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.
+
+This manual is for Ghub version 3.0.0 (v3.0.0-2-g8dbf6b2+1).
+
+ Copyright (C) 2017-2018 Jonas Bernoulli <jonasNOSPAMbernoul.li>
+
+ 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::
+* Using Ghub in Personal Scripts::
+* Using Ghub in a Package::
+* API::
+* GraphQL Support::
+* Support for Other Forges::
+
+— The Detailed Node Listing —
+
+Getting Started
+
+* Setting the Username::
+* Interactively Creating and Storing a Token::
+* Manually Creating and Storing a Token::
+* How Ghub uses Auth-Source::
+
+API
+
+* Making Requests::
+* Authentication::
+* Configuration Variables::
+
+Support for Other Forges
+
+* Forge Functions and Variables::
+* Forge Limitations and Notes::
+
+
+\1f
+File: ghub.info, Node: Introduction, Next: Getting Started, Prev: Top, Up: Top
+
+1 Introduction
+**************
+
+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.
+
+ Fancier interfaces can be implemented on top of Ghub, and one such
+wrapper — named simply Ghub+ — has already been implemented. The
+benefit of basing various opinionated interfaces on top of a single
+library that provides only the core functionality is that choosing the
+programming interface no longer dictates how access tokens are handled.
+Users can then use multiple packages that access the Github API without
+having to learn the various incompatible ways packages expect the
+appropriate token to be made available to them.
+
+ Ghub uses the built-in ‘auth-source’ library to store access tokens.
+That library is very flexible and supports multiple backends, which
+means that it is up to the user how secrets are stored. They can, among
+other things, choose between storing secrets in plain text for ease of
+use, or encrypted for better security.
+
+ Previously (as in until this library is widely adopted) it was up to
+package authors to decide if things should be easy or secure. (Note
+that ‘auth-source’ defaults to "easy" — you have been warned.)
+
+ Ghub expects package authors to use a dedicated access token instead
+of sharing a single token between all packages that rely on it. That
+means that users cannot configure Ghub once and later start using a new
+package without any additional setup. But Ghub helps with that.
+
+ When the user invokes some command that ultimately results in
+‘ghub-request’ being called and the appropriate token is not available
+yet, then the user is guided through the process of creating and storing
+a new token, and at the end of that process the request is carried out
+as if the token had been available to begin with.
+
+\1f
+File: ghub.info, Node: Getting Started, Next: Using Ghub in Personal Scripts, Prev: Introduction, Up: Top
+
+2 Getting Started
+*****************
+
+Each package that uses Ghub uses its own token. Despite that, chances
+are good that after successfully configuring one package you can just
+start using another package pretty much instantly.
+
+ If the necessary token to access a Github instance is not available
+when a package makes an API request, then a setup wizard pops up, and
+after answering a few questions you are good to go. Even the request
+that caused the wizard to be summoned should succeed and for most users
+this should be true even when configuring the very first token.
+
+ However, in some situations some manual configuration is necessary
+*before* using the wizard, or the wizard cannot be used at all:
+
+ • If you don’t want to use the wizard then you don’t have to and can
+ create tokens manually as described in *note Manually Creating and
+ Storing a Token::.
+
+ • Unfortunately only Github supports the creation of tokens by using
+ the API. If you want to access another forge, then you have to
+ create the token manually as describe in *note Manually Creating
+ and Storing a Token::. Also see *note Support for Other Forges::.
+
+ • If you want to access a Github Enterprise instance, then you have
+ to tell Ghub about it before the wizard makes its appearance by
+ setting the Git variable ‘github.host’. For example, if your
+ company’s GitHub Enterprise instance is hosted at
+ ‘git.example.com’, then you should set ‘github.host’ to
+ ‘git.example.com/api/v3’ to use the JSON API. You also have to
+ tell Ghub your username for that instance using the variable
+ ‘github.HOST.user’ (e.g. ‘github.git.example.com/api/v3.user’)
+ even if it is the same as on Github.com. While ‘github.host’
+ should be set per-repository to allow Ghub to interface with both a
+ GitHub Enterprise instance and GitHub.com, setting GitHub
+ Enterprise users globally is recommended.
+
+ • If the variable ‘github.user’ (or ‘github.HOST.user’ for an
+ Enterprise instance) is unset when the wizard is first summoned,
+ then you are asked to provide your username. That value is then
+ stored *globally* to avoid having to ask you that question once per
+ repository. If you have multiple accounts on Github.com (or a
+ Github Enterprise instance), then you have to explicitly tell Ghub
+ about that. This can be done by setting the repository-local
+ values of the appropriate variable *before* the wizard is invoked.
+
+ • You might forget to do the above, which is why it is important to
+ carefully read the output of the wizard. If it turns out that you
+ forgot to set a variable, then you must abort, set the variable,
+ and repeat the request to trigger the wizard again.
+
+ • The setup wizard should work even if you have enabled two-factor
+ authentication. However if your Github Enterprise instance
+ enforces Single Sign-On as an additional security measure, then you
+ are out of luck and have to create the token manually as described
+ in *note Manually Creating and Storing a Token::.
+
+ The variables mentioned above — and others — are documented in *note
+Configuration Variables:: and the setup wizard is documented in *note
+Interactively Creating and Storing a Token::.
+
+* Menu:
+
+* Setting the Username::
+* Interactively Creating and Storing a Token::
+* Manually Creating and Storing a Token::
+* How Ghub uses Auth-Source::
+
+\1f
+File: ghub.info, Node: Setting the Username, Next: Interactively Creating and Storing a Token, Up: Getting Started
+
+2.1 Setting the Username
+========================
+
+If you haven’t set the Git variable ‘github.user’ yet when making a
+request, then you will be asked:
+
+ Git variable `github.user' is unset. Set to:
+
+ You are expected to provide your Github username here. The provided
+value will be saved globally (using ‘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, *before* making a request:
+
+ cd /path/to/repo
+ git config github.user USERNAME
+
+ For Github Enterprise instances you have to specify where the API can
+be accessed *before* you try to access it 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:
+
+ # Do this once
+ git config --global github.example.com/api/v3.user EMPLOYEE
+
+ # Do this for every corporate repository
+ cd /path/to/repo
+ git config github.host example.com/api/v3
+
+ If you do not set ‘github.example.com/api/v3.user’, then you will be
+asked to provide the value when trying to make a request, but you do
+have to manually set ‘github.host’, or Ghub assumes that you are trying
+to access ‘api.github.com’.
+
+\1f
+File: ghub.info, Node: Interactively Creating and Storing a Token, Next: Manually Creating and Storing a Token, Prev: Setting the Username, Up: Getting Started
+
+2.2 Interactively Creating and Storing a Token
+==============================================
+
+Ghub uses a different token for every package as well as for every
+machine from which you access the Github API (and obviously also for
+every Github instance and user). This allows packages to only request
+the scopes that they actually need and also gives users the opportunity
+to refuse access to certain scopes if they expect to not use the
+features that need them.
+
+ Usually you don’t have to worry about creating and storing a token
+yourself and can just make a request. Note however that you don’t have
+to use the setup wizard described below. Alternatively you can perform
+the setup manually as described in the next section.
+
+ If you make a request and the required token is not available yet,
+then the setup wizard will first ask you something like this:
+
+ Such a Github API token is not available:
+
+ Host: api.github.com
+ User: USERNAME
+ Package: PACKAGE
+
+ Scopes requested in `PACKAGE-github-token-scopes':
+ repo
+ Store on Github as:
+ "Emacs package PACKAGE @ LOCAL-MACHINE"
+ Store locally according to option `auth-sources':
+ ("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")
+
+ If in doubt, then abort and first view the section of the Ghub
+ documentation called "Manually Creating and Storing a Token".
+
+ Create and store such a token? (yes or no)
+
+ If you don’t have any doubts, then answer "yes". Lets address some
+of the doubts that you might have:
+
+ • ‘Host’ usually is "api.github.com" and that is usually what you
+ want. If you are trying to access a Github Enterprise instance,
+ then it should be something else and you have to set the value
+ manually before the setup wizard is summoned, as described in the
+ parent section.
+
+ • ‘User’ should be your Github.com (or Github Enterprise instance)
+ username. If it is something else and it doesn’t look like a
+ simple typo, then you should read the parent section again. In
+ either case you have to abort.
+
+ • ‘Package’ should be the name of the package you are using to access
+ the Github API.
+
+ If it is ‘ghub’, then the package author disregarded that
+ convention and you should probably report a bug in the issue
+ tracker of that package.
+
+ Or you yourself are using ‘ghub-request’ or one of its wrappers
+ directly, in which case this is expected and perfectly fine. In
+ that case you might however want to abort and change the value of
+ the variable ‘ghub-github-token-scopes’ before triggering the
+ wizard again.
+
+ • Each ‘PACKAGE’ has to specify the tokens that it needs using a
+ variable named ‘PACKAGE-github-token-scopes’. The doc-string of
+ that variable should document why the various scopes are needed.
+
+ The meaning of the various scopes are documented at
+ <https://magit.vc/goto/f63aeb0a>.
+
+ • The value of ‘auth-sources’ is shown. The default value causes
+ secrets to be stored in plain text. Because this might be
+ unexpected, Ghub additionally displays a warning when appropriate.
+
+ WARNING: The token will be stored unencrypted in "~/.authinfo".
+ If you don't want that, you have to abort and customize
+ the `auth-sources' option.
+
+ Whether that is something that needs fixing, is up to you. If your
+ answer is yes, then you should abort and see *note How Ghub uses
+ Auth-Source:: for instructions on how to save the token more
+ securely.
+
+ • When creating a token it is necessary to provide a token
+ description. Ghub uses descriptions that have the form "Emacs
+ package PACKAGE @ LOCAL-MACHINE".
+
+ Github uses the token description to identify the token, not merely
+ as something useful to humans. Token descriptions therefore have
+ to be unique and in rare cases you get an additional prompt, asking
+ you something like:
+
+ A token named "Emacs package PACKAGE @ LOCAL-MACHINE"
+ already exists on Github. Replace it?
+
+ You might see this message when you have lost the old token and
+ want to replace it with a new one, in which case you should
+ obviously just proceed.
+
+ Or two of your computers have the same hostname, which is bad
+ practice because it gains you nothing but leads to issues such as
+ this. Or you are dual-booting on this machine and use the same
+ hostname in all operating systems, which is a somewhat reasonable
+ thing to do, but never-the-less leads to issues like this.
+
+ In either case you will have to use something other than the value
+ returned by ‘system-name’ to identify the current machine or
+ operating system. Or you can continue to identify different things
+ using the same identifier, in which case you have to manually
+ distribute the token.
+
+ The former is recommended and also easier to do, using the variable
+ ‘ghub-override-system-name’. See *note Configuration Variables::
+ for details.
+
+ After the above prompt you are also asked for you username and
+password. If you have enabled two-factor authentication, then you also
+have to provide the authentication code at least twice. If you make
+sure the code is still good for a while when asked for it first, then
+you can just press ‘RET’ at the later prompt(s).
+
+\1f
+File: ghub.info, Node: Manually Creating and Storing a Token, Next: How Ghub uses Auth-Source, Prev: Interactively Creating and Storing a Token, Up: Getting Started
+
+2.3 Manually Creating and Storing a Token
+=========================================
+
+If you cannot or don’t want to use the wizard then you have to (1)
+figure out what scopes a package wants, (2) create such a token using
+the web interface and (3) store the token where Ghub expects to find it.
+
+ A package named ‘PACKAGE’ has to specify the scopes that it wants in
+the variable named ‘PACKAGE-ghub-token-scopes’. The doc-string of such
+variables should document what the various scopes are needed for.
+
+ To create or edit a token go to <https://github.com/settings/tokens>.
+For Gitlab.com use <https://gitlab.com/profile/personal_access_tokens>.
+
+ Finally store the token in a place where Ghub looks for it, as
+described in *note How Ghub uses Auth-Source::.
+
+ If you store the token in a file like ‘~/.authinfo’, then note that
+‘auth-source’’s parsing of that file is brittle. Make sure the file
+ends with a newline character, that there are no empty or invalid lines,
+and that all comments are prefixed with ‘#’.
+
+\1f
+File: ghub.info, Node: How Ghub uses Auth-Source, Prev: Manually Creating and Storing a Token, Up: Getting Started
+
+2.4 How Ghub uses Auth-Source
+=============================
+
+Please see *note (auth)Top:: for all the gory details about Auth-Source.
+Some Ghub-specific information and important notes follow.
+
+ The variable ‘auth-sources’ controls how and where Auth-Source stores
+new secrets and where it looks for known secrets. The default value is
+‘("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")’, which means that it
+looks in all of these files in order to find secrets and that it stores
+new secrets in ‘~/.authinfo’ because that is the first element of the
+list. It doesn’t matter which files already do or don’t exist when
+storing a new secret, the first file is always used.
+
+ Secrets are stored in ‘~/.authinfo’ in plain text. If you don’t want
+that (good choice), then you have to customize ‘auth-sources’, e.g. by
+flipping the positions of the first two elements.
+
+ Auth-Source also supports storing secrets in various key-chains.
+Refer to its documentation for more information.
+
+ Some 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 username is "ziggy",the package is named "stardust",
+and you want to access *Github.com* an entry in one of the three
+mentioned files would then look like this:
+
+ machine api.github.com login ziggy^stardust password 012345abcdef...
+
+ Assuming your username is "ziggy",the package is named "stardust",
+and you want to access *Gitlab.com* an entry in one of the three
+mentioned files would then look like this:
+
+ machine gitlab.com/api/v4 login ziggy^stardust password 012345abcdef...
+
+\1f
+File: ghub.info, Node: Using Ghub in Personal Scripts, Next: Using Ghub in a Package, Prev: Getting Started, Up: Top
+
+3 Using Ghub in Personal Scripts
+********************************
+
+You can use ‘ghub-request’ and its wrapper functions in your personal
+scripts, of course. Unlike when you use Ghub from a package that you
+distribute for others to use, you don’t have to specify a package in
+personal scripts.
+
+ ;; This is perfectly acceptable in personal scripts ...
+ (ghub-get "/user")
+
+ ;; ... and actually equal to
+ (ghub-get "/user" nil :auth 'ghub)
+
+ ;; In packages you have to specify the package using AUTH.
+ (ghub-get "/user" nil :auth 'foobar)
+
+ When you do not specify the ‘AUTH’ argument, then a request is made
+on behalf of the ‘ghub’ package itself. Like for any package that uses
+Ghub, ‘ghub’ has to declare what scopes it needs, using, in this case,
+the variable ‘ghub-github-token-scopes’.
+
+ The default value of that variable is ‘(repo)’ and you might want to
+add additional scopes. You can later add additional scopes to an
+existing token, using the web interface at
+<https://github.com/settings/tokens>.
+
+ If you do that, then you might want to also set the variable
+accordingly, but note that Ghub only consults that when *creating* a new
+token. If you want to know a token’s effective scopes use the command
+‘ghub-token-scopes’, described in the next section.
+
+\1f
+File: ghub.info, Node: Using Ghub in a Package, Next: API, Prev: Using Ghub in Personal Scripts, Up: Top
+
+4 Using Ghub in a 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.
+
+ The scopes used by ‘PACKAGE’ have to be defined using the variable
+‘PACKAGE-github-token-scopes’, and 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)
+
+ -- Variable: PACKAGE-github-token-scopes
+
+ This variable defines the token scopes requested by the package
+ named ‘PACKAGE’. The doc-string should explain what the various
+ scopes are needed for to prevent users from giving ‘PACKAGE’ fewer
+ permissions than it absolutely needs and also to give them greater
+ confidence that ‘PACKAGE’ is only requesting the permissions that
+ it actually needs.
+
+ The value of this variable does not necessarily correspond to the
+ scopes that the respective token actually gives access to. There
+ is nothing that prevents users from changing the value *after*
+ creating the token or from editing the token’s scopes later on.
+
+ So it is pointless to check the value of this variable before
+ making a request. You also should not query the API to reliably
+ determine the supported tokens before making a query. Doing the
+ latter would mean that every request becomes two requests and that
+ the first request would have to be done using the user’s password
+ instead of a token.
+
+ -- Command: ghub-token-scopes
+
+ Because we cannot be certain that the user hasn’t messed up the
+ scopes, Ghub provides this command to make it easy to debug such
+ issues without having to rely on users being thoughtful enough to
+ correctly determine the used scopes manually.
+
+ Just tell users to run ‘M-x ghub-token-scopes’ and to provide the
+ correct values for the ‘HOST’, ‘USERNAME’ and ‘PACKAGE’ when
+ prompted, and to then post the output.
+
+ It is to be expected that users will occasionally mess that up so
+ this command outputs not only the scopes but also the user input so
+ that you can have greater confidence in the validity of the user’s
+ answer.
+
+ Scopes for USERNAME^PACKAGE@HOST: (SCOPE...)
+
+\1f
+File: ghub.info, Node: API, Next: GraphQL Support, Prev: Using Ghub in a Package, Up: Top
+
+5 API
+*****
+
+This section describes the Ghub API. In other words it describes the
+public functions and variables provided by the Ghub package and not the
+APIs of the supported forges, which can be accessed by using those
+functions. The forge APIs are documented at:
+
+ • Github: <https://developer.github.com/v3>
+
+ • Gitlab: <https://docs.gitlab.com/ee/api/README.html>
+
+ • Gitea: <https://docs.gitea.io/en-us/api-usage> and
+ <https://try.gitea.io/api/swagger>
+
+ • Gogs: <https://github.com/gogs/go-gogs-client/wiki>
+
+ • Bitbucket:
+ <https://developer.atlassian.com/bitbucket/api/2/reference>
+
+* Menu:
+
+* Making Requests::
+* Authentication::
+* Configuration Variables::
+
+\1f
+File: ghub.info, Node: Making Requests, Next: Authentication, Up: API
+
+5.1 Making 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 header is 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 make 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 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.
+
+ 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 an error
+ occurred, then it calls ‘ERRORBACK’, or if that is ‘nil’, then
+ ‘CALLBACK’. When no error occurred then it calls ‘CALLBACK’.
+ When making asynchronous requests, then no errors are
+ signaled, regardless of the value of ‘NOERROR’.
+
+ Both callbacks are called with four arguments.
+
+ • For ‘CALLBACK’, the combined value of the retrieved
+ pages. For ‘ERRORBACK’, the error that occured when
+ retrieving the last page.
+
+ • The headers of the last page as an alist.
+
+ • Status information provided by ‘url-retrieve’. Its
+ ‘:error’ property holds the same information as the first
+ argument to ‘ERRORBACK’.
+
+ • 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 retrieve 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’.
+
+ -- Variable: ghub-override-system-name
+
+ If non-nil, the value of this variable is used to override the
+ value returned by ‘system-name’ for the purpose of identifying the
+ local machine, which is necessary because Ghub uses separate tokens
+ for each machine. Also see *note Configuration Variables::.
+
+ -- Variable: ghub-github-token-scopes
+ -- Variable: PACKAGE-github-token-scopes
+
+ Such a variable defines the token scopes requested by the
+ respective package ‘PACKAGE’ given by the first word in the
+ variable name. ‘ghub’ itself is treated like any other package.
+ Also see *note Using Ghub in a Package::.
+
+ -- 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 the timeout exceeds. 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.
+
+ -- Function: ghub-graphql graphql &optional variables &key username
+ auth host callback
+
+ 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.
+
+\1f
+File: ghub.info, Node: Authentication, Next: Configuration Variables, Prev: Making Requests, Up: API
+
+5.2 Authentication
+==================
+
+ -- Command: ghub-create-token
+
+ This command creates a new token using the values it reads from the
+ user and then stores it according to the variable ‘auth-sources’.
+ It can also be called non-interactively, but you shouldn’t do that
+ yourself.
+
+ This is useful if you want to fully setup things before attempting
+ to make the initial request, if you want to provide fewer than the
+ requested scopes or customize ‘auth-sources’ first, or if something
+ has gone wrong when using the wizard that is used when making a
+ request without doing this first. (Note that instead of using this
+ command you can also just repeat the initial request after making
+ the desired adjustments — that is easier.)
+
+ This command reads, in order, the ‘HOST’ (Github instance), the
+ ‘USERNAME’, the ‘PACKAGE’, and the ‘SCOPES’ in the minibuffer,
+ providing reasonable default choices. ‘SCOPES’ defaults to the
+ scopes that ‘PACKAGE’ requests using the variable
+ ‘PACKAGE-github-token-scopes’.
+
+ -- Command: ghub-token-scopes
+
+ Users are free to give a token access to fewer scopes than what the
+ respective package requested. That can, of course, lead to issues,
+ and package maintainers have to be able to quickly determine if
+ such a (mis-)configuration is the root cause when users report
+ issues.
+
+ This command reads the required values in the minibuffer and then
+ shows a message containing these values along with the scopes of
+ the respective token. It also returns the scopes (only) when
+ called non-interactively. Also see *note Using Ghub in a
+ Package::.
+
+\1f
+File: ghub.info, Node: Configuration Variables, Prev: Authentication, Up: API
+
+5.3 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.)
+
+ -- Variable: ghub-override-system-name
+
+ Ghub uses a different token for each quadruple ‘(USERNAME PACKAGE
+ HOST LOCAL-MACHINE)’. Theoretically it could reuse tokens to some
+ extent but that would be more difficult to implement, less
+ flexible, and less secure (though slightly more convenient).
+
+ A token is identified on the respective Github instance (Github.com
+ or a Github Enterprise instance) using the pair ‘(PACKAGE .
+ LOCAL-MACHINE)’, or more precisely the string "Emacs package
+ PACKAGE @ LOCAL-MACHINE". ‘USERNAME’ and ‘HOST’ do not have to be
+ encoded because the token is stored for ‘USERNAME’ on ‘HOST’ and
+ cannot be used by another user and/or on another instance.
+
+ There is one potential problem though; for any given ‘(PACKAGE .
+ LOCAL-MACHINE)’ there can only be one token identified by "Emacs
+ package PACKAGE @ LOCAL-MACHINE"; Github does not allow multiple
+ tokens with the same description because it uses the description as
+ the identifier (it could use some hash instead, but alas it does
+ not).
+
+ If you have multiple machines and some of them have the same name,
+ then you should probably change that as this is not how things
+ ought to be. However if you dual-boot, then it might make sense to
+ give that machine the same name regardless of what operating system
+ you have booted into.
+
+ You could use the same token on both operating systems, but setting
+ that up might be somewhat difficult because it is not possible to
+ download an existing token from Github. You could, of course,
+ locally copy the token, but that is inconvenient and would make it
+ harder to only revoke the token used on your infected Windows
+ installation without also revoking it for your totally safe *BSD
+ installation.
+
+ Alternatively you can set this variable to a unique value, that
+ will then be used to identify the local machine instead of the
+ value returned by ‘system-name’.
+
+\1f
+File: ghub.info, Node: GraphQL Support, Next: Support for Other Forges, Prev: API, Up: Top
+
+6 GraphQL Support
+*****************
+
+ -- 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 ‘GRAPHEQL’ 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 am implementing 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 and 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
+ ‘graphql’ package is used to turn that into a GraphQL query string,
+ but the format is somewhat different than as documented for that
+ package. Also 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.
+
+ 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 used ‘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: Support for Other Forges, Prev: GraphQL Support, Up: Top
+
+7 Support for Other Forges
+**************************
+
+* Menu:
+
+* Forge Functions and Variables::
+* Forge Limitations and Notes::
+
+\1f
+File: ghub.info, Node: Forge Functions and Variables, Next: Forge Limitations and Notes, Up: Support for Other Forges
+
+7.1 Forge Functions and Variables
+=================================
+
+Originally Ghub supported only Github but now it also supports Gitlab,
+Gitea, Gogs and Bitbucket. The function ‘ghub-request’ and all the
+‘ghub-METHOD’ convenience wrappers default to acting on a Github forge
+but can be told to act on another forge using their FORGE argument.
+
+ The FORGE argument only specifies what kind of forge to act on, not
+which instance. 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.
+
+ 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.
+
+ These variables can be set globally and/or for a specific repository
+as explained in *note Configuration Variables:: with a focus on Github
+instances. To summarize:
+
+ • For <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.
+
+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 ‘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 ‘gitea’ 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
+
+ ‘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 a 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.
+
+\1f
+File: ghub.info, Node: Forge Limitations and Notes, Prev: Forge Functions and Variables, Up: Support for Other Forges
+
+7.2 Forge Limitations and Notes
+===============================
+
+ • The token creation wizard is only available for ‘github’ forges,
+ because all other forges do not support using the API to create an
+ API token. As a consequence, if the user makes a request and the
+ necessary token cannot be found, then that results in an error.
+ Tokens can be created at:
+
+ • Gitlab: <https://gitlab.com/profile/personal_access_tokens>
+
+ • Bitbucket:
+ <https://bitbucket.org/account/user/tarsius/app-passwords>
+
+ • Gitea: <https://localhost:3000/user/settings/applications>
+
+ • Gogs: <https://localhost:3000/user/settings/applications>
+
+ Also see *note Manually Creating and Storing a Token:: and *note
+ How Ghub uses Auth-Source::.
+
+ • As mentioned in the previous node, 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. Some resources only work if a
+ slash is appended while others only work if no slash is appended.
+ I am unable to access any private repositories and some resources
+ don’t work for me at all. Also the API is only RESTish; pagination
+ information is part of the response body instead of the header.
+ Due to such issues it is possible that I will eventually have to
+ remove support for Bitbucket altogether.
+
+ • 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
+ occationally 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
+Tag Table:
+Node: Top\7f764
+Node: Introduction\7f3279
+Node: Getting Started\7f6321
+Node: Setting the Username\7f9944
+Node: Interactively Creating and Storing a Token\7f11369
+Node: Manually Creating and Storing a Token\7f17009
+Node: How Ghub uses Auth-Source\7f18232
+Node: Using Ghub in Personal Scripts\7f20165
+Node: Using Ghub in a Package\7f21621
+Node: API\7f24239
+Node: Making Requests\7f25036
+Node: Authentication\7f39075
+Node: Configuration Variables\7f40920
+Node: GraphQL Support\7f44640
+Node: Support for Other Forges\7f51305
+Node: Forge Functions and Variables\7f51522
+Node: Forge Limitations and Notes\7f56041
+\1f
+End Tag Table
+
+\1f
+Local Variables:
+coding: utf-8
+End:
--- /dev/null
+;;; glab.el --- minuscule client library for the Gitlab API -*- lexical-binding: t -*-
+
+;; Copyright (C) 2016-2018 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; This file is not part of GNU Emacs.
+
+;; 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, 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.
+
+;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
+
+;;; 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-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" owner name)
+ nil :username username :auth auth :host host)))))
+
+;;; _
+(provide 'glab)
+;;; glab.el ends here
--- /dev/null
+;;; gogs.el --- minuscule client library for the Gogs API -*- lexical-binding: t -*-
+
+;; Copyright (C) 2016-2018 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; This file is not part of GNU Emacs.
+
+;; 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, 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.
+
+;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
+
+;;; 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 host that is used if `gogs.host' is not set.")
+
+;; 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
+;;; gtea.el --- minuscule client library for the Gitea API -*- lexical-binding: t -*-
+
+;; Copyright (C) 2016-2018 Jonas Bernoulli
+
+;; Author: Jonas Bernoulli <jonasNOSPAMbernoul.li>
+;; Homepage: https://github.com/magit/ghub
+;; Keywords: tools
+
+;; This file is not part of GNU Emacs.
+
+;; 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, 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.
+
+;; For a copy of the GPL see https://www.gnu.org/licenses/gpl.txt.
+
+;;; 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 host that is used if `gtea.host' is not set.")
+
+;; 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