+++ /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, "?" 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" (23082 53689 16798 734000))
-;;; 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)
-
-;;;***
-\f
-;;;### (autoloads nil nil ("ghub-pkg.el") (23082 53689 19979 831000))
-
-;;;***
-\f
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; End:
-;;; ghub-autoloads.el ends here
+++ /dev/null
-(define-package "ghub" "20171207.1537" "minuscule client library for the Github API"
- '((emacs "24.4")
- (let-alist "1.0.5"))
- :url "https://github.com/magit/ghub" :keywords
- '("tools"))
-;; Local Variables:
-;; no-byte-compile: t
-;; End:
+++ /dev/null
-;;; ghub.el --- minuscule client library for the Github API -*- lexical-binding: t -*-
-
-;; Copyright (C) 2016-2017 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.
-
-;; It also 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.
-
-;; 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 adaption. 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)
-
-(eval-when-compile (require 'subr-x))
-
-(defvar url-http-end-of-headers)
-(defvar url-http-response-status)
-
-;;; Settings
-
-(defconst ghub-default-host "api.github.com")
-
-(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 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 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
-;;;; API
-
-(defvar ghub-response-headers nil
- "The headers returned in response to the last request.
-`ghub-request' returns the response body and stores the
-response header in this variable.")
-
-(defvar ghub-raw-response-body nil)
-
-(cl-defun ghub-head (resource &optional params
- &key query payload headers
- unpaginate noerror reader
- username auth host)
- "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
- :unpaginate unpaginate :noerror noerror :reader reader
- :username username :auth auth :host host))
-
-(cl-defun ghub-get (resource &optional params
- &key query payload headers
- unpaginate noerror reader
- username auth host)
- "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
- :unpaginate unpaginate :noerror noerror :reader reader
- :username username :auth auth :host host))
-
-(cl-defun ghub-put (resource &optional params
- &key query payload headers
- unpaginate noerror reader
- username auth host)
- "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
- :unpaginate unpaginate :noerror noerror :reader reader
- :username username :auth auth :host host))
-
-(cl-defun ghub-post (resource &optional params
- &key query payload headers
- unpaginate noerror reader
- username auth host)
- "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
- :unpaginate unpaginate :noerror noerror :reader reader
- :username username :auth auth :host host))
-
-(cl-defun ghub-patch (resource &optional params
- &key query payload headers
- unpaginate noerror reader
- username auth host)
- "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
- :unpaginate unpaginate :noerror noerror :reader reader
- :username username :auth auth :host host))
-
-(cl-defun ghub-delete (resource &optional params
- &key query payload headers
- unpaginate noerror reader
- username auth host)
- "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
- :unpaginate unpaginate :noerror noerror :reader reader
- :username username :auth auth :host host))
-
-(define-error 'ghub-error "Ghub Error")
-(define-error 'ghub-http-error "HTTP Error" 'ghub-error)
-(define-error 'ghub-301 "Moved Permanently" 'ghub-http-error)
-(define-error 'ghub-400 "Bad Request" 'ghub-http-error)
-(define-error 'ghub-401 "Unauthorized" 'ghub-http-error)
-(define-error 'ghub-403 "Forbidden" 'ghub-http-error)
-(define-error 'ghub-404 "Not Found" 'ghub-http-error)
-(define-error 'ghub-422 "Unprocessable Entity" 'ghub-http-error)
-
-(cl-defun ghub-request (method resource &optional params
- &key query payload headers
- unpaginate noerror reader
- username auth host)
- "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.
-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 UNPAGINATE is non-nil, then make multiple requests if necessary
- to get all items at RESOURCE.
-If NOERROR is non-nil, then do not raise an error if the request
- fails and return nil instead.
-If READER is non-nil, then it is used to read and return from the
- response buffer. The default is `ghub--read-json-response'.
- For the very few resources that do not return json, you might
- want to use `ghub--read-raw-response'.
-
-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 also.
-
- 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."
- (unless (string-prefix-p "/" resource)
- (setq resource (concat "/" resource)))
- (unless host
- (setq host (ghub--host)))
- (cond
- ((not params))
- ((memq 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 (and payload (not (stringp payload)))
- (setq payload (encode-coding-string (json-encode-list payload) 'utf-8)))
- (let* ((qry (and query (concat "?" (ghub--url-encode-params query))))
- (buf (let ((url-request-extra-headers
- `(("Content-Type" . "application/json")
- ,@(and (not (eq auth 'none))
- (list (cons "Authorization"
- (ghub--auth host auth username))))
- ,@headers))
- ;; Encode in case caller used (symbol-name 'GET). #35
- (url-request-method (encode-coding-string method 'utf-8))
- (url-request-data payload))
- (url-retrieve-synchronously
- (concat "https://" host resource qry)))))
- (unwind-protect
- (with-current-buffer buf
- (set-buffer-multibyte t)
- (let (link body)
- (goto-char (point-min))
- (let (headers)
- (while (re-search-forward "^\\([^:]*\\): \\(.+\\)"
- url-http-end-of-headers t)
- (push (cons (match-string 1)
- (match-string 2))
- headers))
- (and (setq link (cdr (assoc "Link" headers)))
- (setq link (car (rassoc
- (list "rel=\"next\"")
- (mapcar (lambda (elt) (split-string elt "; "))
- (split-string link ",")))))
- (string-match "[?&]page=\\([^&>]+\\)" link)
- (setq link (match-string 1 link)))
- (setq ghub-response-headers (nreverse headers)))
- (unless url-http-end-of-headers
- (error "ghub: url-http-end-of-headers is nil when it shouldn't"))
- (goto-char (1+ url-http-end-of-headers))
- (setq body (funcall (or reader 'ghub--read-json-response)))
- (unless (or noerror (= (/ url-http-response-status 100) 2))
- (let ((data (list method resource qry payload body)))
- (pcase url-http-response-status
- (301 (signal 'ghub-301 data))
- (400 (signal 'ghub-400 data))
- (401 (signal 'ghub-401 data))
- (403 (signal 'ghub-403 data))
- (404 (signal 'ghub-404 data))
- (422 (signal 'ghub-422 data))
- (_ (signal 'ghub-http-error
- (cons url-http-response-status data))))))
- (if (and link unpaginate)
- (nconc body
- (ghub-request
- method resource nil
- :query (cons (cons 'page link)
- (cl-delete 'page query :key #'car))
- :payload payload
- :headers headers
- :unpaginate t :noerror noerror :reader reader
- :username username :auth auth :host host))
- body)))
- (kill-buffer buf))))
-
-(defun ghub-wait (resource &optional username auth host duration)
- "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 subequent attemts are made after
-waiting as long 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 (ignore-errors (ghub-get resource nil
- :username username
- :auth auth
- :host host)))
- (message "Waited (%3ss of %ss) for %s..." total duration resource)
- (if (= total duration)
- (signal 'ghub-error
- (list (format "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))))))))
-
-;;;; Internal
-
-(defun ghub--read-json-response ()
- (and (not (eobp))
- (let ((json-object-type 'alist)
- (json-array-type 'list)
- (json-key-type 'symbol)
- (json-false nil)
- (json-null nil))
- (json-read-from-string (ghub--read-raw-response)))))
-
-(defun ghub--read-raw-response ()
- (and (not (eobp))
- (setq ghub-raw-response-body
- (decode-coding-string
- (buffer-substring-no-properties (point) (point-max))
- 'utf-8))))
-
-(defun ghub--url-encode-params (params)
- (mapconcat (lambda (param)
- (concat (url-hexify-string (symbol-name (car param))) "="
- (url-hexify-string (cdr param))))
- 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))))
- ;; 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))
-
-;;;; Internal
-
-(defun ghub--auth (host auth &optional username)
- (unless username
- (setq username (ghub--username host)))
- (if (eq auth 'basic)
- (ghub--basic-auth host username)
- (concat "token "
- (encode-coding-string
- (cl-typecase auth
- (string auth)
- (null (ghub--token host username 'ghub))
- (symbol (ghub--token host username auth))
- (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--token (host username package &optional nocreate)
- (let ((user (ghub--ident username package)))
- (or (ghub--auth-source-get :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.
- (auth-source-forget (list :host host :user user))
- (and (not nocreate)
- (ghub--confirm-create-token host username package))))))
-
-(defun ghub--host ()
- (or (ignore-errors (car (process-lines "git" "config" "github.host")))
- ghub-default-host))
-
-(defun ghub--username (host)
- (let ((var (if (string-prefix-p "api.github.com" host)
- "github.user"
- (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)
- (symbol-value (intern (format "%s-github-token-scopes" package))))
-
-(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 (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 locally according to `auth-sources':\n %S
- Store on Github as:\n %S
-
-WARNING: If you have enabled two-factor authentication then
- you have to abort and create the token manually.
-
-If in doubt, then abort and view the documentation first.
-
-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")
- auth-sources ident))
- (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" nil
- :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")))))
-
-(defun ghub--auth-source-get (key:s &rest spec)
- (declare (indent 1))
- (let ((plist (car (apply #'auth-source-search :max 1 spec))))
- (cl-flet ((value (k) (let ((v (plist-get plist k)))
- (if (functionp v) (funcall v) v))))
- (if (listp key:s)
- (mapcar #'value key:s)
- (value key:s)))))
-
-(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)))
-
-;;; ghub.el ends soon
-(provide 'ghub)
-;; Local Variables:
-;; indent-tabs-mode: nil
-;; End:
-;;; ghub.el ends here
+++ /dev/null
-This is ghub.info, produced by makeinfo version 5.2 from ghub.texi.
-
- Copyright (C) 2017 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.
-
- It also 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.
-
- 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 adaption. 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 1.3.0 (v1.3.0-22-g45cd8c8+1).
-
- Copyright (C) 2017 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::
-* Limitations::
-* Getting Started::
-* Using Ghub in Personal Scripts::
-* Using Ghub in a Package::
-* API::
-
-— 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: Limitations, 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.
-
- It also 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.
-
- 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 adaption.
-
- Fancier interfaces can 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 does no longer also dictate 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
-expects 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 adapted) 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 defined in some package and 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: Limitations, Next: Getting Started, Prev: Introduction, Up: Top
-
-2 Limitations
-*************
-
-As mentioned, Ghub restricts itself to provide core functionality and
-leaves it to other libraries to do opinionated things on top of that.
-But currently there also are some limitations that concern the core
-parts. Obviously these limitations have to be lifted to maximize
-adaption and so they will.
-
- These secondary features already exist (just mentioning them, so you
-don’t assume that they don’t):
-
- • Support for Github Enterprise instances.
-
- • Support for token scope validation.
-
- These features will likely never be implemented in Ghub:
-
- • Functions for particular resources.
-
- • Using something other than json-like alists to handle what the
- Github API calls "parameters".
-
- • Retrieving an existing token. Github does not support it so we
- cannot do it. If you lose your local copy of a token, then you
- have to replace it with a new one.
-
- These features are not supported yet:
-
- • Asynchronous requests are not supported yet.
-
- • The Github GraphQL API is not supported yet.
-
- • If a request fails due to a missing scope, then that isn’t handled
- by ‘ghub-request’ yet. For now package authors just have to hope
- that users don’t mess with scopes or handle such errors themselves.
-
- Likewise, other errors too should be handled by Ghub.
-
- • Packages cannot request additional scopes.
-
- Once a token has been created a package cannot request new scopes
- that it previously did not need. I am not quite sure yet whether
- Ghub should support that. An alternative would be to let packages
- at least invalidate their own tokens.
-
- • Other Git forges (Gitlab, Bitbucket…) are not supported.
-
- Support for other forges will likely be implemented using *other*
- libraries that are very similar to this library. Whether common
- code will be factored out into a library to be used by all these
- libraries or whether some code duplication is to be preferred, has
- not been decided yet.
-
-\1f
-File: ghub.info, Node: Getting Started, Next: Using Ghub in Personal Scripts, Prev: Limitations, Up: Top
-
-3 Getting Started
-*****************
-
-If you would like to use a package that uses this library, then just do
-so. If the necessary information isn’t available when it attempts to
-make a request, then you will be asked to provide it.
-
-* 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
-
-3.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
-
-3.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.
-
- Unfortunately the wizard does not work (yet?) if you have enabled
-two-factor authentication, in which case the token has to be created and
-stored manually.
-
- 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 locally according to `auth-sources':
- ("~/.authinfo" "~/.authinfo.gpg" "~/.netrc")
- Store on Github as:
- "Emacs package FOO @ LOCAL-MACHINE"
-
- WARNING: If you have enabled two-factor authentication
- then you have to create the token manually.
-
- If in doubt, then abort and view the documentation first.
-
- 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:
-
- • Unfortunately Ghub cannot know whether you have enabled two-factor
- authentication without making a request using basic (password)
- authentication, which it cannot do if you have enabled two-factor
- authentication. It also cannot determine whether you have enabled
- two-factor authentication depending on whether a request fails or
- not because there are other reasons why a request might fail.
-
- So this warning is always displayed and you are expected to know
- whether you have enabled two-factor authentication or not. If you
- have, then you must abort and see *note Manually Creating and
- Storing a Token::.
-
- • ‘Host’ usually is "api.github.com" and that’s 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 as described in the next section.
-
- • ‘User’ should be your Github.com (or Github Enterprise instance)
- username. If it is something else, then you made a mistake at the
- first prompt or during the step describe in the previous section
- and have to refer to that in order to fix this issue.
-
- • ‘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: ... unencrypted ... TODO implement ...
-
- 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 used 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 FOO @ 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.
-
- 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.
-
-\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
-
-3.3 Manually Creating and Storing a Token
-=========================================
-
-If you have enabled two-factor authentication, then you have to create
-and store access tokens yourself. You might also prefers to always do
-it manually to have more control than when relying on the setup wizard.
-
- 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>.
-
- Finally store the token in a place where Ghub looks for it as
-describe in *note How Ghub uses Auth-Source::.
-
-\1f
-File: ghub.info, Node: How Ghub uses Auth-Source, Prev: Manually Creating and Storing a Token, Up: Getting Started
-
-3.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".
-
- An entry in one of the three mentioned files would then look like
-this:
-
- machine api.github.com login tarsius^ghub password 012345abcdef...
-
-\1f
-File: ghub.info, Node: Using Ghub in Personal Scripts, Next: Using Ghub in a Package, Prev: Getting Started, Up: Top
-
-4 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
-
-5 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 what 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 need.
-
- 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 does not only output 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, Prev: Using Ghub in a Package, Up: Top
-
-6 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
-
-6.1 Making Requests
-===================
-
- -- Function: ghub-request method resource &optional params &key query
- payload headers unpaginate noerror reader username auth host
-
- This function make 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 in
- 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 are stored in the ‘cdr’ (not
- the ‘cadr’) and can be strings, integers and 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.
-
- • 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 UNPAGINATE is non-nil, then this function makes multiple
- requests if necessary to get all items at RESOURCE.
-
- • If NOERROR is non-nil, then no error is raised if the request
- fails and ‘nil’ is returned instead.
-
- • If READER is non-nil, then it is used to read and return from
- the response buffer. The default is
- ‘ghub--read-json-response’. For the very few resources that
- do not return json, you might want to use
- ‘ghub--read-raw-response’.
-
- • 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, 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 also.
-
- 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.
-
- -- 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 header use this variable after
- ‘ghub-request’ has returned.
-
- -- 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 define 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
- -- Function: ghub-get resource &optional params &key query payload
- headers unpaginate noerror reader username auth host
-
- 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
- -- Function: ghub-post resource &optional params &key query payload
- headers unpaginate noerror reader username auth host
- -- Function: ghub-patch resource &optional params &key query payload
- headers unpaginate noerror reader username auth host
- -- Function: ghub-delete resource &optional params &key query payload
- headers unpaginate noerror reader username auth host
-
- 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 username auth host duration
-
- 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 mention this when describing
- an affected resource.
-
- If you want to do something with some resource right after creating
- 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 for how many seconds to wait at most, 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 often that will actually
- succeed. If not, then another attempt is made after two seconds,
- and each subsequent attempt is made after waiting as long as we
- already waited between all preceding attempts combined.
-
- See ‘ghub-request’’s documentation above for information about the
- other arguments.
-
-\1f
-File: ghub.info, Node: Authentication, Next: Configuration Variables, Prev: Making Requests, Up: API
-
-6.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 variable ‘auth-sources’. It
- can also be called non-interactively, but you shouldn’t do that
- yourself.
-
- This is useful if you want 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 that 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
-
-6.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 the 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 (define in
- the constant ‘ghub-default-host-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
- extend 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 thing 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
-Tag Table:
-Node: Top\7f757
-Node: Introduction\7f2786
-Node: Limitations\7f5322
-Node: Getting Started\7f7463
-Node: Setting the Username\7f7965
-Node: Interactively Creating and Storing a Token\7f9390
-Node: Manually Creating and Storing a Token\7f15345
-Node: How Ghub uses Auth-Source\7f16433
-Node: Using Ghub in Personal Scripts\7f18003
-Node: Using Ghub in a Package\7f19452
-Node: API\7f22017
-Node: Making Requests\7f22444
-Node: Authentication\7f31001
-Node: Configuration Variables\7f32800
-\1f
-End Tag Table
-
-\1f
-Local Variables:
-coding: utf-8
-End:
--- /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
+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.