diff -Nru magithub-0.1.5/CONTRIBUTING.md magithub-0.1.7/CONTRIBUTING.md --- magithub-0.1.5/CONTRIBUTING.md 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/CONTRIBUTING.md 2018-06-03 00:42:26.000000000 +0000 @@ -26,6 +26,18 @@ you. As you write your issue, please follow the instructions the issue template provides. A stack trace helps tremendously! +Sometimes there are intermittent bugs that cannot be reproduced +easily. Anyone who develops software can tell you that it is very +difficult to debug an issue that you cannot see. For this reason, the +'unconfirmed' label indicates an issue that hasn't been reproduced by +a maintainer and the 'waiting' label indicates an issue is waiting for +some response from the folks who are actually experiencing it. Any +issue that has had the 'waiting' label for more than two weeks can be +closed as 'not reproducible'. If you are still having the issue after +that time, please do re-open the issue! I don't mean to say that bugs +are features, but I don't want to give a false first impression of +bugginess. + ## Suggesting Features Feature requests are always welcome! Pull requests even more so. diff -Nru magithub-0.1.5/debian/changelog magithub-0.1.7/debian/changelog --- magithub-0.1.5/debian/changelog 2018-06-03 00:12:19.000000000 +0000 +++ magithub-0.1.7/debian/changelog 2018-06-11 22:23:49.000000000 +0000 @@ -1,3 +1,16 @@ +magithub (0.1.7-1) unstable; urgency=medium + + * New upstream release (Closes: #900822) + * debian/patches/: patchset dropped + * debian/control: + - drop Depends on elpa-s (already managed) + - Vcs-* fields updated pointing to salsa + - Maintainer field updated + - Build-Depends updated (texinfo and texlive added) + - S-V bump 4.1.3 -> 4.1.4 (no changes needed) + + -- Matteo F. Vescovi Tue, 12 Jun 2018 00:23:49 +0200 + magithub (0.1.5-2) unstable; urgency=medium * Team upload. diff -Nru magithub-0.1.5/debian/control magithub-0.1.7/debian/control --- magithub-0.1.5/debian/control 2018-06-03 00:12:19.000000000 +0000 +++ magithub-0.1.7/debian/control 2018-06-11 22:18:01.000000000 +0000 @@ -1,18 +1,18 @@ Source: magithub Section: lisp Priority: optional -Maintainer: Debian Emacs addons team +Maintainer: Debian Emacsen team Uploaders: Matteo F. Vescovi -Build-Depends: debhelper (>= 11~), dh-elpa -Standards-Version: 4.1.3 +Build-Depends: debhelper (>= 11~), dh-elpa, texinfo, texlive +Standards-Version: 4.1.4 Homepage: https://github.com/vermiculus/magithub -Vcs-Browser: https://anonscm.debian.org/cgit/pkg-emacsen/pkg/magithub.git -Vcs-Git: https://anonscm.debian.org/git/pkg-emacsen/pkg/magithub.git +Vcs-Browser: https://salsa.debian.org/emacsen-team/magithub +Vcs-Git: https://salsa.debian.org/emacsen-team/magithub.git Testsuite: autopkgtest-pkg-elpa Package: elpa-magithub Architecture: all -Depends: ${elpa:Depends}, ${misc:Depends}, elpa-s +Depends: ${elpa:Depends}, ${misc:Depends} Recommends: emacs (>= 47.0) Conflicts: emacs24 Enhances: emacs, emacs25 diff -Nru magithub-0.1.5/debian/patches/0001-Fix_package-version.patch magithub-0.1.7/debian/patches/0001-Fix_package-version.patch --- magithub-0.1.5/debian/patches/0001-Fix_package-version.patch 2018-06-03 00:12:19.000000000 +0000 +++ magithub-0.1.7/debian/patches/0001-Fix_package-version.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -Author: "Matteo F. Vescovi" -Description: Fix Package-Version entry -Date: 2018-01-10 - -diff --git a/magithub.el b/magithub.el -index 5157970..f70655a 100644 ---- a/magithub.el -+++ b/magithub.el -@@ -6,7 +6,7 @@ - ;; Keywords: git, tools, vc - ;; Homepage: https://github.com/vermiculus/magithub - ;; Package-Requires: ((emacs "25") (magit "2.8") (s "1.12.0") (ghub+ "0.2") (git-commit "2.8") (markdown-mode "2.3")) --;; Package-Version: 0.1.4 -+;; Package-Version: 0.1.5 - - ;; This program is free software; you can redistribute it and/or modify - ;; it under the terms of the GNU General Public License as published by diff -Nru magithub-0.1.5/debian/patches/series magithub-0.1.7/debian/patches/series --- magithub-0.1.5/debian/patches/series 2018-06-03 00:12:19.000000000 +0000 +++ magithub-0.1.7/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -0001-Fix_package-version.patch diff -Nru magithub-0.1.5/.github/PULL_REQUEST_TEMPLATE.md magithub-0.1.7/.github/PULL_REQUEST_TEMPLATE.md --- magithub-0.1.5/.github/PULL_REQUEST_TEMPLATE.md 1970-01-01 00:00:00.000000000 +0000 +++ magithub-0.1.7/.github/PULL_REQUEST_TEMPLATE.md 2018-06-03 00:42:26.000000000 +0000 @@ -0,0 +1,9 @@ +Thanks for contributing! It's really appreciated :smile: + +Here are some things to remember: + +- In the past, commit messages have mentioned issues directly. I've found this to be more trouble than it's worth -- especially when code goes under review and is edited and subsequently rebased. Try to leave issue/PR references out of commit messages -- opting instead to use the PR body to link the necessary records in the bug tracker. + +- If you're fixing a bug or implementing a new feature, add something to the appropriate `RelNotes/*.org` file. + +- If it's a breaking change (i.e., it could reasonably break someone's configuration), document that in the release notes as well. diff -Nru magithub-0.1.5/.gitignore magithub-0.1.7/.gitignore --- magithub-0.1.5/.gitignore 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/.gitignore 2018-06-03 00:42:26.000000000 +0000 @@ -1,3 +1,9 @@ *.elc *~ /.cask +/magithub.html +/magithub.pdf +/magithub/ +.elpa/ +/build.log +/magithub-autoloads.el diff -Nru magithub-0.1.5/magithub-ci.el magithub-0.1.7/magithub-ci.el --- magithub-0.1.5/magithub-ci.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-ci.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ ;;; magithub-ci.el --- Show CI status as a magit-status header -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Sean Allred +;; Copyright (C) 2016-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: tools @@ -26,47 +26,22 @@ (require 'magit) (require 'magit-section) -(require 'magit-popup) (require 'dash) (require 's) (require 'magithub-core) (require 'magithub-issue) -(defun magithub-ci-enabled-p () - "Non-nil if CI is enabled for this repository. -If magithub.ci.enabled is not set, CI is considered to be enabled." - (member (magit-get "magithub" "ci" "enabled") '(nil "yes" "true"))) -(defun magithub-ci--set-enabled (val) - (magit-set (if val "true" "false") "magithub" "ci" "enabled") - (message (concat "Status integration " - (if val "enabled" "disabled") - " in this repository."))) -(defun magithub-ci-disable () - "Disable CI for this repository." - (magithub-ci--set-enabled nil)) -(defun magithub-ci-enable () - "Enable CI for this repository." - (magithub-ci--set-enabled t)) - +;;;###autoload (defun magithub-maybe-insert-ci-status-header () - "If this is a Github repository, insert the CI status header." - (when (and (magithub-ci-enabled-p) + "If this is a GitHub repository, insert the CI status header." + (when (and (magithub-settings-include-status-p) (magithub-usable-p) (let ((b (magit-get-current-branch))) (or (magit-get-upstream-remote b) (magit-get-push-remote b)))) (magithub-insert-ci-status-header))) -(defun magithub-ci-toggle () - "Toggle CI integration." - (interactive) - (if (magithub-ci-enabled-p) - (magithub-ci-disable) - (magithub-ci-enable)) - (when (derived-mode-p 'magit-status-mode) - (magit-refresh))) - (defvar magithub-ci--status-last-refreshed nil "An alist of alists: repos to refs to times. For efficiency, repos are represented only by their full names.") @@ -128,7 +103,7 @@ (setq branch (or branch (magit-get-current-branch))) (if-let ((pull-request (or (magithub-pull-request-branch->pr--gitconfig branch) - (and (not (magithub-offline-p)) + (and (magithub-online-p) (with-demoted-errors "Error: %S" (magithub-pull-request-branch->pr--ghub branch)))))) (let-alist pull-request .head.sha) @@ -141,8 +116,7 @@ (if (magit-rebase-in-progress-p) ;; avoid rate-limiting ourselves (magithub-debug-message "skipping CI status checks while in rebase") - (condition-case _ - (magithub-cache :ci-status + (or (magithub-cache :ci-status `(magithub-request (ghubp-get-repos-owner-repo-commits-ref-status ',(magithub-repo) ,ref)) @@ -156,10 +130,9 @@ (current-time)) (message "(magithub): new statuses retrieved -- overall: %s" (alist-get 'state status)))) - (ghub-404 - '((state . "error") - (total_count . 0) - (magithub-message . "ref not found on remote"))))))) + '((state . "error") + (total_count . 0) + (magithub-message . "ref not found on remote")))))) (defvar magithub-ci-status-alist '((nil . ((display . "None") (face . magithub-ci-no-status))) @@ -170,12 +143,23 @@ (defconst magithub-ci-status--unknown '((face . magithub-ci-unknown))) +(defun magithub-ci-pr-status (pr) + (interactive (list (thing-at-point 'github-pull-request))) + (unless pr + (user-error "no pr at point")) + (message "state of #%d: %s" + (let-alist pr .number) + (let-alist (ghubp-get-repos-owner-repo-commits-ref-status + (magithub-repo) + (let-alist pr .head.sha)) + .state))) + (defun magithub-ci-visit (ref) "Jump to CI with `browse-url'." (interactive (list (magit-rev-parse (magit-commit-at-point)))) (let (done) (when (null ref) - (pcase (magit-section-value (magit-current-section)) + (pcase (oref (magit-current-section) value) (`(magithub-ci-url . ,url) (browse-url url) (setq done t)) @@ -212,13 +196,10 @@ "Keymap for `magithub-ci-status' header section.") (defun magithub-ci-refresh () - "Invalidate the CI cache and refresh the buffer. -If EVEN-IF-OFFLINE is non-nil, we'll still refresh (that is, -we'll hit the API) if Magithub is offline." + "Invalidate the CI cache and refresh the buffer." (interactive) - (when (and (magithub-offline-p) - (not (y-or-n-p "Magithub offline; refresh statuses anyway? "))) - (user-error "Aborted")) + (unless (magithub-online-p) + (magithub-confirm 'ci-refresh-when-offline)) (magithub-cache-without-cache :ci-status (magithub-ci-status (magithub-ci-status--get-default-ref))) (magit-refresh)) @@ -285,8 +266,5 @@ (propertize (format " %s" context) 'face 'magit-dimmed)))))) -(magithub--deftoggle magithub-toggle-ci-status-header "CI header" t - magit-status-headers-hook #'magithub-maybe-insert-ci-status-header) - (provide 'magithub-ci) ;;; magithub-ci.el ends here diff -Nru magithub-0.1.5/magithub-comment.el magithub-0.1.7/magithub-comment.el --- magithub-0.1.5/magithub-comment.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-comment.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ -;;; magithub-comment.el --- tools for comments -*- lexical-binding: t; -*- +;;; magithub-comment.el --- tools for comments -*- lexical-binding: t; -*- -;; Copyright (C) 2017 Sean Allred +;; Copyright (C) 2017-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: lisp @@ -25,6 +25,8 @@ ;;; Code: (require 'magit) +(require 'markdown-mode) +(require 'thingatpt) (require 'magithub-core) (require 'magithub-repo) @@ -40,10 +42,11 @@ (define-key m [remap magit-delete-thing] #'magithub-comment-delete) (define-key m (kbd "SPC") #'magithub-comment-view) (define-key m [remap magithub-reply-thing] #'magithub-comment-reply) + (define-key m [remap magithub-edit-thing] #'magithub-comment-edit) m)) (defun magithub-comment-browse (comment) - (interactive (list (magithub-thing-at-point 'comment))) + (interactive (list (thing-at-point 'github-comment))) (unless comment (user-error "No comment found")) (let-alist comment @@ -51,7 +54,7 @@ (declare-function face-remap-remove-relative "face-remap.el" (cookie)) (defun magithub-comment-delete (comment) - (interactive (list (magithub-thing-at-point 'comment))) + (interactive (list (thing-at-point 'github-comment))) (unless comment (user-error "No comment found")) (let ((repo (magithub-comment-source-repo comment)) @@ -67,9 +70,7 @@ ;; 'magithub-deleted-thing ))) - (unwind-protect - (unless (yes-or-no-p "Are you sure you wish to delete this comment? ") - (user-error "Aborted")) + (unwind-protect (magithub-confirm 'comment-delete) (face-remap-remove-relative cookie))) (magithub-request (ghubp-delete-repos-owner-repo-issues-comments-id repo comment)) @@ -85,38 +86,6 @@ (defun magithub-comment-source-repo (comment) (magithub-issue-repo (magithub-comment-source-issue comment))) -(defun magithub-comment-draft-file (repo issue) - "Get the filepath of the comment draft for REPO/ISSUE." - (let-alist issue - (expand-file-name (format "%s-comment" .number) - (magithub-repo-data-dir repo)))) - -(defun magithub-comment-draft-save (repo issue comment) - "Save a draft reply to REPO/ISSUE as COMMENT." - (interactive (list (magithub-thing-at-point 'repo) - (magithub-thing-at-point 'issue) - (buffer-string))) - (make-directory (magithub-repo-data-dir repo) t) - (with-temp-buffer - (insert comment) - (write-file (magithub-comment-draft-file repo issue))) - (set-buffer-modified-p nil) - (message "Draft saved")) - -(defun magithub-comment-draft-load (repo issue) - "Load the draft reply to REPO/ISSUE." - (let ((file (magithub-comment-draft-file repo issue))) - (and (file-exists-p file) - (with-temp-buffer - (insert-file-contents file) - (buffer-string))))) - -(defun magithub-comment-draft-delete (repo issue) - "Delete the draft file for REPO/ISSUE if it exists." - (let ((f (magithub-comment-draft-file repo issue))) - (when (file-exists-p f) - (delete-file f magit-delete-by-moving-to-trash)))) - (defun magithub-comment-insert (comment) "Insert a single issue COMMENT." (let-alist comment @@ -135,12 +104,13 @@ (defvar magithub-gfm-view-mode-map (let ((m (make-sparse-keymap))) - (define-key m [remap magit-mode-bury-buffer] #'magithub-comment-view-close) + (define-key m [remap kill-this-buffer] #'magithub-comment-view-close) m) "Keymap for `magithub-gfm-view-mode'.") +(declare-function gfm-view-mode "ext:markdown-mode.el") (define-derived-mode magithub-gfm-view-mode gfm-view-mode "M:GFM-View" - "Major mode for viewing Github markdown content.") + "Major mode for viewing GitHub markdown content.") (defvar-local magithub-comment-view--parent-buffer nil "The 'parent' buffer of the current comment-view. @@ -150,31 +120,23 @@ (defun magithub-comment-view (comment) "View COMMENT in a new buffer." - (interactive (list (magithub-thing-at-point 'comment))) + (interactive (list (thing-at-point 'github-comment))) (let ((prev (current-buffer))) (with-current-buffer (get-buffer-create "*comment*") (magithub-gfm-view-mode) (setq-local magithub-comment-view--parent-buffer prev) - (insert (magithub-wash-gfm (alist-get 'body comment))) + (let ((inhibit-read-only t)) + (erase-buffer) + (insert (magithub-wash-gfm (alist-get 'body comment)))) (goto-char 0) - (read-only-mode) - (switch-to-buffer-other-window (current-buffer))))) + (magit-display-buffer (current-buffer))))) (defun magithub-comment-view-close () "Close the current buffer." (interactive) - (when (derived-mode-p 'magithub-gfm-view-mode) - (let ((buf (current-buffer)) - (prev magithub-comment-view--parent-buffer)) - (when-let ((window (get-buffer-window prev))) - (select-window window)) - (kill-buffer buf)))) - -(defvar magithub-comment-edit-map - (let ((m (make-sparse-keymap))) - (define-key m [remap save-buffer] #'magithub-comment-draft-save) - m) - "Local map for comment-edit buffers.") + (let ((oldbuf magithub-comment-view--parent-buffer)) + (kill-this-buffer) + (magit-display-buffer oldbuf))) ;;;###autoload (defun magithub-comment-new (issue &optional discard-draft initial-content) @@ -189,36 +151,60 @@ (unless (derived-mode-p 'magithub-issue-view-mode) (magithub-issue-view issue))))) (let* ((issueref (magithub-issue-reference issue)) - (repo (magithub-issue-repo issue)) - (draft (magithub-comment-draft-load repo issue))) - (when (and draft discard-draft) - (with-current-buffer (get-buffer-create " *draft*") - (insert draft) - (view-buffer-other-window (current-buffer)) - (when (yes-or-no-p "Discard this draft? ") - (magithub-comment-draft-delete repo issue) - (setq draft nil)) - (kill-buffer (current-buffer)))) - (magithub-edit-new - (concat "reply to " issueref) - #'magithub-issue-comment-submit - #'magithub-issue-comment-cancel - magithub-comment-edit-map - (lambda () - (setq-local magithub-issue issue) - (setq-local magithub-repo repo) - (magit-set-header-line-format - (substitute-command-keys - (format "replying to %s | %s | %s" - issueref - "submit: \\[magithub-edit-submit]" - "cancel: \\[magithub-edit-cancel]"))) - (if (and (null draft) initial-content) - (insert initial-content) - (when draft - (insert draft) - (message "Loaded existing draft"))) - (goto-char (point-max)))))) + (repo (magithub-issue-repo issue))) + (with-current-buffer + (magithub-edit-new (concat "reply to " issueref) + :header (concat "replying to " issueref) + :submit #'magithub-issue-comment-submit + :content initial-content + :prompt-discard-draft discard-draft + :file (magithub-comment--draft-file issue repo)) + (setq-local magithub-issue issue) + (setq-local magithub-repo repo) + (magit-display-buffer (current-buffer))))) + +(defun magithub-comment--draft-file (issue repo) + "Get an appropriate draft file for ISSUE in REPO." + (let-alist issue + (expand-file-name (format "%s-comment" .number) + (magithub-repo-data-dir repo)))) + +(defun magithub-comment--submit-edit (comment repo new-body) + (interactive (list (thing-at-point 'github-comment) + (thing-at-point 'github-repository) + (buffer-string))) + (when (string= new-body "") + (user-error "Can't post an empty comment; try deleting it instead")) + (magithub-confirm 'comment-edit) + (magithub-request + (ghubp-patch-repos-owner-repo-issues-comments-id + repo comment + `((body . ,new-body))))) + +(defun magithub-comment-edit (comment issue repo) + "Edit COMMENT." + (interactive (list (thing-at-point 'github-comment) + (or (thing-at-point 'github-issue) + (thing-at-point 'github-pull-request)) + (thing-at-point 'github-repository))) + (let ((updated (magithub-request (ghubp-follow-get (alist-get 'url comment))))) + (with-current-buffer + (magithub-edit-new (format "*%s: editing comment by %s (%s)*" + (magithub-issue-reference issue) + (let-alist comment .user.login) + (alist-get 'id comment)) + :submit #'magithub-comment--submit-edit + :content (alist-get 'body updated) + :file (magithub-comment--draft-file issue repo)) + (setq-local magithub-issue issue) + (setq-local magithub-repo repo) + (setq-local magithub-comment updated) + (magit-display-buffer (current-buffer))) + + (unless (string= (alist-get 'body comment) + (alist-get 'body updated)) + (message "Comment has changed since information was cached; \ +updated content pulled in for edit")))) (defun magithub-comment-reply (comment &optional discard-draft issue) "Reply to COMMENT on ISSUE. @@ -227,24 +213,22 @@ If ISSUE is not provided, it will be determined from context or from COMMENT." - (interactive (list (magithub-thing-at-point 'comment) + (interactive (list (thing-at-point 'github-comment) current-prefix-arg - (magithub-thing-at-point 'issue))) + (thing-at-point 'github-issue))) (let-alist comment (magithub-comment-new (or issue (magithub-request (ghubp-follow-get .issue_url))) discard-draft - (with-temp-buffer - (insert (string-trim (magithub-wash-gfm .body))) - (markdown-blockquote-region (point-min) (point-max)) - (goto-char (point-max)) - (insert "\n\n") - (buffer-string))))) - -(defun magithub-issue-comment-cancel () - "Cancel current comment." - (interactive) - (call-interactively #'magithub-comment-draft-save)) + (let ((reply-body (if (use-region-p) + (buffer-substring (region-beginning) (region-end)) + .body))) + (with-temp-buffer + (insert (string-trim (magithub-wash-gfm reply-body))) + (markdown-blockquote-region (point-min) (point-max)) + (goto-char (point-max)) + (insert "\n\n") + (buffer-string)))))) (defun magithub-issue-comment-submit (issue comment &optional repo) "On ISSUE, submit a new COMMENT. @@ -253,28 +237,25 @@ REPO is an optional repo object; it will be deduced from ISSUE if not provided." - (interactive (list (magithub-thing-at-point 'issue) + (interactive (list (thing-at-point 'github-issue) (save-restriction (widen) (buffer-substring-no-properties (point-min) (point-max))) - (magithub-thing-at-point 'repo))) + (thing-at-point 'github-repository))) (unless issue (user-error "No issue provided")) (setq repo (or repo (magithub-issue-repo issue) - (magithub-thing-at-point 'repo))) + (thing-at-point 'github-repository))) (unless repo (user-error "No repo detected")) ;; all required args provided - (unless (y-or-n-p (format "Submit this comment to %s? " - (magithub-issue-reference issue))) - (user-error "Aborted")) - ;; confirmed; submit the issue + (magithub-confirm 'comment (magithub-issue-reference issue)) (magithub-request (ghubp-post-repos-owner-repo-issues-number-comments - repo issue `((body . ,comment)))) - (message "Success") - (magithub-comment-draft-delete repo issue)) + repo issue `((body . ,comment)))) + (magithub-edit-delete-draft) + (message "Success")) (provide 'magithub-comment) ;;; magithub-comment.el ends here diff -Nru magithub-0.1.5/magithub-completion.el magithub-0.1.7/magithub-completion.el --- magithub-0.1.5/magithub-completion.el 1970-01-01 00:00:00.000000000 +0000 +++ magithub-0.1.7/magithub-completion.el 2018-06-03 00:42:26.000000000 +0000 @@ -0,0 +1,102 @@ +;;; magithub-completion.el --- Completion using info provided by magithub -*- lexical-binding: t; -*- + +;; Copyright (C) 2018 Steve Purcell + +;; Author: Steve Purcell +;; Keywords: convenience + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Provides `completion-at-point' functions which complete issue +;; numbers etc when they are entered in commit messages. + +;; Extended information is attached to completions so that `company' +;; can access it via the standard `company-capf' backend. + +;;; Code: + + +(require 'magithub-settings) +(require 'magithub-issue) + + +;;;###autoload +(defun magithub-completion-complete-issues () + "A `completion-at-point' function which completes \"#123\" issue references. +Add this to `completion-at-point-functions' in buffers where you +want this to be available." + (when (magithub-enabled-p) + (when (looking-back "#\\([0-9]*\\)" (- (point) 10)) + (let ((start (match-beginning 1)) + (end (match-end 0)) + (prefix (match-string 1)) + completions) + (dolist (i (magithub--issue-list)) + (let-alist i + (let ((n (number-to-string .number))) + (when (string-prefix-p prefix n) + (push (propertize n :issue i) completions))))) + (list start end (sort completions #'string<) + :exclusive 'no + :company-docsig (lambda (c) + (let-alist (get-text-property 0 :issue c) + .title)) + :annotation-function (lambda (c) + (let-alist (get-text-property 0 :issue c) + .title)) + :company-doc-buffer (lambda (c) + (save-window-excursion + (magithub-issue-visit + (get-text-property 0 :issue c))))))))) + +;;;###autoload +(defun magithub-completion-complete-users () + "A `completion-at-point' function which completes \"@user\" user references. +Add this to `completion-at-point-functions' in buffers where you +want this to be available. The user list is currently simply the +list of all users who created issues or pull requests." + (when (magithub-enabled-p) + (when (looking-back "@\\([_-A-Za-z0-9]*\\)" (- (point) 30)) + (let ((start (match-beginning 1)) + (end (match-end 0)) + (prefix (match-string 1)) + completions) + (dolist (i (magithub--issue-list)) + (let-alist i + (when (string-prefix-p prefix .user.login) + (let ((candidate (copy-sequence .user.login)) + (association (and .author_association + (not (string= "NONE" .author_association)) + .author_association))) + (push (propertize candidate :user .user :association association) + completions))))) + (list start end (sort (cl-remove-duplicates completions :test #'string=) + #'string<) + :exclusive 'no + :company-docsig (lambda (c) (get-text-property 0 :association c)) + :annotation-function (lambda (c) (get-text-property 0 :association c))))))) + +;;;###autoload +(defun magithub-completion-enable () + "Enable completion of info from magithub in the current buffer." + (make-local-variable 'completion-at-point-functions) + (dolist (f '(magithub-completion-complete-issues + magithub-completion-complete-users)) + (add-to-list 'completion-at-point-functions f))) + + +(provide 'magithub-completion) +;;; magithub-completion.el ends here diff -Nru magithub-0.1.5/magithub-core.el magithub-0.1.7/magithub-core.el --- magithub-0.1.5/magithub-core.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-core.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ ;;; magithub-core.el --- core functions for magithub -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Sean Allred +;; Copyright (C) 2016-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: tools @@ -20,7 +20,7 @@ ;;; Commentary: -;; +;; Core functions for Magithub. ;;; Code: @@ -29,17 +29,22 @@ (require 's) (require 'subr-x) (require 'ghub) +(require 'ghub+) (require 'bug-reference) (require 'cl-lib) (require 'markdown-mode) (require 'parse-time) +(require 'thingatpt) +(require 'recentf) +(require 'magithub-settings) (require 'magithub-faces) (defconst magithub-github-token-scopes '(repo user notifications) "The authentication scopes Magithub requests.") - ;;; Debugging utilities +;;; Debugging utilities + (defvar magithub-debug-mode nil "Controls what kinds of debugging information shows. List of symbols. @@ -80,95 +85,24 @@ If it does not exist, it will be created." :group 'magithub :type 'directory) +(add-to-list 'recentf-exclude + (lambda (filename) + (file-in-directory-p filename magithub-dir))) - ;;; Turning Magithub on/off -(defun magithub-enable () - "Enable Magithub for this repository." - (interactive) - (magit-set "true" "magithub" "enabled") - (magit-refresh) - (message "Magithub is now enabled in this repository")) +;;; Turning Magithub on/off (defmacro magithub-in-data-dir (&rest forms) "Execute forms in `magithub-dir'. If `magithub-dir' does not yet exist, it and its parents will be created automatically." + (declare (debug t)) `(progn (unless (file-directory-p magithub-dir) (mkdir magithub-dir t)) (let ((default-directory magithub-dir)) ,@forms))) -(defun magithub-disable () - "Disable Magithub for this repository." - (interactive) - (magit-set "false" "magithub" "enabled") - (magit-refresh) - (message "Magithub is now disabled in this repository")) - -(defcustom magithub-enabled-by-default t - "Non-nil if Magithub is enabled by default." - :group 'magithub - :type 'boolean) - -(defun magithub-enabled-p () - "Returns non-nil when Magithub is enabled for this repository." - (let ((enabled (magit-get "magithub" "enabled"))) - (cond - ((member enabled '("yes" "true")) t) - ((member enabled '("no" "false")) nil) - (t magithub-enabled-by-default)))) - -(defun magithub-enabled-toggle () - "Toggle Magithub integration." - (interactive) - (if (magithub-enabled-p) - (magithub-disable) - (magithub-enable))) - - ;;; Caching; Online/Offline mode -(defvar magithub-cache 'when-present - "Determines how the cache behaves. - -If nil, the cache will not be used to read cached data. It will -still be updated and written to disk. - -If t, *only* the cache will be used. This constitutes Magithub's -'offline' mode. - -If `when-present', we'll use the cached value when present, but -we'll make a request if there's no appropriate value. (Note that -an API response of nil is considered an appropriate value.)") - -(defun magithub-offline-p () - "Non-nil if Magithub is not supposed to make API requests." - (memq magithub-cache '(t refreshing-when-offline))) - -(defun magithub-toggle-online () - "Toggle online status. -Runs either `magithub-go-online' or `magithub-go-offline' -depending on `magithub-offline-p'." - (interactive) - (if (magithub-offline-p) - (magithub-go-online) - (magithub-go-offline))) - -(defun magithub-go-online () - "Take Magithub online. -API requests will be made to refresh expired caches." - (interactive) - (setq magithub-cache 'when-present) - (magit-refresh) - (message "Magithub is now online everywhere")) - -(defun magithub-go-offline () - "Take Magithub offline. -No API requests will be made; all information displayed will be -retrieved from the cache." - (interactive) - (setq magithub-cache t) - (magit-refresh) - (message "Magithub is now offline everywhere")) +;;; Caching; Online/Offline mode (defcustom magithub-cache-file "cache" "Use this file for Magithub's persistent cache." @@ -198,63 +132,53 @@ When non-nil, the cache will be written to disk next time the idle timer runs.") -(defvar magithub-cache-ignore-class nil - "Class to ignore in `magithub-cache'. -See also `magithub-cache-without-cache'. - -If t, all classes are ignored.") - (defvar magithub-cache--refreshed-forms nil "Forms that have been refreshed this session. -See also `magithub-refresh'.") +See also `magithub--refresh'.") + (cl-defun magithub-cache (class form &key message after-update) "The cached value for FORM if available. If FORM has not been cached or its CLASS dictates the cache has expired, FORM will be re-evaluated. -CLASS: The kind of data this is; see `magithub-cache-ignore-class'. +CLASS: The kind of data this is; see `magithub-cache--refresh'. MESSAGE may be specified for intensive functions. We'll display this with `with-temp-message' while the form is evaluating. AFTER-UPDATE is a function to run after the cache is updated." (declare (indent defun)) - (let* ((entry (list (ghubp-get-context) class form)) - (refreshing (memq magithub-cache '(refreshing refreshing-when-offline))) - (recalc (or (null magithub-cache) - (and refreshing - (not (member entry magithub-cache--refreshed-forms))) - (and magithub-cache-ignore-class - (or (eq magithub-cache-ignore-class t) - (eq magithub-cache-ignore-class class))))) - no-value-sym cached-value new-value) - - (unless recalc - (setq no-value-sym (cl-gensym) - cached-value (gethash entry magithub-cache--cache no-value-sym) - recalc (and (not (eq magithub-cache t)) - (eq cached-value no-value-sym)) - cached-value (if (eq cached-value no-value-sym) nil cached-value))) + (let* ((no-value-sym (cl-gensym)) + (entry (list (ghubp-get-context) class form)) + (online (magithub-online-p)) + (cached-value (gethash entry magithub-cache--cache no-value-sym)) + (value-does-not-exist (eq cached-value no-value-sym)) + (cached-value (if value-does-not-exist nil cached-value)) + make-request new-value) + + (when online + (if (or (eq magithub-cache--refresh t) + (eq magithub-cache--refresh class)) + ;; if we're refreshing (and we haven't seen this form + ;; before), go ahead and make the request if it's the class + ;; we're refreshing (or t, which encompasses all classes) + (setq make-request (not (member entry magithub-cache--refreshed-forms))) + (setq make-request value-does-not-exist))) - (or (and recalc + (or (and make-request (prog1 (setq new-value (with-temp-message message (eval form))) (puthash entry new-value magithub-cache--cache) - (setq magithub-cache--needs-write t) - (run-with-idle-timer 600 nil #'magithub-cache-write-to-disk) - (when refreshing + (unless magithub-cache--needs-write + (setq magithub-cache--needs-write t) + (run-with-idle-timer 600 nil #'magithub-cache-write-to-disk)) + (when magithub-cache--refresh (push entry magithub-cache--refreshed-forms)) - (when (functionp after-update) - (funcall after-update new-value)))) + (if (functionp after-update) + (funcall after-update new-value) + new-value))) cached-value))) -(defun magithub-cache-invalidate () - "Clear the cache from memory." - (maphash - (lambda (k _) - (remhash k magithub-cache--cache)) - magithub-cache--cache)) - (defun magithub-maybe-report-offline-mode () "Conditionally inserts the OFFLINE header. If this is a Magithub-enabled repository and we're offline, we @@ -262,27 +186,32 @@ To aid in determining if the cache should be refreshed, we report the age of the oldest cached information." (when (and (magithub-usable-p) - (magithub-offline-p)) + (not (magithub-online-p))) (magit-insert-section (magithub nil t) (insert - (format "Magithub: %s; use %s to refresh Github content or %s to go back online%s\n" - (propertize "OFFLINE" 'face 'magit-head) - (propertize - (substitute-command-keys "\\[universal-argument] \\[magit-refresh]") - 'face 'magit-header-line-key) - (propertize - (substitute-command-keys "\\[magithub-dispatch-popup] O") - 'face 'magit-header-line-key) - (propertize "..." 'face 'magit-dimmed))) + (format + "Magithub: %s; use %s to refresh GitHub content or %s to go back online%s\n" + (propertize "OFFLINE" 'face 'magit-head) + (propertize + (substitute-command-keys "\\[universal-argument] \\[magit-refresh]") + 'face 'magit-header-line-key) + (propertize + (substitute-command-keys "\\[magithub-dispatch-popup] C o") + 'face 'magit-header-line-key) + (propertize "..." 'face 'magit-dimmed))) (magit-insert-heading) - (let* ((msg "When Magithub is offline, no API requests are ever made automatically. Even when online, cached API responses never expire, so they must be updated manually with %s.") + (let* ((msg "When Magithub is offline, no API requests are ever made \ +automatically. Even when online, cached API responses never expire, so \ +they must be updated manually with %s.") (msg (s-word-wrap (- fill-column 10) msg)) (msg (format msg (propertize - (substitute-command-keys "\\[universal-argument] \\[magit-refresh]") + (substitute-command-keys + "\\[universal-argument] \\[magit-refresh]") 'face 'magit-header-line-key)))) - (insert (format "%s\n" (replace-regexp-in-string (rx bol) (make-string 10 ?\ ) msg))))))) + (insert (format "%s\n" (replace-regexp-in-string + (rx bol) (make-string 10 ?\ ) msg))))))) -(eval-after-load "magit" +(eval-after-load 'magit '(add-hook 'magit-status-headers-hook #'magithub-maybe-report-offline-mode 'append)) @@ -301,7 +230,7 @@ (defun magithub-cache-write-to-disk () "Write the cache to disk. -The cache is writtin to `magithub-cache-file' in +The cache is written to `magithub-cache-file' in `magithub-dir'" (if (active-minibuffer-window) (run-with-idle-timer 600 nil #'magithub-cache-write-to-disk) ;defer @@ -311,20 +240,23 @@ (insert (prin1-to-string magithub-cache--cache)) (write-file magithub-cache-file))) (setq magithub-cache--needs-write nil) - (magithub-debug-message "wrote cache to disk: %S" (expand-file-name magithub-cache-file magithub-dir))))) + (magithub-debug-message "wrote cache to disk: %S" + (expand-file-name magithub-cache-file + magithub-dir))))) (defmacro magithub-cache-without-cache (class &rest body) "For CLASS, execute BODY without using CLASS's caches. Use t to ignore previously cached values completely. -See also `magithub-cache-ignore-class'." +See also `magithub-cache--refresh'." (declare (indent 1) (debug t)) - `(let ((magithub-cache-ignore-class ,class)) + `(let ((magithub-cache--refresh ,class)) ,@body)) (add-hook 'kill-emacs-hook #'magithub-cache-write-to-disk) - ;;; API availability checking +;;; API availability checking + (define-error 'magithub-error "Magithub Error") (define-error 'magithub-api-timeout "Magithub API Timeout" 'magithub-error) @@ -333,7 +265,7 @@ ;; (eval-when-compile (date-to-time "1/1/1970")) '(14445 17280) "The last time the API was available. -Used to avoid pinging Github multiple times a second.") +Used to avoid pinging GitHub multiple times a second.") (defcustom magithub-api-timeout 3 "Number of seconds we'll wait for the API to respond." @@ -351,10 +283,11 @@ (defcustom magithub-api-available-check-frequency 10 "Minimum number of seconds between each API availability check. -While online (see `magithub-go-online'), we check to ensure the API is available -before making a real request. This involves a `/rate_limit' call (or for some -Enterprise instances, a `/meta' call). Use this setting to configure how often -this is done. It will be done no more frequently than other API actions. +While online (see `magithub-go-online'), we check to ensure the +API is available before making a real request. This involves a +`/rate_limit' call (or for some Enterprise instances, a `/meta' +call). Use this setting to configure how often this is done. It +will be done no more frequently than other API actions. These calls are guaranteed to not count against your rate limit." :group 'magithub @@ -384,20 +317,12 @@ (when (magithub-enabled-p) (magithub-debug-message "checking if the API is available") (prog1 (when - (condition-case _ - (progn - (magithub-debug-message "making sure authinfo is unlocked") - (ghubp-token 'magithub)) - ;; Magithub only works when authenticated. - (ghub-auth-error - (prog1 nil - (if (y-or-n-p "Not yet authenticated; open instructions in your browser? ") - (progn - (browse-url "https://github.com/magit/ghub#initial-configuration") - (setq magithub--api-offline-reason "Try again once you've authenticated")) - (setq magithub--api-offline-reason "Not yet authenticated per ghub's README"))))) + (progn + (magithub-debug-message "making sure authinfo is unlocked") + (ghubp-token 'magithub)) (if (and magithub--api-last-checked - (< (time-to-seconds (time-since magithub--api-last-checked)) magithub-api-available-check-frequency)) + (< (time-to-seconds (time-since magithub--api-last-checked)) + magithub-api-available-check-frequency)) (prog1 magithub--api-last-checked (magithub-debug-message "used cached value for api-last-checked")) @@ -407,45 +332,47 @@ (let (api-status error-data response) (condition-case err (progn - (setq response - (condition-case _ - (with-timeout (magithub-api-timeout - (signal 'magithub-api-timeout nil)) - (ghub-get "/rate_limit" nil :auth 'magithub)) - - (ghub-404 - ;; Rate-limiting is often disabled on - ;; Enterprise instances. Try using /meta - ;; which should (hopefully) always work. See - ;; also issue #107. - (ghub-get "/meta" nil :auth 'magithub))) - api-status (and response t)) + (with-timeout (magithub-api-timeout + (signal 'magithub-api-timeout nil)) + (setq response + ;; /rate_limit is free for GitHub.com. + ;; If rate limiting is disabled + ;; (i.e. GHE), try using /meta which + ;; should (hopefully) always work. See + ;; also issue #107. + (or (ghubp-ratelimit) + (ghub-get "/meta" nil :auth 'magithub)) + + api-status (and response t))) - (magithub-debug-message "new value retrieved for api-last-available: %S" - response)) + (magithub-debug-message + "new value retrieved for api-last-available: %S" response)) ;; Sometimes, the API can take a long time to respond - ;; (whether that's Github not responding or requests being - ;; blocked by some client-side firewal). Handle this + ;; (whether that's GitHub not responding or requests being + ;; blocked by some client-side firewall). Handle this ;; possibility gracefully. (magithub-api-timeout (setq error-data err magithub--api-offline-reason (concat "API is not responding quickly; " - "consider customizing `magithub-api-timeout' if this happens often"))) + "consider customizing `magithub-api-timeout' " + "if this happens often"))) ;; Never hurts to be cautious :-) (error - (setq error-data err - magithub--api-offline-reason (format "unknown issue: %S" err)))) + (setq error-data err) + (setq magithub--api-offline-reason + (format "unknown issue: %S" err)))) (when error-data - (magithub-debug-message "consider reporting unknown error while checking api-available: %S" - error-data)) + (magithub-debug-message + "consider reporting unknown error while checking api-available: %S" + error-data)) api-status))) (when magithub--api-offline-reason - (magithub-go-offline) + (magit-set "false" "magithub.online") (run-with-idle-timer 2 nil #'magithub--api-offline-reason))))) (defun magithub--api-offline-reason () @@ -460,12 +387,21 @@ (defalias 'magithub-api-rate-limit #'ghubp-ratelimit) - ;;; Repository parsing +;;; Repository parsing + +(defcustom magithub-github-hosts + (list "github.com") + "A list of top-level domains that should be recognized as GitHub hosts. +See also `magithub-github-repository-p'." + :group 'magithub + :type '(list string)) + (defun magithub-github-repository-p () - "Non-nil if \"origin\" points to Github or a whitelisted domain." - (when-let ((origin (magit-get "remote" "origin" "url"))) + "Non-nil if \"origin\" points to GitHub or a whitelisted domain. +See also `magithub-github-hosts'." + (when-let ((origin (magit-get "remote" (magithub-settings-context-remote) "url"))) (-some? (lambda (domain) (s-contains? domain origin)) - (cons "github.com" (magit-get-all "hub" "host"))))) + magithub-github-hosts))) (defalias 'magithub--parse-url 'magithub--repo-parse-url) (make-obsolete 'magithub--parse-url 'magithub--repo-parse-url "0.1.4") @@ -518,23 +454,20 @@ (name . ,(match-string 3 url)))))))) (defun magithub--url->repo (url) - "Tries to parse a remote url into a Github repository object" + "Tries to parse a remote url into a GitHub repository object" (cdr (assq 'sparse-repo (magithub--repo-parse-url url)))) -(defun magithub-source--remote () - "Tries to determine the correct remote to use for issue-tracking." - (or (magit-get "magithub" "proxy") "origin")) - (defun magithub-source--sparse-repo () "Returns the sparse repository object for the current context. Only information that can be determined without API calls will be included in the returned object." (magithub-repo-from-remote--sparse - (magithub-source--remote))) + (magithub-settings-context-remote))) (defun magithub-repo-from-remote (remote) - (magithub-repo (magithub-repo-from-remote--sparse remote))) + (when-let ((repo (magithub-repo-from-remote--sparse remote))) + (magithub-repo repo))) (defun magithub-repo-from-remote--sparse (remote) (magithub--url->repo (magit-get "remote" remote "url"))) @@ -543,37 +476,48 @@ (make-obsolete 'magithub-source-repo 'magithub-repo "0.1.4") (defun magithub-repo (&optional sparse-repo) "Turn SPARSE-REPO into a full repository object. -If SPARSE-REPO is null, the current context is used." - (let ((sparse-repo (or sparse-repo (magithub-source--sparse-repo)))) - (or (magithub-cache :repo-demographics - `(condition-case e - (or (magithub-request - (ghubp-get-repos-owner-repo ',sparse-repo)) - (and (not (magithub--api-available-p)) - sparse-repo)) - ;; Repo may not exist; ignore 404 - (ghub-404 nil))) - (when (memq magithub-cache '(when-present refreshing-when-offline)) - (let ((magithub-cache nil)) - (magithub-repo sparse-repo))) - sparse-repo))) +If SPARSE-REPO is null, the current context is used. + +SPARSE-REPO may either be a partial repository object (with at +least the `.owner.login' and `.name' keys) or a string identifier +of the form `owner/name' (as in `vermiculus/magithub')." + (if (and (stringp sparse-repo) + (string-match (rx bos + (group (+? (| alnum "-" "." "_"))) ;owner.login -- vermiculus + "/" + (group (+? (| alnum "-" "." "_"))) ;name -- magithub + eos) + sparse-repo)) + (magithub-repo `((owner (login . ,(match-string 1 sparse-repo))) + (name . ,(match-string 2 sparse-repo)))) + (when-let ((sparse-repo (or sparse-repo (magithub-source--sparse-repo)))) + (or (magithub-cache :repo-demographics + `(or (magithub-request + (ghubp-get-repos-owner-repo ',sparse-repo)) + (and (not (magithub--api-available-p)) + sparse-repo))) + (when (magithub-online-p) + (let ((magithub-cache--refresh t)) + (magithub-repo sparse-repo))) + sparse-repo)))) + +;;; Repository utilities - ;;; Repository utilities (defvar magit-magithub-repo-section-map (let ((m (make-sparse-keymap))) (define-key m [remap magit-visit-thing] #'magithub-repo-visit) m)) (defun magithub-repo-visit (repo) - "Visit REPO on Github." - (interactive (list (magithub-thing-at-point 'repo))) + "Visit REPO on GitHub." + (interactive (list (thing-at-point 'github-repository))) (if-let ((url (alist-get 'html_url repo))) (browse-url url) (user-error "No URL for repo"))) (defun magithub-repo-visit-issues (repo) - "Visit REPO's issues on Github." - (interactive (list (magithub-thing-at-point 'repo))) + "Visit REPO's issues on GitHub." + (interactive (list (thing-at-point 'github-repository))) (if-let ((url (alist-get 'html_url repo))) (browse-url (format "%s/issues" url)) (user-error "No URL for repo"))) @@ -587,13 +531,13 @@ (defun magithub-repo-admin-p (&optional repo) "Non-nil if the currently-authenticated user can manage REPO. REPO defaults to the current repository." - (let-alist (magithub-repo (or repo (magithub-thing-at-point 'repo))) + (let-alist (magithub-repo (or repo (thing-at-point 'github-repository))) .permissions.admin)) (defun magithub-repo-push-p (&optional repo) "Non-nil if the currently-authenticated user can manage REPO. REPO defaults to the current repository." - (let-alist (magithub-repo (or repo (magithub-thing-at-point 'repo))) + (let-alist (magithub-repo (or repo (thing-at-point 'github-repository))) .permissions.push)) (defun magithub--repo-simplify (repo) @@ -607,14 +551,16 @@ (name . ,name)))) (defun magithub-repo-remotes () - "Return Github repositories in this repository. + "Return GitHub repositories in this repository. `magit-list-remotes' is filtered to those remotes that point to -Github repositories." - (delq nil (mapcar (lambda (r) (cons r (magithub-repo-from-remote r))) +GitHub repositories." + (delq nil (mapcar (lambda (r) + (when-let ((repo (magithub-repo-from-remote r))) + (cons r repo))) (magit-list-remotes)))) (defun magithub-read-repo (prompt) - "Using PROMPT, read a Github repository. + "Using PROMPT, read a GitHub repository. See also `magithub-repo-remotes'." (let* ((remotes (magithub-repo-remotes)) (maxlen (->> remotes @@ -638,16 +584,73 @@ (string= .repo.name .remote.name)))) (magit-list-remotes))) - ;;; Feature checking +;;; Feature checking + +(declare-function magithub-pull-request-merge "magithub-issue-tricks" + (pull-request &optional args)) +(declare-function magithub-maybe-insert-ci-status-header "magithub-ci" ()) +(declare-function magithub-issue--insert-pr-section "magithub-issue" ()) +(declare-function magithub-issue--insert-issue-section "magithub-issue" ()) +(declare-function magithub-completion-enable "magithub-completion" ()) (defconst magithub-feature-list - '(pull-request-merge pull-request-checkout) - "All magit-integration features of Magithub. + ;; features must only return nil if they fail to install + `((pull-request-merge . ,(lambda () + (magit-define-popup-action 'magit-am-popup + ?P "Apply patches from pull request" + #'magithub-pull-request-merge) + t)) + + (commit-browse . ,(lambda () + (define-key magit-commit-section-map "w" + #'magithub-commit-browse) + t)) + + (status-checks-header . ,(lambda () + (add-hook 'magit-status-headers-hook + #'magithub-maybe-insert-ci-status-header + t) + t)) + + (completion . ,(lambda () + (dolist (hook '(git-commit-setup-hook magithub-edit-mode-hook)) + (add-hook hook #'magithub-completion-enable)) + t)) + + ;; order is important in this list; pull request section should + ;; come before issues section by default + (pull-requests-section . ,(lambda () + (add-hook 'magit-status-sections-hook + #'magithub-issue--insert-pr-section + t) + t)) + + (issues-section . ,(lambda () + (add-hook 'magit-status-sections-hook + #'magithub-issue--insert-issue-section + t) + t))) + "All Magit-integration features of Magithub. +See `magithub-feature-autoinject'. + +- `pull-request-merge' + Apply patches from pull requests. + (`magithub-pull-request-merge' inserted into `magit-am-popup') + +- `commit-browse' + Browse commits using \\\\[magithub-browse-thing]. + +- `completion' + Enable `completion-at-point' support for #issue and @user references + where possible. -`pull-request-merge' -Apply patches from pull request +- `issues-section' + View issues in the `magit-status' buffer. -`pull-request-checkout' -Checkout pull requests as new branches") +- `pull-requests-section' + View pull requests in the `magit-status' buffer. + +- `status-checks-header' + View project status in the `magit-status' buffer (e.g., CI).") (defvar magithub-features nil "An alist of feature-symbols to Booleans. @@ -662,9 +665,30 @@ signals that `pull-request-merge' is a loaded feature and `other-feature' has not been loaded and will not be loaded. -To enable all features, see `magithub-feature-autoinject'. +See `magithub-feature-list'.") -See `magithub-feature-list' for a list and description of features.") +;;;###autoload +(defun magithub-feature-autoinject (feature) + "Configure FEATURE to recommended settings. +If FEATURE is `all' or t, all known features will be loaded. If +FEATURE is a list, then it is a list of FEATURE symbols to load. + +See `magithub-feature-list' for a list of available features and +`magithub-features' for a list of currently-installed features." + (cond + ((memq feature '(t all)) + (mapc #'magithub-feature-autoinject + (mapcar #'car magithub-feature-list))) + ((listp feature) + (mapc #'magithub-feature-autoinject feature)) + (t + (if-let ((install (cdr (assq feature magithub-feature-list)))) + (if (functionp install) + (if-let ((result (funcall install))) + (add-to-list 'magithub-features (cons feature t)) + (error "feature %S failed to install: %S" feature result)) + (error "install form for %S not a function: %S" feature install)) + (user-error "unknown feature %S" feature))))) (defun magithub-feature-check (feature) "Check if a Magithub FEATURE has been configured. @@ -685,7 +709,8 @@ (message (concat m "; " s) feature-list) (add-to-list 'feature-list '(t . t) t)))))) - ;;; Getting help +;;; Getting help + (defun magithub--meta-new-issue () "Open a new Magithub issue. See /.github/ISSUE_TEMPLATE.md in this repository." @@ -697,10 +722,23 @@ (interactive) (browse-url "https://gitter.im/vermiculus/magithub")) -(defun magithub-error (err-message tag &optional trace) - "Report a Magithub error." - (setq trace (or trace (with-output-to-string (backtrace)))) - (when (y-or-n-p (concat tag " Report? (A bug report will be placed in your clipboard.)")) +(defun magithub-error (err-message &optional tag trace) + "Report a Magithub error. + +ERR-MESSAGE is a string to be shown to the user. + +TAG, if provided, is a user-friendly description of the error. +It defaults to ERR-MESSAGE. + +If TRACE is provided, it should be an appropriate backtrace to +describe the error. If not provided, it is retrieved." + (unless (stringp err-message) + ;; just in case. it'd be embarassing if the bug-reporter was + ;; perceived as buggy + (setq err-message (prin1-to-string err-message))) + (setq trace (or trace (with-output-to-string (backtrace))) + tag (or tag err-message)) + (when (magithub-confirm-no-error 'report-error tag) (with-current-buffer-window (get-buffer-create "*magithub issue*") #'display-buffer-pop-up-window nil @@ -710,6 +748,8 @@ (format "## Automated error report +%s + ### Description %s @@ -719,12 +759,13 @@ ``` %s``` " + err-message (read-string "Briefly describe what you were doing: ") trace)))) (magithub--meta-new-issue)) (error err-message)) - ;;; Miscellaneous utilities +;;; Miscellaneous utilities (defcustom magithub-datetime-format "%c" "The display format string for date-time values. @@ -749,7 +790,8 @@ (magithub--parse-time-string time)) time))) -(defun magithub--completing-read (prompt collection &optional format-function predicate require-match default) +(defun magithub--completing-read + (prompt collection &optional format-function predicate require-match default) "Using PROMPT, get a list of elements in COLLECTION. This function continues until all candidates have been entered or until the user enters a value of \"\". Duplicate entries are not @@ -762,7 +804,8 @@ (when default (funcall format-function default))) collection)))) -(defun magithub--completing-read-multiple (prompt collection &optional format-function predicate require-match default) +(defun magithub--completing-read-multiple + (prompt collection &optional format-function predicate require-match default) "Using PROMPT, get a list of elements in COLLECTION. This function continues until all candidates have been entered or until the user enters a value of \"\". Duplicate entries are not @@ -787,26 +830,6 @@ (magithub-github-repository-p) (magithub-source--sparse-repo))) -(defmacro magithub--deftoggle (name doc on-by-default hook func) - "Define a section-toggle command." - (declare (indent defun)) - (let ((Senabled (cl-gensym))) - `(prog1 (defun ,name () - ,(concat "Toggle the " doc " section.") - (interactive) - (let (,Senabled) - (setq ,Senabled (not (memq ,func ,hook))) - (if ,Senabled - (remove-hook ',hook ,func) - (add-hook ',hook ,func t)) - (magit-refresh) - (message (concat ,(concat doc " section ") (if ,Senabled "enabled" "disabled"))) - ,Senabled)) - ,(when on-by-default - `(eval-after-load "magit" - '(let ((inhibit-magit-refresh t)) - (add-hook ',hook ,func t))))))) - (defun magithub--zip-case (p e) "Get an appropriate value for element E given property/function P." (cond @@ -850,21 +873,35 @@ (eq (magit-section-type SECTION) \\='magithub-issue) return the interned symbol `issue'." - (let* ((type (magit-section-type section)) + (let* ((type (oref section type)) (name (symbol-name type))) (and (string-prefix-p "magithub-" name) (intern (substring name 9))))) -(defvar magithub-thing-type-specializations +(defvar magithub--section-value-at-point-specializations '((user assignee)) "Alist of general types to specific types. Specific types offer more relevant functionality to a given -section, but are inconvenient for `magithub-thing-at-point'. -This alist defines equivalencies such that a search for the -general type will also return sections of a specialized type.") - -(defun magithub-thing-at-point (type) - "Determine the thing of TYPE at point." +section, but are inconvenient for +`magithub--section-value-at-point'. This alist defines +equivalencies such that a search for the general type will also +return sections of a specialized type.") + +(define-obsolete-function-alias + 'magithub-thing-at-point + #'magithub--section-value-at-point + "0.1.5") + +;;;###autoload +(defun magithub--section-value-at-point (type) + "Determine the thing of TYPE at point. +This is intended for use as a resolving function for +`thing-at-point'. + +The following symbols are defined, but other values may work with +this function: `github-user', `github-issue', `github-label', +`github-comment', `github-repository', `github-pull-request', +`github-notification'," (let ((search-sym (intern (concat "magithub-" (symbol-name type)))) this-section) (if (and (boundp search-sym) (symbol-value search-sym)) @@ -876,11 +913,67 @@ ;; exact match (eq type this-type) ;; equivalency - (thread-last magithub-thing-type-specializations + (thread-last magithub--section-value-at-point-specializations (alist-get type) (memq this-type)))))) - (setq this-section (magit-section-parent this-section))) - (and this-section (magit-section-value this-section))))) + (setq this-section (oref this-section parent))) + (and this-section (oref this-section value))))) + +(defvar-local magithub-issue nil + "Issue object.") + +(defvar-local magithub-comment nil + "Comment object.") + +(defvar-local magithub-repo nil + "Repository object.") + +;;;###autoload +(put 'github-user 'thing-at-point + (lambda () + (magithub--section-value-at-point 'user))) + +;;;###autoload +(put 'github-issue 'thing-at-point + (lambda () + (or magithub-issue + (magithub--section-value-at-point 'issue)))) + +;;;###autoload +(put 'github-label 'thing-at-point + (lambda () + (magithub--section-value-at-point 'label))) + +;;;###autoload +(put 'github-comment 'thing-at-point + (lambda () + (or magithub-comment + (magithub--section-value-at-point 'comment)))) + +;;;###autoload +(put 'github-notification 'thing-at-point + (lambda () + (magithub--section-value-at-point 'notification))) + +;;;###autoload +(put 'github-repository 'thing-at-point + (lambda () + (or (magithub--section-value-at-point 'repository) + magithub-repo + (magithub-repo)))) + +;;;###autoload +(put 'github-pull-request 'thing-at-point + (lambda () + (or (magithub--section-value-at-point 'pull-request) + (when-let ((issue (thing-at-point 'github-issue))) + (and + (magithub-issue--issue-is-pull-p issue) + (magithub-cache :issues + `(magithub-request + (ghubp-get-repos-owner-repo-pulls-number + ',(magithub-issue-repo issue) + ',issue)))))))) (defun magithub-verify-manage-labels (&optional interactive) "Verify the user has permission to manage labels. @@ -889,14 +982,14 @@ If INTERACTIVE is non-nil, a `user-error' will be raised instead of a signal (e.g., for interactive forms)." - (let-alist (magithub-repo) + (let-alist (thing-at-point 'github-repository) (if .permissions.push t (if interactive (user-error "You're not allowed to manage labels in %s" .full_name) (signal 'error `(unauthorized manage-labels ,(progn .full_name))))))) (defun magithub-bug-reference-mode-on () - "In Github repositories, configure `bug-reference-mode'." + "In GitHub repositories, configure `bug-reference-mode'." (interactive) (when (magithub-usable-p) (when-let ((repo (magithub-repo))) @@ -916,7 +1009,7 @@ One of the following: `clone_url' (https://github.com/octocat/Hello-World.git) - `git_url' (git:github.com/octocat/Hello-World.git) + `git_url' (git://github.com/octocat/Hello-World.git) `ssh_url' (git@github.com:octocat/Hello-World.git)" :group 'magithub :type '(choice @@ -1026,31 +1119,47 @@ (interactive) (magit-section-show-level -5)) +(defun magithub--refresh-reset () + "Reset everything to the defaults after refreshing. +To be added to `magit-unwind-refresh-hook'." + (setq magithub-cache--refresh nil) + ;; reclaim some memory + (setq magithub-cache--refreshed-forms nil)) + +(defvar magithub-cache--refresh nil + ;; Can also consider making this a list in the future to refresh + ;; multiple forms. No current use-case for this, though. + "Non-nil when refreshing. +If t, all form classes will be refreshed. Otherwise, if non-nil, +this variable is expected to be `eq' to the class of forms that +should be selectively refreshed.") + +(make-obsolete 'magithub-refresh 'magithub--refresh "0.2") (defun magithub-refresh () - "Refresh Github data. + (interactive (user-error (substitute-command-keys + "This is no longer an interactive function; \ +use \\[universal-argument] \\[magit-refresh] instead :-)")))) + +(defun magithub--refresh () + "Refresh GitHub data. Use directly at your own peril; this is intended for use with `magit-pre-refresh-hook'." - (interactive (user-error (substitute-command-keys "This is no longer an interactive function; use \\[universal-argument] \\[magit-refresh] instead :-)"))) (when (and current-prefix-arg + (memq this-command '(magit-refresh + magit-refresh-all + magithub-ci-refresh + magithub-issue-refresh)) (magithub-usable-p) - (y-or-n-p "Refresh Github data? ") + (magithub-confirm-no-error 'refresh) (or (magithub--api-available-p) - (y-or-n-p "Github doesn't seem to be responding, are you sure? "))) - (let ((old-cache-value magithub-cache)) - ;; `magithub-refresh' is part of `magit-pre-refresh-hook' and - ;; our requests are made as part of `magit-refresh'. There's no - ;; way we can let-bind `magithub-cache' around that entire form, - ;; so we do the next best thing: as soon as emacs is idle (i.e., - ;; magit is done refreshing), we reset magithub-cache back to - ;; its old value. - (setq magithub-cache (pcase old-cache-value - (`t 'refreshing-when-offline) - (`nil nil) - (`when-present 'refreshing))) - (run-with-idle-timer 0 nil (lambda () - (setq magithub-cache old-cache-value - magithub-cache--refreshed-forms nil) - (message "(magithub): buffer data refreshed")))))) + (magithub-confirm-no-error 'refresh-when-API-unresponsive))) + ;; `magithub--refresh' is part of `magit-pre-refresh-hook' and our requests + ;; are made as part of `magit-refresh'. There's no way we can let-bind + ;; `magithub-settings--refresh' around that entire form, so we do the next + ;; best thing: use `magit-unwind-refresh-hook' to reset the override back + ;; to its old value. + (setq magithub-cache--refresh t) + (setq magithub-cache--refreshed-forms nil))) (defun magithub-wash-gfm (text) "Wash TEXT as it comes from the API." @@ -1076,16 +1185,40 @@ "Indent TEXT by INDENT spaces." (replace-regexp-in-string (rx bol) (make-string indent ?\ ) text)) +(defun magithub-commit-browse (rev) + "Browse REV on GitHub. +Interactively, this is the commit at point." + (interactive (list (or (when-let ((rev (magit-rev-verify + (oref (magit-current-section) value)))) + rev) + (thing-at-point 'git-revision)))) + (if-let ((parsed (magit-rev-parse rev))) + (if-let ((commits (magithub-request + (ghubp-get-repos-owner-repo-commits + (magithub-repo) nil + :sha parsed)))) + (let-alist (car commits) + (browse-url .html_url)) + (user-error "No commit %s on remote" parsed)) + (error "Could not parse %S" rev))) + (defun magithub-add-thing () + "Conceptual command to add a thing (e.g., label, assignee, ...)" (interactive) (user-error "There is no thing at point that could be added to")) + (defun magithub-browse-thing () + "Conceptual command to browse a thing on GitHub" (interactive) (user-error "There is no thing at point that could be browsed")) + (defun magithub-edit-thing () + "Conceptual command to edit a thing (e.g., comment)" (interactive) - (user-error "There is no thing at point that could be replied to")) + (user-error "There is no thing at point that could be edited")) + (defun magithub-reply-thing () + "Conceptual command to reply to a thing (e.g., comment)" (interactive) (user-error "There is no thing at point that could be replied to")) @@ -1095,7 +1228,8 @@ (define-key m "w" #'magithub-browse-thing) (define-key m "e" #'magithub-edit-thing) (define-key m "r" #'magithub-reply-thing) - m)) + m) + "Parent keymap for Magithub sections.") (defmacro magithub-request (&rest body) "Execute BODY authenticating as Magithub." @@ -1105,13 +1239,15 @@ (defun magithub-debug-section (section) (interactive (list (magit-current-section))) - (pp-eval-expression `(magit-section-value ,section))) + (pp-eval-expression `(oref ,section value))) -(eval-after-load "magit" +(eval-after-load 'magit '(progn (dolist (hook '(magit-revision-mode-hook git-commit-setup-hook)) (add-hook hook #'magithub-bug-reference-mode-on)) - (add-hook 'magit-pre-refresh-hook #'magithub-refresh))) + (add-hook 'magit-pre-refresh-hook #'magithub--refresh) + (add-hook 'magit-unwind-refresh-hook + #'magithub--refresh-reset))) (provide 'magithub-core) ;;; magithub-core.el ends here diff -Nru magithub-0.1.5/magithub-dash.el magithub-0.1.7/magithub-dash.el --- magithub-0.1.5/magithub-dash.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-dash.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ -;;; magithub-dash.el --- magithub dashboard -*- lexical-binding: t; -*- +;;; magithub-dash.el --- magithub dashboard -*- lexical-binding: t; -*- -;; Copyright (C) 2017 Sean Allred +;; Copyright (C) 2017-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: hypermedia @@ -20,7 +20,7 @@ ;;; Commentary: -;; Magithub-Dash is a dashboard for your Github activity. +;; Magithub-Dash is a dashboard for your GitHub activity. ;;; Code: @@ -31,8 +31,8 @@ (declare-function magithub-dispatch-popup "magithub.el") -(defcustom magithub-dashboard-show-unread-notifications nil - "Show unread notifications in the dashboard." +(defcustom magithub-dashboard-show-read-notifications t + "Show read notifications in the dashboard." :type 'boolean :group 'magithub) @@ -40,24 +40,27 @@ "Popup console for the dashboard." 'magithub-commands :actions '("Notifications" - (?r "Toggle showing unread notifications" - magithub-dashboard-show-unread-notifications-toggle))) + (?r "Toggle showing read notifications" + magithub-dashboard-show-read-notifications-toggle))) -(defun magithub-dashboard-show-unread-notifications-toggle () +(defun magithub-dashboard-show-read-notifications-toggle () (interactive) - (setq magithub-dashboard-show-unread-notifications - (not magithub-dashboard-show-unread-notifications)) + (setq magithub-dashboard-show-read-notifications + (not magithub-dashboard-show-read-notifications)) (magit-refresh-buffer)) ;;;###autoload (defun magithub-dashboard () - "View your Github dashboard." + "View your GitHub dashboard." (interactive) (let ((magit-generate-buffer-name-function (lambda (&rest _) "*magithub-dash*"))) (magit-mode-setup #'magithub-dash-mode))) -(defvar magithub-dash-map +(defvaralias 'magithub-dash-map 'magithub-dash-mode-map + "Old name of `magithub-dash-mode-map'. +This will be removed in a future version.") +(defvar magithub-dash-mode-map (let ((m (make-sparse-keymap))) (set-keymap-parent m magit-mode-map) (define-key m (kbd "5") #'magit-section-show-level-5) @@ -65,19 +68,23 @@ (define-key m (kbd ";") #'magithub-dashboard-popup) (define-key m (kbd "H") #'magithub-dispatch-popup) m) - "Keymap for `magihtub-dash-mode'.") + "Keymap for `magithub-dash-mode'.") +;; todo: remove on version bump (define-derived-mode magithub-dash-mode magit-mode "Magithub-Dash" - "Major mode for your Github dashboard." - (use-local-map magithub-dash-map)) + "Major mode for your GitHub dashboard.") (defun magithub-dash-refresh-buffer (&rest _args) "Refresh the dashboard. Runs `magithub-dash-sections-hook'." (interactive) (magit-insert-section (magithub-dash-buf) - (run-hooks 'magithub-dash-sections-hook))) + (run-hooks 'magithub-dash-sections-hook)) + (let ((inhibit-read-only t)) + (save-excursion + (goto-char (point-max)) + (delete-blank-lines)))) (defvar magithub-dash-sections-hook '(magithub-dash-insert-headers @@ -114,7 +121,7 @@ (magithub-request (when-let ((ratelimit (ghubp-ratelimit))) (when (time-less-p (alist-get 'reset ratelimit) (current-time)) - (ghub-get "/rate_limit" nil :auth 'magithub))) + (ghubp-ratelimit 'no-headers))) (let-alist (ghubp-ratelimit) (when .limit (magit-insert-section (magithub-ratelimit) @@ -134,24 +141,32 @@ (defun magithub-dash-insert-notifications (&optional notifications) "Insert NOTIFICATIONS into the buffer bucketed by repository." - (setq notifications (or notifications (magithub-notifications - magithub-dashboard-show-unread-notifications))) + (setq notifications (or notifications + (magithub-notifications + magithub-dashboard-show-read-notifications))) (if notifications - (let* ((bucketed (magithub-core-bucket notifications (apply-partially #'alist-get 'repository))) - (unread (-filter #'magithub-notification-unread-p notifications)) - (hide (null unread))) + (let* ((bucketed (magithub-core-bucket + notifications + (apply-partially #'alist-get 'repository))) + (unread (if magithub-dashboard-show-read-notifications + (-filter #'magithub-notification-unread-p notifications) + notifications)) + (hide (not unread)) + (heading (if magithub-dashboard-show-read-notifications + (format "%s (%d unread of %d)" + (propertize "Notifications" + 'face 'magit-section-heading) + (length unread) + (length notifications)) + (format "%s (%d)" + (propertize "Notifications" + 'face 'magit-section-heading) + (length notifications))))) (magit-insert-section (magithub-notifications notifications hide) - (magit-insert-heading - (if magithub-dashboard-show-unread-notifications - (format "%s (%d unread of %d)" - (propertize "Notifications" 'face 'magit-section-heading) - (length unread) - (length notifications)) - (format "%s (%d)" - (propertize "Notifications" 'face 'magit-section-heading) - (length notifications)))) + (magit-insert-heading heading) (magithub-for-each-bucket bucketed repo repo-notifications - (setq hide (null (-filter #'magithub-notification-unread-p repo-notifications))) + (setq hide (null (-filter #'magithub-notification-unread-p + repo-notifications))) (magit-insert-section (magithub-repo repo hide) (magit-insert-heading (concat (propertize (magithub-repo-name repo) 'face 'magithub-repo) @@ -161,7 +176,7 @@ (insert "\n"))) (magit-insert-section (magithub-notifications) (magit-insert-heading "Notifications") - (insert (propertize (if magithub-dashboard-show-unread-notifications + (insert (propertize (if magithub-dashboard-show-read-notifications "No notifications" "No unread notifications") 'face 'magit-dimmed) @@ -177,14 +192,15 @@ (ghubp-get-issues)))) title (or title "Issues Assigned to Me")) (when-let ((user-repo-issue-buckets - ;; bucket by user then by repo - (magithub-core-bucket-multi issues - #'magithub-issue-repo - (lambda (repo) (alist-get 'owner repo))))) + ;; bucket by user then by repo + (magithub-core-bucket-multi issues + #'magithub-issue-repo + (lambda (repo) (alist-get 'owner repo))))) (magit-insert-section (magithub-users-repo-issue-buckets) - (magit-insert-heading (format "%s (%d)" - (propertize title 'face 'magit-section-heading) - (length issues))) + (magit-insert-heading + (format "%s (%d)" + (propertize title 'face 'magit-section-heading) + (length issues))) (magithub-for-each-bucket user-repo-issue-buckets user repo-issue-buckets (magit-insert-section (magithub-user-repo-issues) (magit-insert-heading @@ -193,7 +209,8 @@ (magithub-for-each-bucket repo-issue-buckets repo repo-issues (magit-insert-section (magithub-repo-issues repo) (magit-insert-heading - (format "%s:" (propertize (alist-get 'name repo) 'face 'magithub-repo))) + (format "%s:" (propertize (alist-get 'name repo) + 'face 'magithub-repo))) (magithub-issue-insert-sections repo-issues) (insert "\n"))))) (insert "\n"))))) diff -Nru magithub-0.1.5/magithub-edit-mode.el magithub-0.1.7/magithub-edit-mode.el --- magithub-0.1.5/magithub-edit-mode.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-edit-mode.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ ;;; magithub-edit-mode.el --- message-editing mode -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Sean Allred +;; Copyright (C) 2016-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: multimedia @@ -20,7 +20,7 @@ ;;; Commentary: -;; Edit generic Github (markdown) content. To be used for comments, +;; Edit generic GitHub (markdown) content. To be used for comments, ;; issues, pull requests, etc. ;;; Code: @@ -32,15 +32,19 @@ (let ((m (make-sparse-keymap))) (define-key m (kbd "C-c C-c") #'magithub-edit-submit) (define-key m (kbd "C-c C-k") #'magithub-edit-cancel) - m)) + m) + "Keymap for `magithub-edit-mode'.") ;;;###autoload (define-derived-mode magithub-edit-mode gfm-mode "Magithub-Edit" - "Major mode for editing Github issues and pull requests.") + "Major mode for editing GitHub issues and pull requests.") -(defvar-local magithub-edit-submit-function nil) -(defvar-local magithub-edit-cancel-function nil) -(defvar-local magithub-edit-previous-buffer nil) +(defvar-local magithub-edit-submit-function nil + "Populated by SUBMIT in `magithub-edit-new'.") +(defvar-local magithub-edit-cancel-function nil + "Populated by CANCEL in `magithub-edit-new'.") +(defvar-local magithub-edit-previous-buffer nil + "The buffer we were in when the edit was initiated.") (defface magithub-edit-title '((t :inherit markdown-header-face-1)) @@ -48,36 +52,157 @@ :group 'magithub-faces) (defun magithub-edit-submit () + "Submit this post. +Uses `magithub-edit-submit-function' to do so." (interactive) + (unless (commandp magithub-edit-submit-function t) + (error "No submit function defined")) (magithub-edit--done magithub-edit-submit-function) (magithub-cache-without-cache t (magit-refresh-buffer))) + (defun magithub-edit-cancel () + "Cancel this post. +Offer to save a draft if the buffer is considered modified, then +call `magithub-edit-cancel-function'." (interactive) + ;; Offer to save the draft + (if (and (buffer-modified-p) + ;; don't necessarily want to use `magithub-confirm', here + ;; this is potentially a very dangerous action + (y-or-n-p "Save draft? ")) + (save-buffer) + (set-buffer-modified-p nil)) + + ;; If the saved draft is empty, might as well delete it + (when (and (stringp buffer-file-name) + (file-readable-p buffer-file-name) + (string= "" (let ((f buffer-file-name)) + (with-temp-buffer + (insert-file-contents f) + (buffer-string))))) + (magithub-edit-delete-draft)) + (magithub-edit--done magithub-edit-cancel-function)) + (defun magithub-edit--done (callback) - (let ((prevbuf magithub-edit-previous-buffer)) - (save-excursion - (when-let ((newbuf (call-interactively callback))) + "Cleanup this buffer. +If CALLBACK is a command, call it interactively. (This will +usually be the SUBMIT or CANCEL commands from +`magithub-edit-new'.) If that function returns a buffer, switch +to that buffer." + (let ((nextbuf magithub-edit-previous-buffer)) + (when (commandp callback t) + (let ((newbuf (save-excursion + (call-interactively callback)))) (when (bufferp newbuf) - (switch-to-buffer newbuf)))) + (setq nextbuf newbuf)))) + (set-buffer-modified-p nil) (kill-buffer) - (when prevbuf + (when nextbuf (let ((switch-to-buffer-preserve-window-point t)) - (switch-to-buffer prevbuf))))) -(defun magithub-edit-new (buffer-name submit-func cancel-func local-map setup) - (declare (indent 4)) - (let ((prevbuf (current-buffer))) + (switch-to-buffer nextbuf))))) + +(defun magithub-edit-delete-draft () + "Delete the draft for the current edit buffer." + (when (and (stringp buffer-file-name) + (file-exists-p buffer-file-name) + (file-writable-p buffer-file-name)) + (delete-file buffer-file-name magit-delete-by-moving-to-trash) + (message "Deleted %s" buffer-file-name)) + (set-visited-file-name nil)) + +(cl-defun magithub-edit-new (buffer-name &key cancel content file header prompt-discard-draft submit template) + "Generate a new edit buffer called BUFFER-NAME and return it. +'Edit' buffers provide a common interface and handling for +submitting, cancelling, and saving drafts of posts. + +CANCEL is a function to use upon \\[magithub-edit-cancel]. + +CONTENT is initial content for the buffer. It is considered +novel and the buffer will not be closed without prompting to save +a draft. + +FILE is the file to use for drafts of this post. + +HEADER is a title to use in the header line of the new buffer. + +If PROMPT-DISCARD-DRAFT is non-nil, this function will display +the draft before offering to delete it. This option is +recommended when using \\[universal-argument] with the command +that calls this function. + +SUBMIT is a function to use upon \\[magithub-edit-submit]. + +TEMPLATE is like CONTENT, but is not considered novel. We won't +ask to save a draft here if post is cancelled." + (declare (indent 1)) + (let ((prevbuf (current-buffer)) + (file (and (stringp file) + (file-writable-p file) + file)) + draft) + + ;; Load the draft + (setq draft (and (stringp file) + (file-readable-p file) + (with-temp-buffer + (insert-file-contents file) + (buffer-string)))) + (when (string= draft "") + (setq draft nil)) + + ;; Discard the draft if desired + (when (and draft prompt-discard-draft) + (with-current-buffer (get-buffer-create " *draft*") + (erase-buffer) + (insert draft) + (view-buffer-other-window (current-buffer)) + ;; don't necessarily want to use `magithub-confirm', here + ;; this is potentially a very dangerous action + (when (yes-or-no-p "Discard this draft? ") + (setq draft nil) + (when (file-writable-p file) + (delete-file file magit-delete-by-moving-to-trash))) + (kill-buffer (current-buffer)))) + (with-current-buffer (get-buffer-create buffer-name) (magithub-edit-mode) - (use-local-map (let ((m local-map)) - (set-keymap-parent m magithub-edit-mode-map) - m)) - (funcall setup) + (setq magithub-edit-previous-buffer prevbuf - magithub-edit-submit-function submit-func - magithub-edit-cancel-function cancel-func) - (switch-to-buffer-other-window (current-buffer))))) + magithub-edit-submit-function submit + magithub-edit-cancel-function cancel) + (magit-set-header-line-format + (substitute-command-keys + (let ((line "submit: \\[magithub-edit-submit] | cancel: \\[magithub-edit-cancel]")) + (when header + (setq line (concat line " | " header))) + line))) + + (when file + (let ((orig-name (buffer-name)) + (dir default-directory)) + (set-visited-file-name file) + (rename-buffer orig-name) + (cd dir))) + + (cond + (draft + (insert draft) + (set-buffer-modified-p nil) + (goto-char (point-max)) + (message "Loaded existing draft from %s" file)) + (content + (insert content) + (goto-char (point-max)) + (message "Loaded initial content")) + (template + (insert template) + (set-buffer-modified-p nil) + (goto-char (point-min)) + (message "Loaded template"))) + + (current-buffer)))) (provide 'magithub-edit-mode) ;;; magithub-edit-mode.el ends here diff -Nru magithub-0.1.5/magithub.el magithub-0.1.7/magithub.el --- magithub-0.1.5/magithub.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,12 +1,12 @@ -;;; magithub.el --- Magit interfaces for Github -*- lexical-binding: t; -*- +;;; magithub.el --- Magit interfaces for GitHub -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Sean Allred +;; Copyright (C) 2016-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: git, tools, vc ;; Homepage: https://github.com/vermiculus/magithub -;; Package-Requires: ((emacs "25") (magit "2.8") (s "1.12.0") (ghub+ "0.2") (git-commit "2.8") (markdown-mode "2.3")) -;; Package-Version: 0.1.4 +;; Package-Requires: ((emacs "25") (magit "2.12") (s "1.12.0") (ghub+ "0.3") (git-commit "2.12") (markdown-mode "2.3")) +;; Package-Version: 0.1.7 ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by @@ -23,18 +23,21 @@ ;;; Commentary: -;; Magithub is a Magit-based interface to Github. +;; Magithub is a Magit-based interface to GitHub. ;; -;; Integrated into Magit workflows, Magithub allows easy Github -;; repository management. Supported actions include: +;; Integrated into Magit workflows, Magithub lets you interact with +;; your GitHub repositories and manage your work/play from emacs: ;; -;; - pushing brand-new local repositories up to Github -;; - creating forks of existing repositories -;; - submitting pull requests upstream -;; - viewing and creating issues -;; - seeing status checks -;; -;; all from the `magit-status' buffer. +;; - push brand-new local repositories up to GitHub +;; - create forks of existing repositories +;; - submit pull requests upstream +;; - view and create issues +;; - view, create, and edit comments +;; - view status checks (e.g., Travis CI) +;; - manage labels and assignees +;; - view/visit notifications +;; - write personal notes on issues for reference later +;; - and probably more... ;; ;; Press `H' in the status buffer to get started -- happy hacking! @@ -42,7 +45,6 @@ (require 'magit) (require 'magit-process) -(require 'magit-popup) (require 'cl-lib) (require 's) (require 'dash) @@ -51,7 +53,6 @@ (require 'magithub-core) (require 'magithub-issue) (require 'magithub-ci) -(require 'magithub-proxy) (require 'magithub-issue-post) (require 'magithub-issue-tricks) (require 'magithub-orgs) @@ -61,22 +62,20 @@ (magit-define-popup magithub-dispatch-popup "Popup console for dispatching other Magithub popups." 'magithub-commands + :variables '((?C "Settings..." magithub-settings-popup)) :actions '("Actions" (?d "Dashboard" magithub-dashboard) - (?H "Browse on Github" magithub-browse) - (?c "Create" magithub-create) - (?f "Fork" magithub-fork) - (?i "Issues" magithub-issue-new) + (?H "Browse on GitHub" magithub-browse) + (?c "Create on GitHub" magithub-create) + (?f "Fork this repo" magithub-fork) + (?i "Submit an issue" magithub-issue-new) (?p "Submit a pull request" magithub-pull-request-new) - (?x "Use a proxy repository for issues/PRs" magithub-proxy-set) - (?O "Toggle online/offline" magithub-toggle-online) "Meta" - (?` "Toggle Magithub-Status integration" magithub-enabled-toggle) - (?~ "Toggle CI status header" magithub-ci-toggle) (?& "Request a feature or report a bug" magithub--meta-new-issue) (?h "Ask for help on Gitter" magithub--meta-help))) -(eval-after-load "magit" +;;;###autoload +(eval-after-load 'magit '(progn (magit-define-popup-action 'magit-dispatch-popup ?H "Magithub" #'magithub-dispatch-popup ?!) @@ -87,17 +86,17 @@ "Open the repository in your browser." (interactive) (unless (magithub-github-repository-p) - (user-error "Not a Github repository")) + (user-error "Not a GitHub repository")) (magithub-repo-visit (magithub-repo))) (defvar magithub-after-create-messages '("Don't be shy!" "Don't let your dreams be dreams!") "One of these messages will be displayed after you create a -Github repository.") +GitHub repository.") (defun magithub-create (repo &optional org) - "Create REPO on Github. + "Create REPO on GitHub. If ORG is non-nil, it is an organization object under which to create the new repository. You must be a member of this @@ -106,7 +105,7 @@ (list nil nil) (let* ((ghub-username (ghubp-username)) ;performance (account (magithub--read-user-or-org)) - (priv (yes-or-no-p "Will this be a private repository? ")) + (priv (magithub-confirm-no-error 'create-repo-as-private)) (reponame (magithub--read-repo-name account)) (desc (read-string "Description (optional): "))) (list @@ -116,18 +115,18 @@ (unless (string= ghub-username account) `((login . ,account))))))) (when (magithub-github-repository-p) - (error "Already in a Github repository")) + (error "Already in a GitHub repository")) (if (not (magit-toplevel)) - (when (y-or-n-p "Not inside a Git repository; initialize one here? ") + (when (magithub-confirm-no-error 'init-repo-after-create) (magit-init default-directory) (call-interactively #'magithub-create)) - (with-temp-message "Creating repository on Github..." + (with-temp-message "Creating repository on GitHub..." (setq repo (magithub-request (if org (ghubp-post-orgs-org-repos org repo) (ghubp-post-user-repos repo))))) - (magithub--random-message "Creating repository on Github...done!") + (magithub--random-message "Creating repository on GitHub...done!") (magit-status-internal default-directory) (magit-remote-add "origin" (magithub-repo--clone-url repo)) (magit-refresh) @@ -160,7 +159,8 @@ ;; This is not very clever, but it gets the job done. I'd like to ;; either have instant feedback on what's valid or not allow users ;; to enter invalid names at all. Could code from Ivy be used? - (while (not (s-matches-p valid-regexp (setq ret (read-string prompt nil nil dirnam)))) + (while (not (s-matches-p valid-regexp + (setq ret (read-string prompt nil nil dirnam)))) (message "invalid name") (sit-for 1)) ret)) @@ -171,26 +171,30 @@ (if prefix (format "%s %s" prefix msg) msg))) (defun magithub-fork () - "Fork 'origin' on Github." + "Fork 'origin' on GitHub." (interactive) (unless (magithub-github-repository-p) - (user-error "Not a Github repository")) + (user-error "Not a GitHub repository")) + (magithub-confirm 'fork) (let* ((repo (magithub-repo)) - (fork (with-temp-message "Forking repository on Github..." + (fork (with-temp-message "Forking repository on GitHub..." (magithub-request (ghubp-post-repos-owner-repo-forks repo))))) - (when (y-or-n-p "Create a spinoff branch? ") + (when (magithub-confirm-no-error 'fork-create-spinoff) (call-interactively #'magit-branch-spinoff)) (magithub--random-message (let-alist repo (format "%s/%s forked!" .owner.login .name))) (let-alist fork - (when (y-or-n-p (format "Add %s as a remote in this repository? " .owner.login)) + (when (magithub-confirm-no-error 'fork-add-me-as-remote .owner.login) (magit-remote-add .owner.login (magithub-repo--clone-url fork)) (magit-set .owner.login "branch" (magit-get-current-branch) "pushRemote"))) (let-alist repo - (when (y-or-n-p (format "Set upstream to %s? " .owner.login)) + (when (magithub-confirm-no-error 'fork-set-upstream-to-me .owner.login) (call-interactively #'magit-set-branch*merge/remote))))) +(defvar magithub-clone-history nil + "History for `magithub-clone' prompt.") + (defun magithub-clone--get-repo () "Prompt for a user and a repository. Returns a sparse repository object." @@ -201,10 +205,11 @@ (while (not (and repo (string-match repo-regexp repo))) (setq repo (read-from-minibuffer (concat - "Clone Github repository " + "Clone GitHub repository " (if repo "(format is \"user/repo\"; C-g to quit)" "(user/repo)") ": ") - (when user (concat user "/"))))) + (when user (concat user "/")) + nil nil 'magithub-clone-history))) `((owner (login . ,(match-string 1 repo))) (name . ,(match-string 2 repo))))) @@ -216,52 +221,49 @@ (defun magithub-clone (repo dir) "Clone REPO. -Banned inside existing Github repositories if +Banned inside existing GitHub repositories if `magithub-clone-default-directory' is nil. See also `magithub-preferred-remote-method'." - (interactive (if (and (not magithub-clone-default-directory) - (magithub-github-repository-p)) - (user-error "Already in a Github repo") - (let ((repo (magithub-clone--get-repo))) - (condition-case _ - (let* ((repo (magithub-request - (ghubp-get-repos-owner-repo repo))) - (dirname (read-directory-name - "Destination: " - magithub-clone-default-directory - (alist-get 'name repo)))) - (list repo dirname)) - (ghub-404 (let-alist repo - (user-error "Repository %s/%s does not exist" - .owner.login .name))))))) + (interactive (let* ((repo (magithub-clone--get-repo)) + (repo (or (magithub-request + (ghubp-get-repos-owner-repo repo)) + (let-alist repo + (user-error "Repository %s/%s does not exist" + .owner.login .name)))) + (name (alist-get 'name repo)) + (dirname (read-directory-name + "Destination: " + magithub-clone-default-directory + name nil name))) + (list repo dirname))) ;; Argument validation (unless (called-interactively-p 'any) - (condition-case _ - (setq repo (magithub-request - (ghubp-get-repos-owner-repo repo))) - (ghub-404 - (let-alist repo - (user-error "Repository %s/%s does not exist" - .owner.login .name))))) + (unless (setq repo (magithub-request + (ghubp-get-repos-owner-repo repo))) + (let-alist repo + (user-error "Repository %s/%s does not exist" + .owner.login .name)))) + (let ((parent (file-name-directory dir))) + (unless (file-exists-p parent) + (when (magithub-confirm 'clone-create-directory parent) + (mkdir parent t)))) (unless (file-writable-p dir) - (user-error "%s does not exist or is not writable" dir)) + (user-error "%s is not writable" dir)) (let-alist repo - (when (y-or-n-p (format "Clone %s to %s? " .full_name dir)) - (let ((default-directory dir) - (magit-clone-set-remote.pushDefault t) - set-upstream set-proxy) - + (when (magithub-confirm-no-error 'clone .full_name dir) + (let (set-upstream set-proxy) (setq set-upstream - (and .fork (y-or-n-p (format (concat "This repository appears to be a fork of %s; " - "set upstream to that remote?") - .parent.full_name))) + (and .fork (magithub-confirm-no-error + 'clone-fork-set-upstream-to-parent + .parent.full_name)) set-proxy - (and set-upstream (y-or-n-p "Use upstream as a proxy for issues, etc.? "))) - + (and set-upstream (magithub-confirm-no-error + 'clone-fork-set-proxy-to-upstream))) (condition-case _ - (progn + (let ((default-directory dir) + (magit-clone-set-remote.pushDefault t)) (mkdir dir t) (magit-clone (magithub-repo--clone-url repo) dir) (while (process-live-p magit-this-process) @@ -270,41 +272,16 @@ (sit-for 1)) (when set-upstream (let ((upstream "upstream")) - (when set-proxy (magithub-proxy-set upstream)) + (when set-proxy (magit-set upstream "magithub.proxy")) (magit-remote-add upstream (magithub-repo--clone-url .parent)) - (magit-set-branch*merge/remote (magit-get-current-branch) upstream))))))))) + (magit-set-branch*merge/remote (magit-get-current-branch) + upstream))))))))) (defun magithub-clone--finished (user repo dir) "After finishing the clone, allow the user to jump to their new repo." - (when (y-or-n-p (format "%s/%s has finished cloning to %s. Open? " user repo dir)) + (when (magithub-confirm-no-error 'clone-open-magit-status user repo dir) (magit-status-internal (s-chop-suffix "/" dir)))) -(defun magithub-feature-autoinject (feature) - "Configure FEATURE to recommended settings. -If FEATURE is `all' or t, all known features will be loaded. - -Features: - -- `pull-request-merge' - (`magithub-pull-request-merge' inserted into `magit-am-popup') - -- `pull-request-checkout' - (`magithub-pull-request-checkout' inserted into `magit-branch-popup')" - (if (memq feature '(t all)) - (mapc #'magithub-feature-autoinject magithub-feature-list) - (cl-case feature - - (pull-request-merge - (magit-define-popup-action 'magit-am-popup - ?P "Apply patches from pull request" #'magithub-pull-request-merge)) - - (pull-request-checkout - (magit-define-popup-action 'magit-branch-popup - ?P "Checkout pull request" #'magithub-pull-request-checkout)) - - (t (user-error "unknown feature %S" feature))) - (add-to-list 'magithub-features (cons feature t)))) - (defun magithub-visit-thing () (interactive) (user-error diff -Nru magithub-0.1.5/magithub-faces.el magithub-0.1.7/magithub-faces.el --- magithub-0.1.5/magithub-faces.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-faces.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ -;;; magithub-faces.el --- faces of Magithub -*- lexical-binding: t; -*- +;;; magithub-faces.el --- faces of Magithub -*- lexical-binding: t; -*- -;; Copyright (C) 2017 Sean Allred +;; Copyright (C) 2017-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: faces @@ -43,6 +43,11 @@ "Face used for issue numbers." :group 'magithub-faces) +(defface magithub-issue-title-edit + '((t :inherit magithub-issue-title :inherit (git-commit-summary))) + "Face used for post titles during editing." + :group 'magithub-faces) + (defface magithub-issue-title-with-note '((t :inherit magithub-issue-title :inherit (git-commit-summary))) "Face used for issue titles when the issue has an attached note. diff -Nru magithub-0.1.5/magithub-issue.el magithub-0.1.7/magithub-issue.el --- magithub-0.1.5/magithub-issue.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-issue.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ ;;; magithub-issue.el --- Browse issues with Magithub -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Sean Allred +;; Copyright (C) 2016-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: tools @@ -29,6 +29,7 @@ (require 'ghub+) (require 'cl-lib) (require 'magit) +(require 'thingatpt) (require 'magithub-core) (require 'magithub-user) @@ -36,9 +37,6 @@ (declare-function magithub-issue-view "magithub-issue-view.el" (issue)) -(defvar-local magithub-issue nil - "The issue object associated with a buffer.") - (defvar magit-magithub-repo-issues-section-map (let ((m (make-sparse-keymap))) (define-key m [remap magit-visit-thing] #'magithub-repo-visit-issues) @@ -50,7 +48,8 @@ (define-key m [remap magit-visit-thing] #'magithub-issue-personal-note) m)) - ;; Core +;;; Core + (defmacro magithub-interactive-issue-or-pr (sym args doc &rest body) "Declare an interactive form that works on both issues and PRs. SYM is a postfix for the function symbol. An appropriate prefix @@ -91,8 +90,8 @@ `(magithub-request (ghubp-unpaginate (ghubp-get-repos-owner-repo-issues - ',(magithub-repo) - ,@params))) + ',(magithub-repo) + ,@params))) :message "Retrieving issue list...")) @@ -111,7 +110,8 @@ (ghubp-unpaginate (ghubp-get-repos-owner-repo-issues-number-comments ',repo ',issue)))))) - ;; Finding issues and pull requests +;;; Finding issues and pull requests + (defun magithub-issues () "Return a list of issue objects that are actually issues." (-filter #'magithub-issue--issue-is-issue-p @@ -122,7 +122,8 @@ (-filter #'magithub-issue--issue-is-pull-p (magithub--issue-list))) - ;; Sorting +;;; Sorting + (defcustom magithub-issue-sort-function #'magithub-issue-sort-ascending "Function used for sorting issues and pull requests in the @@ -142,7 +143,8 @@ "Sort ISSUES by `magithub-issue-sort-function'." (sort issues magithub-issue-sort-function)) - ;; Getting issues from the user +;;; Getting issues from the user + (defun magithub-issue--format-for-read (issue) "Format ISSUE as a string suitable for completion." (let-alist issue (format "%3d %s" .number .title))) @@ -157,19 +159,19 @@ t default)) (defun magithub-issue-completing-read-issues (&optional default) "Read an issue in the minibuffer with completion." - (interactive (list (magithub-thing-at-point 'issue))) + (interactive (list (thing-at-point 'github-issue))) (magithub-issue--completing-read "Issue: " default (list #'magithub-issue--issue-is-issue-p))) (defun magithub-issue-completing-read-pull-requests (&optional default) "Read a pull request in the minibuffer with completion." - (interactive (list (magithub-thing-at-point 'pull-request))) + (interactive (list (thing-at-point 'github-pull-request))) (magithub-issue--completing-read "Pull Request: " default (list #'magithub-issue--issue-is-pull-p))) (defun magithub-interactive-issue () - (or (magithub-thing-at-point 'issue) + (or (thing-at-point 'github-issue) (magithub-issue-completing-read-issues))) (defun magithub-interactive-pull-request () - (or (magithub-thing-at-point 'pull-request) + (or (thing-at-point 'github-pull-request) (magithub-issue-completing-read-pull-requests))) (defun magithub-issue-find (number) @@ -188,7 +190,7 @@ (magithub-cache :issues `(magithub-request (ghubp-get-repos-owner-repo-issues-number - ',repo '((number . ,num)))) + ',repo '((number . ,num)))) :message (format "Getting issue %s#%d..." (magithub-repo-name repo) num)))) @@ -284,12 +286,15 @@ (let-alist issue (magit-insert-heading (format (format "%%%ds %%s" (1+ pad-num-to-len)) ;1+ accounts for # - (propertize (format "#%d" .number) 'face 'magithub-issue-number) - (propertize .title 'face (if (magithub-issue-has-personal-note-p issue) - 'magithub-issue-title-with-note - 'magithub-issue-title)))) + (propertize (format "#%d" .number) + 'face 'magithub-issue-number) + (propertize .title + 'face (if (magithub-issue-has-personal-note-p issue) + 'magithub-issue-title-with-note + 'magithub-issue-title)))) (run-hook-with-args 'magithub-issue-details-hook issue - (format " %s %%-12s" (make-string pad-num-to-len ?\ ))))))) + (format " %s %%-12s" + (make-string pad-num-to-len ?\ ))))))) (defvar magithub-issue-details-hook '(magithub-issue-detail-insert-personal-notes @@ -373,7 +378,9 @@ (setq maxchar (* 3 width)) (setq did-cut (< maxchar (length .body))) (setq maxchar (if did-cut (- maxchar 3) maxchar)) - (setq text (if did-cut (substring .body 0 (min (length .body) (* 4 width))) .body)) + (setq text (if did-cut + (substring .body 0 (min (length .body) (* 4 width))) + .body)) (setq text (replace-regexp-in-string " " "" text)) (setq text (let ((fill-column width)) (thread-last text @@ -392,16 +399,13 @@ (magithub-label-insert-list .labels) (insert "\n"))) - ;; Magithub-Status stuff +;;; Magithub-Status stuff -(defun magithub-issue-refresh (even-if-offline) - "Refresh issues for this repository. -If EVEN-IF-OFFLINE is non-nil, we'll still refresh (that is, -we'll hit the API) if Magithub is offline." - (interactive "P") - (let ((magithub-cache (if even-if-offline nil magithub-cache))) - (magithub-cache-without-cache :issues - (ignore (magithub--issue-list)))) +(defun magithub-issue-refresh () + "Refresh issues for this repository." + (interactive) + (magithub-cache-without-cache :issues + (magithub--issue-list)) (when (derived-mode-p 'magit-status-mode) (magit-refresh))) @@ -423,14 +427,13 @@ (define-key map [remap magithub-browse-thing] #'magithub-issue-browse) (define-key map [remap magit-refresh] #'magithub-issue-refresh) map) - "Keymap for `magithub-issue-list' sections.") + "Keymap for `magithub-issues-list' sections.") (defvar magit-magithub-pull-request-section-map (let ((map (make-sparse-keymap))) - (set-keymap-parent map magithub-map) - (define-key map [remap magit-visit-thing] #'magithub-pull-visit) - (define-key map [remap magithub-browse-thing] #'magithub-pull-browse) - (define-key map "L" #'magithub-issue-add-labels) + (set-keymap-parent map magit-magithub-issues-list-section-map) + (define-key map [remap magithub-issue-visit] #'magithub-pull-visit) + (define-key map [remap magithub-issue-browse] #'magithub-pull-browse) map) "Keymap for `magithub-pull-request' sections.") @@ -443,9 +446,9 @@ map) "Keymap for `magithub-pull-request-list' sections.") -;;; By maintaining these as lists of functions, we're setting -;;; ourselves up to be able to dynamically apply new filters from the -;;; status buffer (e.g., 'bugs' or 'questions' assigned to me) +;; By maintaining these as lists of functions, we're setting +;; ourselves up to be able to dynamically apply new filters from the +;; status buffer (e.g., 'bugs' or 'questions' assigned to me) (defcustom magithub-issue-issue-filter-functions nil "List of functions that filter issues. Each function will be supplied a single issue object. If any @@ -467,8 +470,8 @@ (interactive (when (magithub-verify-manage-labels t) (let* ((fmt (lambda (l) (alist-get 'name l))) - (issue (or (magithub-thing-at-point 'issue) - (magithub-thing-at-point 'pull-request))) + (issue (or (thing-at-point 'github-issue) + (thing-at-point 'github-pull-request))) (current-labels (alist-get 'labels issue)) (to-remove (magithub--completing-read-multiple "Remove labels: " current-labels fmt))) @@ -478,14 +481,16 @@ nil nil current-labels))))) (when (magithub-request (ghubp-patch-repos-owner-repo-issues-number - (magithub-repo) issue `((labels . ,labels)))) + (magithub-repo) issue `((labels . ,labels)))) (setcdr (assq 'labels issue) labels)) (when (derived-mode-p 'magit-status-mode) (magit-refresh))) +;;;###autoload (defun magithub-issue--insert-issue-section () - "Insert Github issues if appropriate." - (when (and (magithub-usable-p) + "Insert GitHub issues if appropriate." + (when (and (magithub-settings-include-issues-p) + (magithub-usable-p) (alist-get 'has_issues (magithub-repo))) (magithub-issue--insert-generic-section (magithub-issues-list) @@ -493,12 +498,13 @@ (magithub-issues) magithub-issue-issue-filter-functions))) +;;;###autoload (defun magithub-issue--insert-pr-section () - "Insert Github pull requests if appropriate." - (when (magithub-usable-p) + "Insert GitHub pull requests if appropriate." + (when (and (magithub-settings-include-pull-requests-p) + (magithub-usable-p)) (magithub-feature-maybe-idle-notify - 'pull-request-merge - 'pull-request-checkout) + 'pull-request-merge) (magithub-issue--insert-generic-section (magithub-pull-requests-list) "Pull Requests" @@ -511,7 +517,7 @@ `(when-let ((,sym-filtered (magithub-filter-all ,filters ,list))) (magit-insert-section ,spec (insert (format "%s%s:" - (propertize ,title 'face 'magit-header-line) + (propertize ,title 'face 'magit-section-heading) (if ,filters (propertize " (filtered)" 'face 'magit-dimmed) ""))) @@ -559,19 +565,15 @@ (when (magithub-usable-p) (number-to-string (length (magithub-pull-requests))))) -(magithub--deftoggle magithub-toggle-pull-requests "pull requests" t - magit-status-sections-hook #'magithub-issue--insert-pr-section) -(magithub--deftoggle magithub-toggle-issues "issues" t - magit-status-sections-hook #'magithub-issue--insert-issue-section) +;;; Pull Request handling - ;; Pull Request handling (defun magithub-pull-request (repo number) "Retrieve a pull request in REPO by NUMBER." (magithub-cache :issues `(magithub-request (ghubp-get-repos-owner-repo-pulls-number - ',repo '((number . ,number)))) + ',repo '((number . ,number)))) :message (format "Getting pull request %s#%d..." (magithub-repo-name repo) @@ -593,45 +595,8 @@ (magit-branch-p branch) (string= remote (magit-get-push-remote branch)))))) -(defun magithub-pull-request-checkout (pull-request) - "Checkout PULL-REQUEST. -PULL-REQUEST is the full object; not just the issue subset." - (interactive (list - (let ((pr (or (magithub-thing-at-point 'pull-request) - (magithub-issue-completing-read-pull-requests)))) - (magithub-request - (ghubp-get-repos-owner-repo-pulls-number - (magithub-repo) - `((number . ,(alist-get 'number pr)))))))) - (let-alist pull-request - (let ((remote .user.login) - (branch (format "%s/%s" .user.login .head.ref))) - (cond - ((magithub-pull-request-checked-out pull-request) - (with-temp-message (format "PR#%d is already checked out somewhere; checking out %s" - .number branch) - (magit-checkout branch) - (magit-fetch remote (magit-fetch-arguments)))) - ((magit-branch-p branch) - (user-error "Cannot checkout pull request: branch `%s' already exists; rename branch on remote" branch)) - (t - (magithub--run-git-synchronously - ;; get remote - (unless (magit-remote-p remote) - (magit-remote-add remote (magithub-repo--clone-url .head.repo))) - (magit-fetch remote (magit-fetch-arguments)) - ;; create branch - (magit-git-success "branch" branch .base.sha) ; also sets upstream to base ref - ;; set push to remote branch - (magit-set (concat "refs/heads/" .base.ref) "branch" branch "merge") - (magit-set "." "branch" branch "remote") ;same as merge - (magit-set remote "branch" branch "pushRemote") - (magit-set (number-to-string .number) "branch" branch "magithub" "sourcePR") - ;; set descripiton - (magit-set (concat "PR: " .title) "branch" branch "description") - ;; Checkout - (magit-git-success "checkout" branch) - (magit-refresh))))))) +(make-obsolete 'magithub-pull-request-checkout 'magit-checkout-pull-request "0.1.6") +(defalias 'magithub-pull-request-checkout #'magit-checkout-pull-request) (provide 'magithub-issue) ;;; magithub-issue.el ends here diff -Nru magithub-0.1.5/magithub-issue-post.el magithub-0.1.7/magithub-issue-post.el --- magithub-0.1.5/magithub-issue-post.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-issue-post.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,174 +1,98 @@ +;;; magithub-issue-post.el --- -*- lexical-binding: t; -*- + +;; Copyright (C) 2017-2018 Sean Allred + +;; Author: Sean Allred + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;;; Code: + (require 'magithub-core) (require 'magithub-issue) (require 'magithub-label) - -(require 'widget) -(eval-and-compile - (require 'wid-edit)) +(require 'magithub-edit-mode) (declare-function magithub-issue-view "magithub-issue-view.el" (issue)) -(define-derived-mode magithub-issue-post-mode nil - "Magithub Issue Post" - "Major mode for posting Github issues and pull requests.") - (defvar-local magithub-issue--extra-data nil) -(defvar-local magithub-issue--widgets nil - "Alist of symbols to widgets.") -(defun magithub-issue--widget-get (key) - (alist-get key magithub-issue--widgets)) -(defun magithub-issue--widget-value (key) - (widget-value (magithub-issue--widget-get key))) - -(setq magithub-issue-post-mode-map - (let ((m (copy-keymap widget-keymap))) - (define-key m (kbd "C-c RET") #'magithub-issue-wsubmit) - (define-key m (kbd "C-c C-k") #'magithub-issue-wcancel) - (define-key m "b" #'magithub-issue-w-jump-to-body) - m)) -(add-hook 'magithub-issue-post-mode-hook - #'magithub-bug-reference-mode-on) - -(defun magithub-issue-w-beginning-of-buffer-dwim () - (interactive) - (let ((start-of-body (magithub-issue--w-start-of-body))) - (goto-char - (if (= (point) start-of-body) - (point-min) - start-of-body)))) -(defun magithub-issue-w-end-of-buffer-dwim () - (interactive) - (let ((end-of-body (magithub-issue--w-end-of-body))) - (goto-char - (if (= (point) end-of-body) - (point-max) - end-of-body)))) -(defun magithub-issue-w-jump-to-body () +(defun magithub-issue-post-submit () (interactive) - (if (or (not (widget-at)) - (eq 'checkbox (widget-type (widget-at)))) - (goto-char (magithub-issue--w-start-of-body)) - (call-interactively #'self-insert-command))) - -(defun magithub-issue--w-start-of-body () - (save-excursion - (goto-char (widget-get (magithub-issue--widget-get 'body) :from)) - (forward-line) - (point))) -(defun magithub-issue--w-end-of-body () - (save-excursion - (goto-char (widget-get (magithub-issue--widget-get 'body) :to)) - (backward-char 3) - (point))) -(defun magithub-issue-w-next-widget-dwim () - (interactive) - (ignore-errors - (let ((start (magithub-issue--w-start-of-body)) - (end (magithub-issue--w-end-of-body)) - (keys (kbd (substitute-command-keys - "\\[magithub-issue-w-next-widget-dwim]")))) - (if (or (<= (point) start) (<= end (point))) - (call-interactively #'widget-forward) - (when-let ((func (with-temp-buffer - (key-binding keys)))) - (call-interactively func)))))) - -(define-widget 'magithub-issue-title 'editable-field - "Issue / pull-request title entry" - :tag "Title" - :format "%t: %v \n\n") -(define-widget 'magithub-issue-labels 'checklist - "Tag entry" - :greedy t - :tag "Labels" - :format "%t:\n%v \n\n") -(define-widget 'magithub-issue-text 'text - "Issue / pull-request body entry" - :tag "Body" - :format "%t:\n%v\n\n" - :inline nil) - -(defun magithub-issue--new-form (repo issue - buffer-name - header - show-labels-p - submit-caption - submit-function - cancel-caption - cancel-function) - "Opens a new widget-based form for issue/PR submission. - -REPO should be a full repository object. - -ISSUE should be an issue object. The `title' and `labels' -properties are respected and prepopulate the form." - (let-alist `((repo . ,repo) (issue . ,issue)) - (with-current-buffer (generate-new-buffer buffer-name) - (magithub-issue-post-mode) - (setq header-line-format - (substitute-command-keys - (s-join " | " (list header - "submit: \\[magithub-issue-wsubmit]" - "cancel: \\[magithub-issue-wcancel]")))) - (push - (cons 'title (widget-create 'magithub-issue-title - :size 76 - .issue.title)) - magithub-issue--widgets) - - (when show-labels-p - (push (cons 'labels - (let ((w (apply #'widget-create 'magithub-issue-labels - (mapcar (lambda (label) `(item ,(alist-get 'name label))) - (magithub-label-list))))) - (widget-value-set w (ghubp-get-in-all '(name) .issue.labels)) - w)) - magithub-issue--widgets)) - - (push (cons 'body (widget-create 'magithub-issue-text)) - magithub-issue--widgets) - - (widget-insert "\n") - (widget-create 'push-button :notify submit-function submit-caption) - (widget-insert " ") - (widget-create 'push-button :notify cancel-function cancel-caption) - (widget-insert "\n") - (widget-setup) - (magithub-issue-w-jump-to-body) - ;; GFM-mode doesn't handle line breaks just yet - (visual-line-mode 1) - (current-buffer)))) - -(defun magithub-issue-new (repo title labels) - (interactive - (let-alist (setq repo (magithub-repo)) - (list repo - (read-string (format "Issue title (%s): " .full_name)) - (when .permissions.push - (magithub-label-read-labels "Labels: "))))) - - (let-alist repo + (let ((issue (magithub-issue-post--parse-buffer)) + (repo (magithub-repo))) + (when (s-blank-p (alist-get 'title issue)) + (user-error "Title is required")) + (when (magithub-repo-push-p repo) + (when-let ((issue-labels (magithub-label-read-labels "Labels: "))) + (push (cons 'labels issue-labels) issue))) + (magithub-confirm 'submit-issue) + (let ((issue (magithub-request + (ghubp-post-repos-owner-repo-issues repo issue)))) + (magithub-edit-delete-draft) + (magithub-issue-view issue)))) + +(defun magithub-issue-post--parse-buffer () + (let ((lines (split-string (buffer-string) "\n"))) + `((title . ,(s-trim (car lines))) + (body . ,(s-trim (mapconcat #'identity (cdr lines) "\n")))))) + +(defun magithub-issue-new (repo) + (interactive (list (magithub-repo))) + (let* ((repo (magithub-repo repo)) + (name (magithub-repo-name repo))) (with-current-buffer - (magithub-issue--new-form - repo `((title . ,title) (labels . ,labels)) - "*magithub-issue*" - (format "Creating an issue for %s" .full_name) - .permissions.push - "Create new issue" - #'magithub-issue-wsubmit-issue - "Cancel" - #'magithub-issue-wcancel) - (setq magithub-issue--extra-data '((kind . issue))) - (magithub-issue--template-insert "ISSUE_TEMPLATE") - (switch-to-buffer-other-window (current-buffer))))) - -(defun magithub-issue--template-insert (filename) - "Inserts template FILENAME into the issue body" - (save-excursion - (magithub-issue-w-jump-to-body) - (when-let ((template (magithub-issue--template-find filename))) - (insert-file-contents template)))) + (magithub-edit-new (format "*magithub-issue: %s*" name) + :header (format "Creating an issue for %s" name) + :submit #'magithub-issue-post-submit + :file (expand-file-name "new-issue-draft" + (magithub-repo-data-dir repo)) + :template (magithub-issue--template-text "ISSUE_TEMPLATE")) + (font-lock-add-keywords nil `((,(rx bos (group (*? any)) eol) 1 + 'magithub-issue-title-edit t))) + (magithub-bug-reference-mode-on) + (magit-display-buffer (current-buffer))))) + +(defun magithub-pull-request-new-from-issue + (repo issue base head &optional maintainer-can-modify) + "Create a pull request from an existing issue. +REPO is the parent repository of ISSUE. BASE and HEAD are as +they are in `magithub-pull-request-new'." + (interactive (if-let ((issue-at-point (thing-at-point 'github-issue))) + (let-alist (magithub-pull-request-new-arguments) + (let ((allow-maint-mod (magithub-confirm-no-error + 'pr-allow-maintainers-to-submit))) + (magithub-confirm 'submit-pr-from-issue + (magithub-issue-reference issue-at-point) + .user+head .base) + (list .repo issue-at-point .base .head allow-maint-mod))) + (user-error "No issue detected at point"))) + (let ((pull-request `((head . ,head) + (base . ,base) + (issue . ,(alist-get 'number issue))))) + (when maintainer-can-modify + (push (cons 'maintainer_can_modify t) pull-request)) + (magithub-request + (ghubp-post-repos-owner-repo-pulls repo pull-request)))) + +(defun magithub-issue--template-text (template) + (with-temp-buffer + (when-let ((template (magithub-issue--template-find template))) + (insert-file-contents template) + (buffer-string)))) (defun magithub-issue--template-find (filename) "Find an appropriate template called FILENAME and returns its absolute path. @@ -191,107 +115,109 @@ (defun magithub-remote-branches-choose (prompt remote &optional default) "Using PROMPT, choose a branch on REMOTE." - (magit-completing-read - (format "[%s] %s" - (magithub-repo-name (magithub-repo-from-remote remote)) - prompt) - (magithub-remote-branches remote) - nil t nil nil default)) + (let ((branches (magithub-remote-branches remote))) + (magit-completing-read + (format "[%s] %s" + (magithub-repo-name (magithub-repo-from-remote remote)) + prompt) + branches + nil t nil nil (and (member default branches) default)))) (defun magithub-pull-request-new-arguments () - (let* ((this-repo (magithub-read-repo "Fork's remote (this is you!)")) + (unless (magit-get-push-remote) + (user-error "Nothing on remote yet; have you pushed your branch? Aborting")) + (let* ((this-repo (magithub-read-repo "Fork's remote (this is you!) ")) (this-repo-owner (let-alist this-repo .owner.login)) (parent-repo (or (alist-get 'parent this-repo) this-repo)) (this-remote (car (magithub-repo-remotes-for-repo this-repo))) (on-this-remote (string= (magit-get-push-remote) this-remote)) (base-remote (car (magithub-repo-remotes-for-repo parent-repo))) - (head (magithub-remote-branches-choose - "Head branch" this-remote - (when on-this-remote - (magit-get-current-branch)))) + (head-branch (let ((branch (magithub-remote-branches-choose + "Head branch" this-remote + (when on-this-remote + (magit-get-current-branch))))) + (unless (magit-rev-verify (magit-get-push-branch branch)) + (user-error "`%s' has not yet been pushed to your fork (%s)" + branch (magithub-repo-name this-repo))) + branch)) (base (magithub-remote-branches-choose "Base branch" base-remote - (when on-this-remote - (magit-get-upstream-branch head)))) - (head (if (string= this-remote base-remote) - head - (format "%s:%s" this-repo-owner head)))) - (unless (y-or-n-p (format "You are about to create a pull request to merge branch `%s' into %s:%s; is this what you wanted to do?" - head (magithub-repo-name parent-repo) base)) - (user-error "Aborting")) - (let-alist parent-repo - (list parent-repo base head - (read-string (format "Pull request title (%s/%s): " - .owner.login .name)))))) + (or (and on-this-remote + (magit-get-upstream-branch head-branch)) + (let-alist parent-repo .default_branch)))) + (user+head (format "%s:%s" this-repo-owner head-branch))) + (when (magithub-request (ghubp-get-repos-owner-repo-pulls parent-repo nil + :head user+head)) + (user-error "A pull request on %s already exists for head \"%s\"" + (magithub-repo-name parent-repo) + user+head)) + `((repo . ,parent-repo) + (base . ,base) + (head . ,(if (string= this-remote base-remote) + head-branch + user+head)) + (head-no-user . ,head-branch) + (fork . ,this-repo) + (user+head . ,user+head)))) -(defun magithub-pull-request-new (repo base head title) +(defun magithub-pull-request-new (repo base head head-no-user) "Create a new pull request." - (interactive (magithub-pull-request-new-arguments)) - (when (ghubp-get-repos-owner-repo-pulls repo nil :head head) - (user-error "A pull request on %s already exists for head %s" - (magithub-repo-name repo) - head)) - (let-alist repo + (interactive (let-alist (magithub-pull-request-new-arguments) + (magithub-confirm 'pre-submit-pr .user+head + (magithub-repo-name .repo) .base) + (list .repo .base .head .head-no-user))) + (let ((is-single-commit + (string= "1" (magit-git-string "rev-list" "--count" (format "%s.." base))))) + (unless is-single-commit + (apply #'magit-log (list (format "%s..%s" base head)) (magit-log-arguments))) (with-current-buffer - (magithub-issue--new-form - repo `((title . ,title)) - "*magithub-pull-request*" - (format "PR %s/%s (%s->%s)" .owner.login .name head base) - nil - "Submit new pull request" - #'magithub-issue-wsubmit-pull-request - "Cancel" - #'magithub-issue-wcancel) + (let ((template (magithub-issue--template-text "PULL_REQUEST_TEMPLATE"))) + (magithub-edit-new (format "*magithub-pull-request: %s into %s:%s*" + head + (magithub-repo-name repo) + base) + :header (let-alist repo (format "PR %s/%s (%s->%s)" + .owner.login .name head base)) + :submit #'magithub-pull-request-submit + :file (expand-file-name "new-pull-request-draft" + (magithub-repo-data-dir repo)) + :template template + :content (when is-single-commit + ;; when we only want to merge one commit + ;; insert that commit message as the initial content + (concat + (with-temp-buffer + (magit-git-insert "show" "-q" head-no-user "--format=%B") + (let ((fill-column (point-max))) + (fill-region (point-min) (point-max)) + (buffer-string))) + template)))) + (font-lock-add-keywords nil `((,(rx bos (group (*? any)) eol) 1 + 'magithub-issue-title-edit t))) + (magithub-bug-reference-mode-on) (setq magithub-issue--extra-data - `((base . ,base) (head . ,head) - (kind . pull-request))) - (magithub-issue--template-insert "PULL_REQUEST_TEMPLATE") - (switch-to-buffer-other-window (current-buffer))))) - -(defun magithub-issue-wsubmit () - (interactive) - (call-interactively - (pcase (alist-get 'kind magithub-issue--extra-data) - ('pull-request #'magithub-issue-wsubmit-pull-request) - ('issue #'magithub-issue-wsubmit-issue)))) + `((base . ,base) (head . ,head) (repo . ,repo))) + (magit-display-buffer (current-buffer))))) -(defun magithub-issue-wsubmit-issue (&rest _) +(defun magithub-pull-request-submit () (interactive) - (let ((issue `((title . ,(s-trim (magithub-issue--widget-value 'title))) - (body . ,(s-trim (magithub-issue--widget-value 'body))) - ,@(when-let ((vlabels (ignore-errors (magithub-issue--widget-value 'labels)))) - `((labels . ,vlabels)))))) - (when (s-blank-p (alist-get 'title issue)) - (user-error "Title is required")) - (when (yes-or-no-p "Are you sure you want to submit this issue? ") - (let ((issue (magithub-request - (ghubp-post-repos-owner-repo-issues (magithub-repo) issue)))) - (kill-buffer-and-window) - (magithub-issue-view issue))))) - -(defun magithub-issue-wsubmit-pull-request (&rest _) - (interactive) - (let ((pull-request `((title . ,(s-trim (magithub-issue--widget-value 'title))) - (body . ,(s-trim (magithub-issue--widget-value 'body))) - (base . ,(alist-get 'base magithub-issue--extra-data)) - (head . ,(alist-get 'head magithub-issue--extra-data))))) + (let ((pull-request `(,@(magithub-issue-post--parse-buffer) + (base . ,(alist-get 'base magithub-issue--extra-data)) + (head . ,(alist-get 'head magithub-issue--extra-data))))) (when (s-blank-p (alist-get 'title pull-request)) (user-error "Title is required")) - (when (yes-or-no-p "Are you sure you want to submit this pull request? ") - (when (y-or-n-p "Allow maintainers to modify this pull request? ") - (push (cons 'maintainer_can_modify t) pull-request)) - (let ((pr (condition-case err - (magithub-request - (ghubp-post-repos-owner-repo-pulls (magithub-repo) pull-request)) - (ghub-422 - (user-error "This pull request already exists!"))))) - (kill-buffer-and-window) - (magithub-issue-view pr))))) - -(defun magithub-issue-wcancel (&rest _) - (interactive) - ;; It'd be nice to have a sort of kill ring for cancelled issues - (when (yes-or-no-p "You will lose this buffer completely; are you sure? ") - (kill-buffer-and-window))) + (magithub-confirm 'submit-pr) + (when (magithub-confirm-no-error 'pr-allow-maintainers-to-submit) + (push (cons 'maintainer_can_modify t) pull-request)) + (let ((pr (condition-case _ + (magithub-request + (ghubp-post-repos-owner-repo-pulls + (alist-get 'repo magithub-issue--extra-data) + pull-request)) + (ghub-422 + (user-error "This pull request already exists!"))))) + (magithub-edit-delete-draft) + (magithub-issue-view pr)))) (provide 'magithub-issue-post) +;;; magithub-issue-post.el ends here diff -Nru magithub-0.1.5/magithub-issue-tricks.el magithub-0.1.7/magithub-issue-tricks.el --- magithub-0.1.5/magithub-issue-tricks.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-issue-tricks.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,3 +1,26 @@ +;;; magithub-issue-tricks.el --- -*- lexical-binding: t; -*- + +;; Copyright (C) 2017-2018 Sean Allred + +;; Author: Sean Allred + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;;; Code: + (require 'magit) (require 'magithub-issue) @@ -30,3 +53,4 @@ (message "#%d has been applied" .number))) (provide 'magithub-issue-tricks) +;;; magithub-issue-tricks.el ends here diff -Nru magithub-0.1.5/magithub-issue-view.el magithub-0.1.7/magithub-issue-view.el --- magithub-0.1.5/magithub-issue-view.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-issue-view.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ -;;; magithub-issue-view.el --- view issues -*- lexical-binding: t; -*- +;;; magithub-issue-view.el --- view issues -*- lexical-binding: t; -*- -;; Copyright (C) 2017 Sean Allred +;; Copyright (C) 2017-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: lisp @@ -38,7 +38,7 @@ m)) (define-derived-mode magithub-issue-view-mode magit-mode - "Issue View" "View Github issues with Magithub.") + "Issue View" "View GitHub issues with Magithub.") (defvar magithub-issue-view-headers-hook '(magithub-issue-view-insert-title @@ -106,10 +106,12 @@ ;;;###autoload (defun magithub-issue-view (issue) - "View ISSUE in a new buffer." + "View ISSUE in a new buffer. +Return the new buffer." (interactive (list (magithub-interactive-issue))) (let ((magit-generate-buffer-name-function #'magithub-issue-view--buffer-name)) - (magit-mode-setup-internal #'magithub-issue-view-mode (list issue) t))) + (magit-mode-setup-internal #'magithub-issue-view-mode (list issue) t) + (current-buffer))) (cl-defun magithub-issue-view-insert--generic (title text &optional type section-value &key face) "Insert a generic header line with TITLE: VALUE" @@ -118,7 +120,8 @@ (magit-insert-section ((eval type) section-value) (insert (format "%-10s" title) (or (and face (propertize text 'face face)) - text)) + text) + ?\n) (magit-insert-heading))) (defun magithub-issue-view-insert-title () @@ -128,10 +131,11 @@ (defun magithub-issue-view-insert-author () "Insert issue author." + (insert (format "%-10s" "Author:")) (let-alist magithub-issue - (magithub-issue-view-insert--generic "Author:" .user.login - 'magithub-user .user - :face 'magithub-user))) + (magit-insert-section (magithub-user .user) + (insert (propertize .user.login 'face 'magithub-user) ?\n) + (magit-insert-heading)))) (defun magithub-issue-view-insert-state () "Insert issue state (either \"open\" or \"closed\")." @@ -147,10 +151,9 @@ (defun magithub-issue-view-insert-labels () "Insert labels." - (magit-insert-section (magithub-label) - (insert (format "%-10s" "Labels:")) - (magithub-label-insert-list (alist-get 'labels magithub-issue)) - (magit-insert-heading))) + (insert (format "%-10s" "Labels:")) + (magithub-label-insert-list (alist-get 'labels magithub-issue)) + (insert ?\n)) (defun magithub-issue-view-insert-body () "Insert issue body." diff -Nru magithub-0.1.5/magithub-label.el magithub-0.1.7/magithub-label.el --- magithub-0.1.5/magithub-label.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-label.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,4 +1,29 @@ +;;; magithub-labels.el --- -*- lexical-binding: t; -*- + +;; Copyright (C) 2017-2018 Sean Allred + +;; Author: Sean Allred + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;;; Code: + +(require 'thingatpt) (require 'ghub+) + (require 'magithub-core) (defvar magit-magithub-label-section-map @@ -16,8 +41,9 @@ "Return a list of issue and pull-request labels." (magithub-cache :label `(magithub-request - (ghubp-get-repos-owner-repo-labels - (magithub-repo))) + (ghubp-unpaginate + (ghubp-get-repos-owner-repo-labels + ',(magithub-repo)))) :message "Loading labels...")) @@ -41,11 +67,11 @@ "Visit LABEL with `browse-url'. In the future, this will likely be replaced with a search on issues and pull requests with the label LABEL." - (interactive (list (magithub-thing-at-point 'label))) + (interactive (list (thing-at-point 'github-label))) (unless label (user-error "No label found at point to browse")) (unless (string= (ghubp-host) ghub-default-host) - (user-error "Label browsing not yet supported on Github Enterprise; pull requests welcome!")) + (user-error "Label browsing not yet supported on GitHub Enterprise; pull requests welcome!")) (let-alist (magithub-repo) (browse-url (format "%s/%s/%s/labels/%s" (ghubp-base-html-url) @@ -54,7 +80,7 @@ (defcustom magithub-label-color-replacement-alist nil "Make certain label colors easier to see. In your theme, you may find that certain colors are very -difficult to see. Customize this list to map Github's label +difficult to see. Customize this list to map GitHub's label colors to their Emacs replacements." :group 'magithub :type '(alist :key-type color :value-type color)) @@ -78,14 +104,14 @@ (defun magithub-label-color-replace (label new-color) "For LABEL, define a NEW-COLOR to use in the buffer." (interactive - (list (magithub-thing-at-point 'label) + (list (thing-at-point 'github-label) (magithub-core-color-completing-read "Replace label color: "))) (let ((label-color (concat "#" (alist-get 'color label)))) (if-let ((cell (assoc-string label-color magithub-label-color-replacement-alist))) (setcdr cell new-color) (push (cons label-color new-color) magithub-label-color-replacement-alist))) - (when (y-or-n-p "Save customization? ") + (when (magithub-confirm-no-error 'label-save-customized-colors) (customize-save-variable 'magithub-label-color-replacement-alist magithub-label-color-replacement-alist "Auto-saved by `magithub-label-color-replace'")) @@ -99,37 +125,35 @@ (defun magithub-label-remove (issue label) "From ISSUE, remove LABEL." (interactive (and (magithub-label--verify-manage) - (list (magithub-thing-at-point 'issue) - (magithub-thing-at-point 'label)))) + (list (thing-at-point 'github-issue) + (thing-at-point 'github-label)))) (unless issue (user-error "No issue here")) (unless label (user-error "No label here")) (let-alist label - (if (yes-or-no-p (format "Remove label %S from this issue? " .name)) - (prog1 (magithub-request - (ghubp-delete-repos-owner-repo-issues-number-labels-name - (magithub-issue-repo issue) issue label)) - (magithub-cache-without-cache :issues - (magit-refresh-buffer))) - (user-error "Aborted")))) + (magithub-confirm 'remove-label .name) + (prog1 (magithub-request + (ghubp-delete-repos-owner-repo-issues-number-labels-name + (magithub-issue-repo issue) issue label)) + (magithub-cache-without-cache :issues + (magit-refresh-buffer))))) (defun magithub-label-add (issue labels) "To ISSUE, add LABELS." - (interactive (list (magithub-thing-at-point 'issue) + (interactive (list (thing-at-point 'github-issue) (magithub-label-read-labels "Add labels: "))) (if (not (and issue labels)) (user-error "No issue/labels") - (if (yes-or-no-p (format "Add {%s} to %s#%s? " - (s-join "," (ghubp-get-in-all '(name) labels)) - (magithub-repo-name (magithub-issue-repo issue)) - (alist-get 'number issue))) - (prog1 (magithub-request - (ghubp-post-repos-owner-repo-issues-number-labels - (magithub-issue-repo issue) issue labels)) - (magithub-cache-without-cache :issues - (magit-refresh))) - (user-error "Aborted")))) + (magithub-confirm 'add-label + (s-join "," (ghubp-get-in-all '(name) labels)) + (magithub-repo-name (magithub-issue-repo issue)) + (alist-get 'number issue)) + (prog1 (magithub-request + (ghubp-post-repos-owner-repo-issues-number-labels + (magithub-issue-repo issue) issue labels)) + (magithub-cache-without-cache :issues + (magit-refresh))))) (defun magithub-label-insert (label) "Insert LABEL into the buffer. @@ -149,3 +173,4 @@ (insert " "))))) (provide 'magithub-label) +;;; magithub-labels.el ends here diff -Nru magithub-0.1.5/magithub-notification.el magithub-0.1.7/magithub-notification.el --- magithub-0.1.5/magithub-notification.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-notification.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ ;;; magithub-notification.el --- notification handling -*- lexical-binding: t; -*- -;; Copyright (C) 2017 Sean Allred +;; Copyright (C) 2017-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: lisp @@ -20,10 +20,12 @@ ;;; Commentary: -;; View and interact with notifications +;; View and interact with notifications. ;;; Code: +(require 'thingatpt) + (require 'magithub-issue-view) (defvar magit-magithub-notification-section-map @@ -69,10 +71,14 @@ (interactive) (magithub-cache-without-cache :notification (magit-refresh)) - (message "(magithub) notifcations refreshed")) + (message "(magithub) notifications refreshed")) -(defun magithub-notification-unread-p (notification) +(defun magithub-notification-read-p (notification) "Non-nil if NOTIFICATION has been read." + (not (magithub-notification-unread-p notification))) + +(defun magithub-notification-unread-p (notification) + "Non-nil if NOTIFICATION has been not been read." (alist-get 'unread notification)) (defconst magithub-notification-reasons @@ -86,7 +92,7 @@ ("subscribed" . "You're watching the repository.") ("team_mention" . "You were on a team that was mentioned.")) "Human-readable description of possible notification reasons. -Stripped from the Github API Docs: +Stripped from the GitHub API Docs: URL `https://developer.github.com/v3/activity/notifications/#notification-reasons'.") @@ -103,18 +109,19 @@ (defalias 'magithub-notification-visit #'magithub-notification-browse) (defun magithub-notification-browse (notification) "Visits the URL pointed to by NOTIFICATION." - (interactive (list (magithub-thing-at-point 'notification))) - (if notification - (let-alist notification - (cond - ((or (string= .subject.type "Issue") - (string= .subject.type "PullRequest")) - (magithub-issue-view (magithub-request (ghubp-follow-get .subject.url)))) - (t (if-let ((url (or .subject.latest_comment_url .subject.url)) - (html-url (alist-get 'html_url (magithub-request (ghubp-follow-get url))))) - (browse-url html-url) - (user-error "No target URL found"))))) - (user-error "No notification here"))) + (interactive (list (thing-at-point 'github-notification))) + (magithub-request + (if notification + (let-alist notification + (cond + ((member .subject.type '("Issue" "PullRequest")) + (ghubp-patch-notifications-threads-id notification) + (magithub-issue-view (ghubp-follow-get .subject.url))) + (t (if-let ((url (or .subject.latest_comment_url .subject.url)) + (html-url (alist-get 'html_url (ghubp-follow-get url)))) + (browse-url html-url) + (user-error "No target URL found"))))) + (user-error "No notification here")))) (defvar magithub-notification-details-hook '(magithub-notification-detail-insert-type diff -Nru magithub-0.1.5/magithub.org magithub-0.1.7/magithub.org --- magithub-0.1.5/magithub.org 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub.org 2018-06-03 00:42:26.000000000 +0000 @@ -1,105 +1,588 @@ -#+Title: Magithub -- Magit interfaces for GitHub -#+Author: Sean Allred +#+TITLE: Magithub -- Magit interfaces for GitHub +#+AUTHOR: Sean Allred +#+EMAIL: code@seanallred.com +#+DATE: 2017-2018 +#+LANGUAGE: en + +#+TEXINFO_DIR_CATEGORY: Emacs +#+TEXINFO_DIR_TITLE: Magithub: (magithub). +#+TEXINFO_DIR_DESC: Magit interfaces for GitHub +#+SUBTITLE: for version 0.1.5 (0.1.5-106-ge4a004c+1) +#+BIND: ox-texinfo+-before-export-hook ox-texinfo+-update-version-strings + +#+TEXINFO_DEFFN: t +#+OPTIONS: H:4 num:4 toc:2 + +You may also be interested in [[https://github.com/vermiculus/magithub/tree/master/RelNotes][the most current release notes]]. -* Preface Magithub provides an integrated GitHub experience through Magit's familiar interface. Just as Magit hopes to 'outsmart git', Magithub hopes to add smarts to GitHub for performing common tasks. Happy hacking! -** Installation +#+TEXINFO: @noindent +This manual is for Magithub version 0.1.5 (0.1.5-106-ge4a004c+1). + +#+BEGIN_QUOTE +Copyright (C) 2017-2018 Sean Allred + +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. +#+END_QUOTE + +* Installation +** _ :ignore: Magithub can be installed from [[http://melpa.milkbox.net/#/magithub][MELPA]] using =M-x list-packages= or by evaluating the following: + #+BEGIN_SRC elisp (package-install 'magithub) #+END_SRC Here is the basic recommended [[https://github.com/jwiegley/use-package][=use-package=]] configuration: + #+BEGIN_SRC elisp (use-package magithub :after magit :ensure t :config (magithub-feature-autoinject t)) #+END_SRC + If you prefer to install the package manually, this can of course be done via the usual means. +For more information, see [[info:emacs#Packages]]. + ** Authentication -Given Github's rate-limiting policy, Magithub is unlikely to ever support -running with authenticating. As such, you /must/ authenticate before you use -Magithub. (As of #107, Magithub will not even attempt go online until +Given GitHub's rate-limiting policy, Magithub is unlikely to ever support +running without authenticating. As such, you /must/ authenticate before you +use Magithub. (As of #107, Magithub will not even attempt go online until you're properly authenticated.) To authenticate, you can simply start using Magithub; Ghub should walk you -through the authentication process. (Your token is stored in one of your -~auth-sources~; see [[https://magit.vc/manual/ghub/How-Ghub-uses-Auth_002dSource.html#How-Ghub-uses-Auth_002dSource][Ghub's manual]] for details.) +through the authentication process unless you use two-factor authentication. +(Your token is stored in one of your ~auth-sources~; see [[https://magit.vc/manual/ghub/How-Ghub-uses-Auth_002dSource.html#How-Ghub-uses-Auth_002dSource][Ghub's manual]] for +details.) + +If you do use two-factor authentication, you must + +1. Manually create a GitHub token (from https://github.com/settings/tokens) + for scopes `repo`, `notifications` and `user` (see variable + ~magithub-github-token-scopes~) +2. Store it for Magithub per user in one of your ~auth-sources~ + (e.g. =~/.authinfo=). Write a line like this: + + #+BEGIN_EXAMPLE + machine api.github.com login YOUR_GITHUB_USERNAME^magithub password YOUR_GITHUB_TOKEN + #+END_EXAMPLE + +Beware that writing the token in plaintext in =~/.authinfo= (or elsewhere) is +not secure against attackers with access to that file. For details and +better alternatives (like using GPG), see Ghub's manual on [[https://magit.vc/manual/ghub/Manually-Creating-and-Storing-a-Token.html#Manually-Creating-and-Storing-a-Token][Manually Creating +and Storing a Token]] and [[https://magit.vc/manual/ghub/How-Ghub-uses-Auth_002dSource.html#How-Ghub-uses-Auth_002dSource][How Ghub uses Auth-Source]]. If you want to authenticate Ghub without using Magithub, you can simply evaluate the following: + #+BEGIN_SRC emacs-lisp (require 'magithub) (ghub-get "/user" nil :auth 'magithub) #+END_SRC + After Ghub walks you through the authentication process during evaluation, the ~ghub-get~ form should return familiar information (your login, email, etc.). If you're having trouble /authenticating/, [[https://github.com/magit/ghub/issues/new][open a Ghub issue]] or drop by -[[https://github.com/magit/ghub/issues/new][Magithub's Gitter channel]]. +[[https://gitter.im/vermiculus/magithub][Magithub's]] or [[https://gitter.im/magit/magit][Magit's]] Gitter channel. -* Documentation -** Interface -| Default Key | Description | -|-------------+------------------------------------| -| =H `= | Toggle Magithub-Status integration | +** Enterprise Support -*** Manipulating the Cache -When point is on a Magithub-controlled section (like the status header): -| Default Key | Description | -|-------------+----------------------------------------| -| =g= | Refresh this section only | -| =C-u g= | Like =g=, but works even in offline mode | - -Everywhere: -| Default Key | Description | -|-------------+-------------------------| -| =H g= | Refresh all GitHub data | +For GitHub Enterprise support, you'll need to add your enterprise domain to +~magithub-github-hosts~ so that Magithub can detect when it's in a GitHub +repository. You will also need to configure your =~/.authinfo= file +appropriately to authenticate to your domain; see Ghub's manual for details. + +* Introduction +** _ :ignore: + +Magithub tries to follow closely Magit's lead in general interface. Most of +its functionality is developed to tightly integrate with its section/ +framework. See [[https://magit.vc/manual/magit/Sections.html#Sections][Magit's documentation]] for information on how to navigate +using this framework. + +Magithub's functionality uses section-specific keymaps to expose +functionality. Where it makes sense, the following keys will map to +functions that 'do the right thing': + +- Key: w, magithub-browse-thing + + Open a browser to the thing at point. For instance, when point is on + issue 42 in your-favorite/github-repo, we'll open + =http://github.com/your-favorite/github-repo/issue/42=. + +- Key: a, magithub-add-thing + + Add something to the thing at point. For instance, on a list of labels, + you can add more labels. + +- Key: e, magithub-edit-thing + + Edit the thing at point, such as an issue. + +- Key: r, magithub-reply-thing + + Reply to the thing at point, such as a comment. + +Magithub also considers the similar placeholder commands introduced by Magit +which you may already be familiar with: + +- Key: k, magit-delete-thing +- Key: RET, magit-visit-thing + +These concepts are intended to provide a more consistent experience +throughout Magithub within Magit by categorizing your broader interactions +with all GitHub content. As with Magit, more commands are added as the +situation calls for it. + +** Note + +By default, Magithub enables itself in all repositories where =origin= points +to GitHub. + +- User Option: magithub-enabled-by-default + + When non-nil, Magithub is enabled by default. This is the fallback value + of git variable =magithub.enabled= is not set in this repository. + +- User Option: magithub-github-hosts + + A list of top-level domains that should be recognized as GitHub hosts. + +** Brief Tutorial + +Here's a script that will guide you through the major features of Magithub. +This is not a replacement for the documentation, but rather an example +workflow to whet your appetite. + +*** Clone a repository +#+BEGIN_EXAMPLE +M-x magithub-clone RET vermiculus/my-new-repository +#+END_EXAMPLE +Cloning a repository this way gets the clone URL from GitHub and forwards +that on to ~magit-clone~. If the repository is a fork, you're prompted to add +the parent is added under the =upstream= remote. + +Fork behavior may change in the future. It may be more appropriate to +actually/ clone the source repository and add your remote as a fork. This +will cover the 90% case (the 10% case being active forks of unmaintained +projects). + +*** Viewing project status +You are dropped into a status buffer for =vermiculus/my-new-repository=. You +see some open issues and pull requests. You move your cursor to an issue of +interest and =TAB= to expand it, seeing the author, when it was +created/updated, any labels, and a preview of the issue contents. + +If =vermiculus/my-new-repository= used any status checks, you would see those +statuses as a header in this buffer. + +*** Viewing and replying to an issue +You =RET= on the issue and are taken to a dedicated buffer for that issue. +You can now see its full contents as well as all comments. You'd like to +leave a comment -- a suggestion for a fix or an additional use-case to +consider -- you press =r= to open a new buffer to /reply/ to this issue. You +write your comment and =C-c C-c= to submit. But, oh no! You didn't turn on +=flyspell-mode= in markdown buffers, so you submitted a spelling error. A +simple =e= on the comment will /edit/ it. After submitting again with =C-c C-c=, +everything is well. + +Right now, other activity on the issue is not inserted into this buffer. +Press =w= to open the issue in your browser. + +*** Creating an issue +You notice a small issue in how some feature is implemented, so back in the +status buffer, you use =H i= to create a new issue. (While inside the GitHub +repository, you could've used any key bound to ~magithub-issue-new~.) The +first line is the title of the new issue; everything else is the body. You +submit the issue with =C-c C-c=. + +You come back a little while later to leave additional details -- you reply +to your own issue in a comment, but realize you should just edit your +original issue to avoid confusion. You =k= to /kill/ / delete the comment. + +*** Creating a pull request +Since you care about this project and want to help it succeed, you decide to +fix this issue yourself. You checkout a new branch (=b c my-feature RET=) and +get to work. -=H g= should only be used as a last resort; it completely nukes the cache -across all repositories (meaning all data will have to be re-retrieved). +Because you're so /awesome/, you're ready to push your commit to fix your +issue. After realizing you don't have push permissions to this repository, +you create a fork using =H f=. You push your branch to your new remote (named +after your username) and create a pull request with =H p=. You select the +head branch as =my-feature= and the base branch as =master= (or whatever the +production/staging branch is for the project). You fill out the pull +request template provided by the project (and inserted into your PR) and off +you go! -See also the variable documentation for =magithub-cache=. +* Status Buffer Integration + +The part of Magithub you're likely to interact with the most is +embedded right into Magit's status buffer. + +- Key: H, magithub-dispatch-popup + + Access many Magithub entry-points. See [[*Dispatch Popup]] for more details. + +- Key: H C e, FIXME + + Toggle status buffer integration in this repository. + +There are two integrations turned on by default: + +** Project Status + +Many services (such as Travis CI and CircleCI) will post statuses to +commits. A summary of these statuses are visible in the status buffer +headers. + +- Key: RET, magithub-ci-visit +- Key: w, magithub-ci-visit + + Visit the service's summary of this status. For example, a status posted + by Travis CI will open that build on Travis. + +- Key: g, magithub-ci-refresh + + Refresh statuses from GitHub and then refresh the current buffer. + +- Key: H C s, FIXME + + Enable/disable status checks in this repository. + +** Open Issues and Pull Requests + +These will also display in the status buffer. There's a lot of +functionality available right from an issue section. + +- Key: g, magithub-issue-refresh + + Refresh issues and pull requests from GitHub and then refresh the current + buffer. + +- Key: RET, magithub-issue-visit + + Open a new buffer to view an issue and its comments. + +- Key: w, magithub-issue-browse +- Key: w, magithub-pull-browse + + Browse this issue / pull request on GitHub. + +- Key: N, magithub-issue-personal-note + + Opens a buffer for offline note-taking. + +- Key: L, magithub-issue-add-labels + + Add labels to the issue. + +- Key: a, magithub-label-add +- Key: k, magithub-label-remove + + When point is on a label section, you can add/remove labels (provided you + have permission to do so). + +- Command: magithub-label-color-replace + + Labels are colored as they would be on GitHub. In some themes, this + produces an illegible or otherwise undesirable color. This command can + help you find a substitute for labels of this color. + +- Variable: magithub-issue-details-hook + + Control which issue details display in the status buffer. Functions + intended for this variable use the =magithub-issue-detail-insert-*= prefix. + + Performance note: judicious use of this variable can improve your overall + Magit experience in large buffers. + +- User Option: magithub-issue-issue-filter-functions +- User Option: magithub-issue-pull-request-filter-functions + + These are lists of functions which must all return non-nil for an issue/PR + to be displayed in the status buffer. They all receive the issue/PR + object as their sole argument. For example, you might want to filter out + issues labels =enhancement= from your list: + + #+BEGIN_SRC emacs-lisp + (setq magithub-issue-issue-filter-functions + (list (lambda (issue) ; don't show enhancement requests + (not + (member "enhancement" + (let-alist issue + (ghubp-get-in-all '(name) .labels))))))) + #+END_SRC + +*** Manipulating the Cache + When point is on a Magithub-controlled section (like the status header): + | Default Key | Description | + |-------------+--------------------------------------------| + | =g= | Refresh only this section's GitHub content | + | =C-u g= | Like =g=, but works on the whole buffer | *** Offline Mode -| Default Key | Description | -|-------------+---------------------| -| =H O= | Toggle offline mode | - -Offline mode was introduced for those times when you're on the go, but you'd -still like to have an overview of GitHub data in your status buffer. It's -also useful for folks who want to explicitly control when Emacs communicates -with GitHub -- for this purpose, you can use =C-u g= (discussed above) to pull -data from GitHub while in offline mode. + | Default Key | Description | + |-------------+---------------------| + | =H C c= | Toggle offline mode | + + Offline mode was introduced for those times when you're on the go, but you'd + still like to have an overview of GitHub data in your status buffer. It's + also useful for folks who want to explicitly control when Emacs communicates + with GitHub -- for this purpose, you can use =C-u g= (discussed above) to pull + data from GitHub while in offline mode. + + To start into offline mode everywhere, use + #+BEGIN_SRC sh + git config --global magithub.cache always + #+END_SRC -To start into offline mode, throw ~(setq magithub-cache t)~ in your -configuration. See that variable's documentation for details. + See the documentation for function ~magithub-settings--set-magithub.cache~ + for details on appropriate values. *** Controlling Sections -Sections like the issue list and the status header can be toggled with the -interactive functions of the form =magithub-toggle-*=. These functions have -no default keybinding. - -Since status checks can be API-hungry and not all projects use them, you can -disable the status header at the repository-level with =H ~=; see the Status -Checks section for more information. + Sections like the issue list and the status header can be toggled with the + interactive functions of the form =magithub-toggle-*=. These functions have + no default keybinding. + + Since status checks can be API-hungry and not all projects use them, you can + disable the status header at the repository-level with =H ~=; see the Status + Checks section for more information. + +* Dispatch Popup + +Much of Magithub's functionality, including configuration options, is behind +this popup. In Magit status buffers, it's bound to =H=. + +- Key: d, magithub-dashboard + + See [[*Dashboard]]. + +- Key: c, magithub-create + + Push a local repository up to GitHub. +- Key: H, magithub-browse + + Open the current repository in your browser. + +- Key: f, magithub-fork + + Fork this repository on GitHub. This will add your fork as a remote under + your username. For example, if user =octocat= forked Magit, we would see a + new remote called =octocat= pointing to =octocat/magit=. + +- Key: i, magithub-issue-new +- Key: p, magithub-pull-request-new + + Open a new buffer to create an issue or open a pull request. See + [[*Creating Content]]. + +** Configuration + +Per-repository configuration is controlled via git variables reachable from +the dispatch popup via =H C=. Use =? = to get online help for each +variable in that popup. + +- Key: C e, FIXME + + Turn Magithub on/off (completely). + +- Key: C s, FIXME + + Turn the project status header on/off. + +- Key: C c, FIXME + + Control whether Magithub is considered 'online'. This controls the + behavior of the the cache. This may go away in the future. See + [[*Manipulating the Cache]] for more details. + +- Key: C i, FIXME + + Toggle the issues section. + +- Key: C p, FIXME + + Toggle the pull requests section. + +- Key: C x, FIXME + + Set the 'proxy' used for this repository. See [[*Proxies]]. + +** Meta + +Since Magithub is so integrated with Magit, there's often confusion about +whom to ask for support (especially for users of preconfigured Emacsen like +Spacemacs and Prelude). Hopefully, these functions can direct you to the +appropriate spot. + +- Key: &, magithub--meta-new-issue + + Open the browser to create a new issue for Magithub functionality + described in this document. + +- Key: h, magithub--meta-help + + Open the browser to ask for help on Gitter, a GitHub-focused chatroom. + +* 'Features' + +Given that some features of Magithub are not desired by or appropriate for +every type of user, there are features that are not turned on by default. +These are features that are injected into standard Magit popups. + +The list of available features is available in constant +~magithub-feature-list~. Despite its name, this is an alist of symbols (i.e., +'features') to functions that install the feature. While the documentation +for each feature lives in that symbol, you would normally not otherwise +interact with it. + +- Function: magithub-feature-autoinject + + This function is the expected interface to install features. You will + normally use + #+BEGIN_SRC emacs-lisp + (magithub-feature-autoinject t) + #+END_SRC + in your configuration to install all features, but you have the option of + installing them one at a time using the symbols from constant + ~magithub-feature-list~ or as a list of those symbols: + #+BEGIN_SRC emacs-lisp + (magithub-feature-autoinject 'commit-browse) + (magithub-feature-autoinject '(commit-browse pull-request-merge)) + #+END_SRC + +* Cloning + +- Command: magithub-clone + + Clone a repository from GitHub. + +- User Option: magithub-clone-default-directory + + The default destination directory to use for cloning. + +- User Option: magithub-preferred-remote-method + + This option is a symbol indicating the preferred cloning method (between + HTTPS, SSH, and the =git://= protocol). + +* Dashboard + +The dashboard shows you information pertaining to /you/: +- notifications +- issues and pull requests you're assigned per repository +as well as contextual information like the logged-in user and [[https://developer.github.com/v3/#rate-limiting][rate-limiting]] +information. + +- Command: magithub-dashboard + + View your dashboard. + +- Key: ;, magithub-dashboard-popup + + Configure your global dashboard settings. + +- User Option: magithub-dashboard-show-read-notifications + + When non-nil, we'll show read notifications in the dashboard. + +* Creating Content + +It's great to read about what's been happening, but it's even better to +contribute your own thoughts and activity! + +- Key: H i, magithub-issue-new +- Key: H p, magithub-pull-request-new + + Create issues and pull requests. If you have push access to the + repository, you'll have the opportunity to add labels before you submit + the issue. + + Creating a pull request requires a HEAD branch, a BASE branch, and to know + which remote points to your fork. + +- Key: r, magithub-comment-new +- Key: r, magithub-comment-reply + + On an issue or pull request section, ~magithub-comment-new~ will allow you + to post a comment to that issue/PR. If point is already on a comment, + ~magithub-comment-reply~ will quote the comment at point for you. + +* Caching + +Caching is a complicated topic with a long Magithub history of, well, +failure. As of today, all data retrieved from the API is cached by +default. Using =g= on Magithub sections will usually refresh the information +in the buffer pertaining to that section. Otherwise, =C-u g= in any Magit +buffer will refresh all GitHub data in that buffer. + +This behavior may change in the future, but for now, it's the most stable +option. See + +* Proxies + +It's not uncommon to have repositories where the bug-tracker is in a +separate repository. For these cases, you can use the idea of 'proxies'. A +proxy is a remote (with a GitHub-associated URL) that you choose to use for +all GitHub API requests concerning the /actual/ current repository. This is +manifest in the git variable =magithub.proxy=. + +- Function: magithub-proxy-set-default + + If you consistently use a specific remote name for the bug tracker, you + can set it globally. + +All GitHub requests specific to the current repository context are routed +through ~magithub-repo~ which respects this proxy. + +* Configuring + +Magithub uses a standardized configuration scheme implemented using Git +variables. This allows your Magithub configuration to use all the powerful +features of =git-config(1)= and allows tight integration into Magit's existing +repository configuration workflows. + +To get the most up-to-date list of configuration options, use +#+BEGIN_SRC example +M-x apropos-command RET magithub-settings--set +#+END_SRC +to summarize them all. If an important option is missing from this manual, +reports and pull requests are welcome! + +The decision to implement these as Git variables stems from the varying size +of project repositories: it is extremely common to contribute to +exceptionally large repositories where including, say, the 'issues' section +would bring Emacs to its knees -- but it is equally common to work on +smaller repositories where such concern is negligible and the issues section +is a nice feature. + +* Unfiled ** Content *** Working with Repositories -**** General +**** DONE General | Default Key | Description | |--------------------+------------------------------------------------| | =H H= | Opens the current repository in the browser | @@ -112,7 +595,7 @@ also detect when the repository is a fork and can create and set an upstream remote accordingly (similar to =M-x magithub-fork=). -**** Issues +**** DONE Issues | Default Key | Description | |-------------+--------------------------| | =H i= | Create a new issue | @@ -130,7 +613,7 @@ Each function in the =*-functions= list must return non-nil for the issue to appear in the issue list. See also the documentation for that variable. -**** Forking and Pull Requests +**** DONE Forking and Pull Requests | Default Key | Description | |-------------+-------------------------------| | =H f= | Fork the current repository | @@ -140,7 +623,7 @@ =magithub-issue-pull-request-filter-functions=. See the section on issue-filtering for an example. -**** Labels +**** TODO Labels | Default Key | Description | |----------------------------------+-------------------------------------------| | =M-x magithub-label-color-replace= | Choose a new color for the label at point | @@ -151,7 +634,7 @@ another one. (This will apply to all labels in all repositories, but will of course not apply to all /shades/ of the original color.) -**** Status Checks +**** TODO Status Checks | Default Key | Description | |-------------+--------------------------------------------------| | =RET= | Visit the status's dashboard in your browser | @@ -178,22 +661,11 @@ you expand the header to show the individual checks, =RET= on those will take you straight to that check. -*** Your Dashboard +*** TODO Your Dashboard Check out =M-x magithub-dashboard= to view your notifications and issues assigned to you -** Using a Proxy - -It's not uncommon to have repositories where the bug-tracker is in a -separate repository. For these cases, you can use the idea of 'proxies'. A -proxy is a remote (with a GitHub-associated URL) that you choose to use for -all GitHub API requests concerning the /actual/ current repository. - -| Default Key | Description | -|-------------+----------------------------------------| -| =H x= | Set a proxy for all GitHub information | - -** 'Tricks' +** TODO 'Tricks' Most of Magithub is implemented in pure Elisp now, but there are a few lingering goodies that haven't been ported (since their real logic is @@ -204,56 +676,33 @@ GitHub repository to force the program to authenticate -- this avoids some weirdness on the Emacs side of things. -* FAQ -** I've authenticated, but some features are still not authorized. - -You might need to configure your personal access token with more -permissions. [[https://github.com/settings/tokens][Manage these permissions on GitHub.]] - -The scopes used by Magithub are detailed in the variable -~magithub-github-token-scopes~. - -** What's this 'Magithub features not configured' message? - -Sometimes, Magithub might complain that you haven't turned on certain -features. These features are those pieces of functionality that are -injected into Magit's internal popups by =magithub-feature-autoinject=. In -order to play nice with the ecosystem, Magithub doesn't turn these features -on by default (see the conversation around #42). Since pulling -functionality out of popups is currently infeasible with standard API, -Magithub had to instead provide a way to introduce these features manually. - -'Turning on a feature' only involves =magithub-feature-autoinject=. For -example, to turn on the 'PR Merge' feature: -#+BEGIN_SRC emacs-lisp - (magithub-feature-autoinject 'pull-request-merge) -#+END_SRC -This inserts the feature into =magithub-features= and places it in the -appropriate popup. - -To turn on all features, use ~(magithub-feature-autoinject t)~. - -If for some reason you don't want to use a feature, see the documentation -for =magithub-features= for instructions on how to disable specific (or all) -messages. - -** Does Magithub work for Enterprise instances? - -Mostly, yes, but it's entirely tested by you folks. I don't have access to -an Enterprise to test against, so YMMV. (There is one obscure bug here -(#115) but you're unlikely to run into it.) - -* Shameless Plugs - -Magithub is made possible by [[https://github.com/vermiculus/ghub-plus][Ghub+]] via [[https://github.com/vermiculus/apiwrap.el][API-Wrap.el]] over [[https://www.github.com/magit/ghub][Ghub]]. - -* Prior Art - -- The [[https://hub.github.com][=hub=]] command-line tool also hopes to add smarts to working with GitHub - repositories. Magithub used to be based on this tool, so it's certainly - an honorable mention. +* _ Copying +:PROPERTIES: +:COPYING: t +:END: + +#+BEGIN_QUOTE +Copyright (C) 2017-2018 Sean Allred + +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. +#+END_QUOTE +* _ :ignore: +# IMPORTANT: Also update ORG_ARGS and ORG_EVAL in the Makefile. # Local Variables: # fill-column: 76 +# eval: (require 'ox-extra nil t) +# eval: (require 'ox-texinfo+ nil t) +# eval: (and (featurep 'ox-extra) (ox-extras-activate '(ignore-headlines))) +# indent-tabs-mode: nil +# org-src-preserve-indentation: nil # End: diff -Nru magithub-0.1.5/magithub-orgs.el magithub-0.1.7/magithub-orgs.el --- magithub-0.1.5/magithub-orgs.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-orgs.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ -;;; magithub-orgs.el --- Organization handling -*- lexical-binding: t; -*- +;;; magithub-orgs.el --- Organization handling -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Sean Allred +;; Copyright (C) 2016-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: tools diff -Nru magithub-0.1.5/magithub-proxy.el magithub-0.1.7/magithub-proxy.el --- magithub-0.1.5/magithub-proxy.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-proxy.el 1970-01-01 00:00:00.000000000 +0000 @@ -1,44 +0,0 @@ -;;; magithub-proxy.el --- Fake repository context -*- lexical-binding: t; -*- - -;; Copyright (C) 2017 Sean Allred - -;; Author: Sean Allred -;; Keywords: tools - -;; This program is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . - -;;; Commentary: - -;; Use a different repository context - -;;; Code: - -(require 'magit) - -(defun magithub-proxy-set (remote) - "Use REMOTE as the issue-tracker in this repository." - (interactive (list (magit-read-remote "Issue-tracking remote"))) - (magit-set (unless (string= remote "origin") remote) "magithub" "proxy")) - -(defun magithub-proxy-set-default (remote) - "Use REMOTE as the default issue-tracker for all repositories. -If no issue-tracker is defined on a per-repo basis, this tracker is used. - -This modifies global git-config." - (interactive "sDefault remote name of issue-tracker (applies to all repositories): ") - (and (magit-git-success "config" "--global" "magithub.proxy" remote) - remote)) - -(provide 'magithub-proxy) -;;; magithub-proxy.el ends here diff -Nru magithub-0.1.5/magithub-repo.el magithub-0.1.7/magithub-repo.el --- magithub-0.1.5/magithub-repo.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-repo.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ -;;; magithub-repo.el --- repo tools -*- lexical-binding: t; -*- +;;; magithub-repo.el --- repo tools -*- lexical-binding: t; -*- -;; Copyright (C) 2017 Sean Allred +;; Copyright (C) 2017-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: lisp @@ -25,10 +25,9 @@ ;;; Code: (require 'magit) -(require 'magithub-core) +(require 'thingatpt) -(defvar-local magithub-repo nil - "Repo object.") +(require 'magithub-core) (defvar magit-magithub-repo-section-map (let ((m (make-sparse-keymap))) @@ -37,7 +36,7 @@ m)) (defun magithub-repo-browse (repo) - (interactive (list (magithub-thing-at-point 'repo))) + (interactive (list (thing-at-point 'github-repo))) (unless repo (user-error "No repository found at point")) (let-alist repo diff -Nru magithub-0.1.5/magithub-settings.el magithub-0.1.7/magithub-settings.el --- magithub-0.1.5/magithub-settings.el 1970-01-01 00:00:00.000000000 +0000 +++ magithub-0.1.7/magithub-settings.el 2018-06-03 00:42:26.000000000 +0000 @@ -0,0 +1,380 @@ +;;; magithub-settings.el --- repo-specific user settings -*- lexical-binding: t; -*- + +;; Copyright (C) 2018 Sean Allred + +;; Author: Sean Allred +;; Keywords: tools + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;;; Code: + +(require 'magit) + +(defconst magithub-settings-section "magithub" + "This string prefixes all Magithub-related git settings.") +(defconst magithub-settings-prefix "magithub" + "This string prefixes all Magithub-related git settings.") + +(defmacro magithub-settings--simple (popup key variable docstring choices default) + (declare (indent 3) (doc-string 4)) + (unless (stringp variable) + (error "VARIABLE must be a string: %S" variable)) + (let* ((variable (concat magithub-settings-section "." variable)) + (Nset (concat "magithub-settings--set-" variable)) + (Nfmt (concat "magithub-settings--format-" variable))) + (let ((Sset (intern Nset)) + (Sfmt (intern Nfmt)) + (docstring (format "%s\n\nThis is the Git variable %S." docstring variable))) + `(progn + (defun ,Sset () ,docstring (interactive) + (magit--set-popup-variable ,variable ,choices ,default)) + (defun ,(intern Nfmt) () ,(format "See `%s'." Nset) + (magit--format-popup-variable:choices ,variable ,choices ,default)) + (magit-define-popup-variable ',popup ,key ,variable ',Sset ',Sfmt) + ,variable)))) + +(defun magithub-settings--value-or (variable default &optional accessor) + (declare (indent 2)) + (if (magit-get variable) + (funcall (or accessor #'magit-get) variable) + default)) + +;;;###autoload (autoload 'magithub-settings-popup "magithub-settings" nil t) +(magit-define-popup magithub-settings-popup + "Popup console for managing Magithub settings." + 'magithub-commands) + +(magithub-settings--simple magithub-settings-popup ?e "enabled" + "Enable/disable all Magithub functionality." + '("true" "false") "true") + +(defun magithub-enabled-p () + "Returns non-nil if Magithub content is available." + (magithub-settings--value-or "magithub.enabled" t + #'magit-get-boolean)) + +(magithub-settings--simple magithub-settings-popup ?o "online" + "Controls whether Magithub is online or offline. + +- `true': requests are made to GitHub for missing data +- `false': no requests are made to GitHub + +In both cases, when there is data in the cache, that data is +used. Refresh the buffer with a prefix argument to disregard the +cache while refreshing: \\\\[universal-argument] \\[magit-refresh]" + '("true" "false") "true") + +(defun magithub-online-p () + "See `magithub-settings--set-magithub.online'. +Returns the value as t or nil." + (magithub-settings--value-or "magithub.online" t + #'magit-get-boolean)) + + +(magithub-settings--simple magithub-settings-popup ?s "status.includeStatusHeader" + "When true, the project status header is included in +`magit-status-headers-hook'." + '("true" "false") "true") + +(defun magithub-settings-include-status-p () + "Non-nil if the project status header should be included." + (magithub-settings--value-or "magithub.status.includeStatusHeader" t + #'magit-get-boolean)) + + +(magithub-settings--simple magithub-settings-popup ?i "status.includeIssuesSection" + "When true, project issues are included in +`magit-status-sections-hook'." + '("true" "false") "true") + +(defun magithub-settings-include-issues-p () + "Non-nil if the issues section should be included." + (magithub-settings--value-or "magithub.status.includeIssuesSection" t + #'magit-get-boolean)) + + +(magithub-settings--simple magithub-settings-popup ?p "status.includePullRequestsSection" + "When true, project pull requests are included in +`magit-status-sections-hook'." + '("true" "false") "true") + +(defun magithub-settings-include-pull-requests-p () + "Non-nil if the pull requests section should be included." + (magithub-settings--value-or "magithub.status.includePullRequestsSection" t + #'magit-get-boolean)) + + +(magithub-settings--simple magithub-settings-popup ?x "contextRemote" + "Use REMOTE as the proxy. +When set, the proxy is used whenever a GitHub repository is needed." + (magit-list-remotes) "origin") + +(defun magithub-settings-context-remote () + "Determine the correct remote to use for issue-tracking." + (magithub-settings--value-or "magithub.contextRemote" "origin")) + +(defvar magithub-confirmation + ;; todo: future enhancement - could allow prompt message to be a function. + '((pre-submit-pr short "You are about to create a pull request to merge branch `%s' into %s:%s; is this what you wanted to do?") + (submit-pr long "Are you sure you want to submit this pull request?") + (submit-pr-from-issue long "Are you sure you wish to create a PR based on %s by merging `%s' into `%s'?") + (pr-allow-maintainers-to-submit short "Allow maintainers to modify this pull request?") + (submit-issue long "Are you sure you want to submit this issue?") + (remove-label short "Remove label {%s} from this issue?") + (add-label short "Add label(s) {%s} to %s#%s?") + (create-repo-as-private long "Will this be a private repository?") + (init-repo-after-create short "Not inside a Git repository; initialize one here?") + (fork long "Fork this repository?") + (fork-create-spinoff short "Create a spinoff branch?") + (fork-add-me-as-remote short "Add %s as a remote in this repository?") + (fork-set-upstream-to-me short "Set upstream to %s?") + (clone long "Clone %s to %s?") + (clone-fork-set-upstream-to-parent short "This repository appears to be a fork of %s; set upstream to that remote?") + (clone-fork-set-proxy-to-upstream short "Use upstream as a proxy for issues, etc.?") + (clone-open-magit-status short "%s/%s has finished cloning to %s. Open?") + (clone-create-directory short "%s does not exist. Create it?") + (ci-refresh-when-offline short "Magithub offline; refresh statuses anyway?") + (refresh short "Refresh GitHub data?") + (refresh-when-API-unresponsive short "GitHub doesn't seem to be responding, are you sure?") + (label-save-customized-colors short "Save customization?") + (user-email short "Email @%s at \"%s\"?") + (user-email-self short "Email yourself?") + (assignee-add long "Assign '%s' to %s#%d?") + (assignee-remove long "Remove '%s' from %s#%d?") + (comment short "Submit this comment to %s?") + (comment-edit short "Commit this edit?") + (comment-delete long "Are you sure you wish to delete this comment?") + (report-error short "%s Report? (A bug report will be placed in your clipboard.)")) + "Alist of actions/decisions to their default behaviors and associated prompts. + +These behaviors can be overridden with (man)git-config. + +A behavior is one of the following symbols: + + `long' + use `yes-or-no-p' to confirm each time + + `short' + use `y-or-n-p' to confirm each time + + `allow' + always allow action + + `deny' + always deny action") + +(defun magithub-confirm (action &rest prompt-format-args) + "Confirm ACTION using Git config settings. +See `magithub--confirm'." + (magithub--confirm action prompt-format-args nil)) + +(defun magithub-confirm-no-error (action &rest prompt-format-args) + "Confirm ACTION using Git config settings. +See `magithub--confirm'." + (magithub--confirm action prompt-format-args t)) + +(defun magithub-settings--from-confirmation-action (action) + "Create a magithub.confirm.* setting from ACTION." + (concat + magithub-settings-section + ".confirm." + (let ((pascal-case (replace-regexp-in-string "-" "" (upcase-initials (symbol-name action))))) + ;; we have PascalCase, we want camelCase + (concat (downcase (substring pascal-case 0 1)) + (substring pascal-case 1))))) + +(defvar magithub-confirm-y-or-n-p-map + (let ((m (make-keymap))) + (define-key m (kbd "C-g") 'quit) ;don't know how to remap keyboard-quit here + (define-key m "q" 'quit) + (define-key m (kbd "C-u") 'cycle) + (define-key m "y" 'allow) + (define-key m "n" 'deny) + m)) + +(defvar magithub-confirm-yes-or-no-p-map + (let ((m (make-sparse-keymap))) + (set-keymap-parent m minibuffer-local-map) + (define-key m [remap universal-argument] #'magithub--confirm-cycle-set-default-interactive) + m)) + +(defvar magithub-confirm--current-cycle nil + "Control how a response should be saved. +This variable should never be set globally; always let-bind it! + + nil + Do not save the response + + `local' + Save response locally + + `global' + Save response globally") + +(defun magithub-confirm-yes-or-no-p (prompt var) + "Like `yes-or-no-p', but optionally save response to VAR." + (let ((p (concat prompt (substitute-command-keys " (yes, no, or \\[universal-argument]*) "))) + magithub-confirm--current-cycle old-cycle done answer changed) + (while (not done) + (setq changed (not (eq old-cycle magithub-confirm--current-cycle)) + old-cycle magithub-confirm--current-cycle + answer (read-from-minibuffer + (magithub--confirm-get-prompt-with-cycle + p var magithub-confirm--current-cycle) + ;; default in what was already entered if the save-behavior changed + (when changed answer) + magithub-confirm-yes-or-no-p-map nil + 'yes-or-no-p-history)) + ;; If the user activated `magithub--confirm-cycle-set-default-interactive', + ;; `magithub-confirm--current-cycle' will have been updated. + (when (and (eq old-cycle magithub-confirm--current-cycle) + (stringp answer)) + (setq answer (downcase (s-trim answer))) + (if (member answer '("yes" "no")) + (setq done t) + (message "Please answer yes or no. ") + (sleep-for 2)))) + (when magithub-confirm--current-cycle + (magithub--confirm-cycle-save-var-value + var (pcase answer + ("yes" "allow") + ("no" "deny")))) + (string= answer "yes"))) + +(defun magithub-confirm-y-or-n-p (prompt var) + "Like `y-or-n-p', but optionally save response to VAR." + (let ((cursor-in-echo-area t) + (newprompt (format "%s (y, n, C-u*) " prompt)) + magithub-confirm--current-cycle done answer varval explain) + (while (not done) + (setq newprompt + (if explain + (format "%s (please answer y or n or use C-u to cycle through and set default answers) " prompt) + (format "%s (y, n, C-u*) " prompt)) + explain nil + answer + (lookup-key magithub-confirm-y-or-n-p-map + (vector + (read-key (magithub--confirm-get-prompt-with-cycle + newprompt var magithub-confirm--current-cycle))))) + (pcase answer + (`quit (keyboard-quit)) + (`cycle (magithub--confirm-cycle-set-default)) + (`allow (setq done t varval "allow")) + (`deny (setq done t varval "deny")) + (_ (setq explain t)))) + (when (stringp varval) + (magithub--confirm-cycle-save-var-value var varval)) + (eq answer 'allow))) + +(defun magithub--confirm-cycle-save-var-value (var val) + "Save VAR with VAL locally or globally. +See `magithub-confirm--current-cycle'." + (pcase magithub-confirm--current-cycle + (`local (magit-set val var)) + (`global (magit-set val "--global" var)))) + +(defun magithub--confirm-cycle-set-default-interactive () + "In `magithub--confirm-yes-or-no-p', update behavior." + (interactive) + (magithub--confirm-cycle-set-default) + (exit-minibuffer)) + +(defun magithub--confirm-cycle-set-default () + (setq magithub-confirm--current-cycle + (cadr (member magithub-confirm--current-cycle + '(nil local global))))) + +(defun magithub--confirm-get-prompt-with-cycle (prompt var cycle) + "Get an appropriate PROMPT associated with VAR for CYCLE. +See `magithub-confirm--current-cycle'." + (propertize + (pcase cycle + (`local (format "%s[and don't ask again: git config %s] " prompt var)) + (`global (format "%s[and don't ask again: git config --global %s] " prompt var)) + (_ prompt)) + 'face 'minibuffer-prompt)) + +(defun magithub--confirm (action prompt-format-args noerror) + "Confirm ACTION using Git config settings. + +When PROMPT-FORMAT-ARGS is non-nil, the prompt piece of ACTION's +confirmation spec is passed through `format' with these +arguments. + +Unless NOERROR is non-nil, denying ACTION will result in a user +error to abort the action. + +This is like `magit-confirm', but a little more powerful. It +might belong in Magit, but we'll see how it goes." + (let ((spec (alist-get action magithub-confirmation)) + var default prompt setting choice) + (unless spec + (magithub-error "No confirmation settings for %S" spec)) + (unless (= 2 (length spec)) + (magithub-error "Spec for %S must have 2 members: %S" action spec)) + (setq default (symbol-name (nth 0 spec)) + prompt (nth 1 spec) + var (magithub-settings--from-confirmation-action action)) + (when prompt-format-args + (setq prompt (apply #'format prompt prompt-format-args))) + (when (and (null noerror) (string= "deny" default)) + (magithub-error (format "The default for %S is deny, but this will cause an error" action))) + + (setq setting (magithub-settings--value-or var default)) + (when (and (string= setting "deny") + (null noerror)) + (let ((raw (magit-git-string "config" "--show-origin" var)) + washed) + (when (string-match (rx bos (group (+ any)) (+ space) (group (+ any)) eos) raw) + (setq washed (format "%s => %s" + (match-string 1 raw) + (match-string 2 raw)))) + (user-error "Abort per %s [%s]" var (or washed raw)))) + + (setq choice + (pcase setting + ("long" (magithub-confirm-yes-or-no-p prompt var)) + ("short" (magithub-confirm-y-or-n-p prompt var)) + ("allow" t) + ("deny" nil))) + + (or choice + (unless noerror + (user-error "Abort"))))) + +(defun magithub-confirm-set-default-behavior (action default &optional globally) + "Set the default behavior of ACTION to DEFAULT. + +If GLOBALLY is non-nil, make this configuration apply globally. + +See `magithub-confirmation' for valid values of DEFAULT." + (unless (alist-get action magithub-confirmation) + (error "Action not defined: %S" action)) + (let* ((var (magithub-settings--from-confirmation-action action)) + (args (list var))) + (when globally + (push "--global" args)) + (apply #'magit-set + (if (memq default '(long short allow deny)) + (symbol-name default) + (error "Invalid default behavior: %S" default)) + args) + default)) + +(provide 'magithub-settings) +;;; magithub-settings.el ends here diff -Nru magithub-0.1.5/magithub.texi magithub-0.1.7/magithub.texi --- magithub-0.1.5/magithub.texi 1970-01-01 00:00:00.000000000 +0000 +++ magithub-0.1.7/magithub.texi 2018-06-03 00:42:26.000000000 +0000 @@ -0,0 +1,1011 @@ +\input texinfo @c -*- texinfo -*- +@c %**start of header +@setfilename magithub.info +@settitle Magithub -- Magit interfaces for GitHub +@documentencoding UTF-8 +@documentlanguage en +@c %**end of header + +@copying +@quotation +Copyright (C) 2017-2018 Sean Allred + +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. +@end quotation +@end copying + +@dircategory Emacs +@direntry +* Magithub: (magithub). Magit interfaces for GitHub. +@end direntry + +@finalout +@titlepage +@title Magithub -- Magit interfaces for GitHub +@subtitle for version 0.1.5 (0.1.5-106-ge4a004c+1) +@author Sean Allred +@page +@vskip 0pt plus 1filll +@insertcopying +@end titlepage + +@contents + +@ifnottex +@node Top +@top Magithub -- Magit interfaces for GitHub + +You may also be interested in @uref{https://github.com/vermiculus/magithub/tree/master/RelNotes, the most current release notes}. + +Magithub provides an integrated GitHub experience through Magit's familiar +interface. Just as Magit hopes to 'outsmart git', Magithub hopes to add +smarts to GitHub for performing common tasks. + +Happy hacking! + +@noindent +This manual is for Magithub version 0.1.5 (0.1.5-106-ge4a004c+1). + +@quotation +Copyright (C) 2017-2018 Sean Allred + +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. +@end quotation +@end ifnottex + +@menu +* Installation:: +* Introduction:: +* Status Buffer Integration:: +* Dispatch Popup:: +* 'Features':: +* Cloning:: +* Dashboard:: +* Creating Content:: +* Caching:: +* Proxies:: +* Unfiled:: + +@detailmenu +--- The Detailed Node Listing --- + +Installation + +* Authentication:: +* Enterprise Support:: + +Introduction + +* Note:: +* Brief Tutorial:: + +Brief Tutorial + +* Clone a repository:: +* Viewing project status:: +* Viewing and replying to an issue:: +* Creating an issue:: +* Creating a pull request:: + + +Status Buffer Integration + +* Project Status:: +* Open Issues and Pull Requests:: + +Open Issues and Pull Requests + +* Manipulating the Cache:: +* Offline Mode:: +* Controlling Sections:: + + +Dispatch Popup + +* Configuration:: +* Meta:: + +Unfiled + +* Content:: +* 'Tricks':: + +Content + +* Working with Repositories:: +* Your Dashboard:: + + +@end detailmenu +@end menu + +@node Installation +@chapter Installation + +Magithub can be installed from @uref{http://melpa.milkbox.net/#/magithub, MELPA} using @samp{M-x list-packages} or by +evaluating the following: + +@lisp +(package-install 'magithub) +@end lisp + +Here is the basic recommended @uref{https://github.com/jwiegley/use-package, @samp{use-package}} configuration: + +@lisp +(use-package magithub + :after magit + :ensure t + :config (magithub-feature-autoinject t)) +@end lisp + +If you prefer to install the package manually, this can of course be done +via the usual means. + +For more information, see @ref{Packages,,,emacs,}. + +@menu +* Authentication:: +* Enterprise Support:: +@end menu + +@node Authentication +@section Authentication + +Given GitHub's rate-limiting policy, Magithub is unlikely to ever support +running without authenticating. As such, you @emph{must} authenticate before you +use Magithub. (As of #107, Magithub will not even attempt go online until +you're properly authenticated.) + +To authenticate, you can simply start using Magithub; Ghub should walk you +through the authentication process unless you use two-factor authentication. +(Your token is stored in one of your @code{auth-sources}; see @uref{https://magit.vc/manual/ghub/How-Ghub-uses-Auth_002dSource.html#How-Ghub-uses-Auth_002dSource, Ghub's manual} for +details.) + +If you do use two-factor authentication, you must + +@itemize +@item +Manually create a GitHub token (from @uref{https://github.com/settings/tokens}) +for scopes `repo`, `notifications` and `user` (see variable +@code{magithub-github-token-scopes}) + +@item +Store it for Magithub per user in one of your @code{auth-sources} +(e.g. @samp{~/.authinfo}). Write a line like this: + +@example +machine api.github.com login YOUR_GITHUB_USERNAME^magithub password YOUR_GITHUB_TOKEN +@end example +@end itemize + +Beware that writing the token in plaintext in @samp{~/.authinfo} (or elsewhere) is +not secure against attackers with access to that file. For details and +better alternatives (like using GPG), see Ghub's manual on @uref{https://magit.vc/manual/ghub/Manually-Creating-and-Storing-a-Token.html#Manually-Creating-and-Storing-a-Token, Manually Creating +and Storing a Token} and @uref{https://magit.vc/manual/ghub/How-Ghub-uses-Auth_002dSource.html#How-Ghub-uses-Auth_002dSource, How Ghub uses Auth-Source}. + +If you want to authenticate Ghub without using Magithub, you can simply +evaluate the following: + +@lisp +(require 'magithub) +(ghub-get "/user" nil :auth 'magithub) +@end lisp + +After Ghub walks you through the authentication process during evaluation, +the @code{ghub-get} form should return familiar information (your login, email, +etc.). + +If you're having trouble @emph{authenticating}, @uref{https://github.com/magit/ghub/issues/new, open a Ghub issue} or drop by +@uref{https://gitter.im/vermiculus/magithub, Magithub's} or @uref{https://gitter.im/magit/magit, Magit's} Gitter channel. + +@node Enterprise Support +@section Enterprise Support + +For GitHub Enterprise support, you'll need to add your enterprise domain to +@code{magithub-github-hosts} so that Magithub can detect when it's in a GitHub +repository. You will also need to configure your @samp{~/.authinfo} file +appropriately to authenticate to your domain; see Ghub's manual for details. + +@node Introduction +@chapter Introduction + +Magithub tries to follow closely Magit's lead in general interface. Most of +its functionality is developed to tightly integrate with its section/ +framework. See @uref{https://magit.vc/manual/magit/Sections.html#Sections, Magit's documentation} for information on how to navigate +using this framework. + +Magithub's functionality uses section-specific keymaps to expose +functionality. Where it makes sense, the following keys will map to +functions that 'do the right thing': + +@table @asis +@kindex w +@cindex magithub-browse-thing +@item @kbd{w} @tie{}@tie{}@tie{}@tie{}(@code{magithub-browse-thing}) + +Open a browser to the thing at point. For instance, when point is on +issue 42 in your-favorite/github-repo, we'll open +@samp{http://github.com/your-favorite/github-repo/issue/42}. + +@kindex a +@cindex magithub-add-thing +@item @kbd{a} @tie{}@tie{}@tie{}@tie{}(@code{magithub-add-thing}) + +Add something to the thing at point. For instance, on a list of labels, +you can add more labels. + +@kindex e +@cindex magithub-edit-thing +@item @kbd{e} @tie{}@tie{}@tie{}@tie{}(@code{magithub-edit-thing}) + +Edit the thing at point, such as an issue. + +@kindex r +@cindex magithub-reply-thing +@item @kbd{r} @tie{}@tie{}@tie{}@tie{}(@code{magithub-reply-thing}) + +Reply to the thing at point, such as a comment. +@end table + +Magithub also considers the similar placeholder commands introduced by Magit +which you may already be familiar with: + +@table @asis +@kindex k +@cindex magit-delete-thing +@item @kbd{k} @tie{}@tie{}@tie{}@tie{}(@code{magit-delete-thing}) +@kindex RET +@cindex magit-visit-thing +@item @kbd{RET} @tie{}@tie{}@tie{}@tie{}(@code{magit-visit-thing}) +@end table + +These concepts are intended to provide a more consistent experience +throughout Magithub within Magit by categorizing your broader interactions +with all GitHub content. As with Magit, more commands are added as the +situation calls for it. + +@menu +* Note:: +* Brief Tutorial:: +@end menu + +@node Note +@section Note + +By default, Magithub enables itself in all repositories where @samp{origin} points +to GitHub. + +@defopt magithub-enabled-by-default + +When non-nil, Magithub is enabled by default. This is the fallback value +of git variable @samp{magithub.enabled} is not set in this repository. +@end defopt + +@defopt magithub-github-hosts + +A list of top-level domains that should be recognized as GitHub hosts. +@end defopt + +@node Brief Tutorial +@section Brief Tutorial + +Here's a script that will guide you through the major features of Magithub. +This is not a replacement for the documentation, but rather an example +workflow to whet your appetite. + +@menu +* Clone a repository:: +* Viewing project status:: +* Viewing and replying to an issue:: +* Creating an issue:: +* Creating a pull request:: +@end menu + +@node Clone a repository +@subsection Clone a repository + +@example +M-x magithub-clone RET vermiculus/my-new-repository +@end example +Cloning a repository this way gets the clone URL from GitHub and forwards +that on to @code{magit-clone}. If the repository is a fork, you're prompted to add +the parent is added under the @samp{upstream} remote. + +Fork behavior may change in the future. It may be more appropriate to +actually/ clone the source repository and add your remote as a fork. This +will cover the 90% case (the 10% case being active forks of unmaintained +projects). + +@node Viewing project status +@subsection Viewing project status + +You are dropped into a status buffer for @samp{vermiculus/my-new-repository}. You +see some open issues and pull requests. You move your cursor to an issue of +interest and @samp{TAB} to expand it, seeing the author, when it was +created/updated, any labels, and a preview of the issue contents. + +If @samp{vermiculus/my-new-repository} used any status checks, you would see those +statuses as a header in this buffer. + +@node Viewing and replying to an issue +@subsection Viewing and replying to an issue + +You @samp{RET} on the issue and are taken to a dedicated buffer for that issue. +You can now see its full contents as well as all comments. You'd like to +leave a comment -- a suggestion for a fix or an additional use-case to +consider -- you press @samp{r} to open a new buffer to @emph{reply} to this issue. You +write your comment and @samp{C-c C-c} to submit. But, oh no! You didn't turn on +@samp{flyspell-mode} in markdown buffers, so you submitted a spelling error. A +simple @samp{e} on the comment will @emph{edit} it. After submitting again with @samp{C-c C-c}, +everything is well. + +Right now, other activity on the issue is not inserted into this buffer. +Press @samp{w} to open the issue in your browser. + +@node Creating an issue +@subsection Creating an issue + +You notice a small issue in how some feature is implemented, so back in the +status buffer, you use @samp{H i} to create a new issue. (While inside the GitHub +repository, you could've used any key bound to @code{magithub-issue-new}.) The +first line is the title of the new issue; everything else is the body. You +submit the issue with @samp{C-c C-c}. + +You come back a little while later to leave additional details -- you reply +to your own issue in a comment, but realize you should just edit your +original issue to avoid confusion. You @samp{k} to @emph{kill} / delete the comment. + +@node Creating a pull request +@subsection Creating a pull request + +Since you care about this project and want to help it succeed, you decide to +fix this issue yourself. You checkout a new branch (@samp{b c my-feature RET}) and +get to work. + +Because you're so @emph{awesome}, you're ready to push your commit to fix your +issue. After realizing you don't have push permissions to this repository, +you create a fork using @samp{H f}. You push your branch to your new remote (named +after your username) and create a pull request with @samp{H p}. You select the +head branch as @samp{my-feature} and the base branch as @samp{master} (or whatever the +production/staging branch is for the project). You fill out the pull +request template provided by the project (and inserted into your PR) and off +you go! + +@node Status Buffer Integration +@chapter Status Buffer Integration + +The part of Magithub you're likely to interact with the most is +embedded right into Magit's status buffer. + +@table @asis +@kindex H +@cindex magithub-dispatch-popup +@item @kbd{H} @tie{}@tie{}@tie{}@tie{}(@code{magithub-dispatch-popup}) + +Access many Magithub entry-points. See @ref{Dispatch Popup} for more details. + +@kindex H e +@cindex FIXME +@item @kbd{H e} @tie{}@tie{}@tie{}@tie{}(@code{FIXME}) + +Toggle status buffer integration in this repository. +@end table + +There are two integrations turned on by default: + +@menu +* Project Status:: +* Open Issues and Pull Requests:: +@end menu + +@node Project Status +@section Project Status + +Many services (such as Travis CI and CircleCI) will post statuses to +commits. A summary of these statuses are visible in the status buffer +headers. + +@table @asis +@kindex RET +@cindex magithub-ci-visit +@item @kbd{RET} @tie{}@tie{}@tie{}@tie{}(@code{magithub-ci-visit}) +@kindex w +@cindex magithub-ci-visit +@item @kbd{w} @tie{}@tie{}@tie{}@tie{}(@code{magithub-ci-visit}) + +Visit the service's summary of this status. For example, a status posted +by Travis CI will open that build on Travis. + +@kindex g +@cindex magithub-ci-refresh +@item @kbd{g} @tie{}@tie{}@tie{}@tie{}(@code{magithub-ci-refresh}) + +Refresh statuses from GitHub and then refresh the current buffer. + +@kindex H s +@cindex FIXME +@item @kbd{H s} @tie{}@tie{}@tie{}@tie{}(@code{FIXME}) + +Enable/disable status checks in this repository. +@end table + +@node Open Issues and Pull Requests +@section Open Issues and Pull Requests + +These will also display in the status buffer. There's a lot of +functionality available right from an issue section. + +@table @asis +@kindex g +@cindex magithub-issue-refresh +@item @kbd{g} @tie{}@tie{}@tie{}@tie{}(@code{magithub-issue-refresh}) + +Refresh issues and pull requests from GitHub and then refresh the current +buffer. + +@kindex RET +@cindex magithub-issue-visit +@item @kbd{RET} @tie{}@tie{}@tie{}@tie{}(@code{magithub-issue-visit}) + +Open a new buffer to view an issue and its comments. + +@kindex w +@cindex magithub-issue-browse +@item @kbd{w} @tie{}@tie{}@tie{}@tie{}(@code{magithub-issue-browse}) +@kindex w +@cindex magithub-pull-browse +@item @kbd{w} @tie{}@tie{}@tie{}@tie{}(@code{magithub-pull-browse}) + +Browse this issue / pull request on GitHub. + +@kindex N +@cindex magithub-issue-personal-note +@item @kbd{N} @tie{}@tie{}@tie{}@tie{}(@code{magithub-issue-personal-note}) + +Opens a buffer for offline note-taking. + +@kindex L +@cindex magithub-issue-add-labels +@item @kbd{L} @tie{}@tie{}@tie{}@tie{}(@code{magithub-issue-add-labels}) + +Add labels to the issue. + +@kindex a +@cindex magithub-label-add +@item @kbd{a} @tie{}@tie{}@tie{}@tie{}(@code{magithub-label-add}) +@kindex k +@cindex magithub-label-remove +@item @kbd{k} @tie{}@tie{}@tie{}@tie{}(@code{magithub-label-remove}) + +When point is on a label section, you can add/remove labels (provided you +have permission to do so). + +@end table + +@cindex magithub-label-color-replace +@deffn Command magithub-label-color-replace + +Labels are colored as they would be on GitHub. In some themes, this +produces an illegible or otherwise undesirable color. This command can +help you find a substitute for labels of this color. +@end deffn + +@defvar magithub-issue-details-hook + +Control which issue details display in the status buffer. Functions +intended for this variable use the @samp{magithub-issue-detail-insert-*} prefix. + +Performance note: judicious use of this variable can improve your overall +Magit experience in large buffers. +@end defvar + +@defopt magithub-issue-issue-filter-functions +@end defopt +@defopt magithub-issue-pull-request-filter-functions + +These are lists of functions which must all return non-nil for an issue/PR +to be displayed in the status buffer. They all receive the issue/PR +object as their sole argument. For example, you might want to filter out +issues labels @samp{enhancement} from your list: + +@lisp +(setq magithub-issue-issue-filter-functions + (list (lambda (issue) ; don't show enhancement requests + (not + (member "enhancement" + (let-alist issue + (ghubp-get-in-all '(name) .labels))))))) +@end lisp +@end defopt + +@menu +* Manipulating the Cache:: +* Offline Mode:: +* Controlling Sections:: +@end menu + +@node Manipulating the Cache +@subsection Manipulating the Cache + +When point is on a Magithub-controlled section (like the status header): +@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@headitem Default Key +@tab Description +@item @samp{g} +@tab Refresh only this section's GitHub content +@item @samp{C-u g} +@tab Like @samp{g}, but works on the whole buffer +@end multitable + +@node Offline Mode +@subsection Offline Mode + +@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaa} +@headitem Default Key +@tab Description +@item @samp{H C c} +@tab Toggle offline mode +@end multitable + +Offline mode was introduced for those times when you're on the go, but you'd +still like to have an overview of GitHub data in your status buffer. It's +also useful for folks who want to explicitly control when Emacs communicates +with GitHub -- for this purpose, you can use @samp{C-u g} (discussed above) to pull +data from GitHub while in offline mode. + +To start into offline mode everywhere, use +@example +git config --global magithub.cache always +@end example + +See the documentation for function @code{magithub-settings--set-magithub.cache} +for details on appropriate values. + +@node Controlling Sections +@subsection Controlling Sections + +Sections like the issue list and the status header can be toggled with the +interactive functions of the form @samp{magithub-toggle-*}. These functions have +no default keybinding. + +Since status checks can be API-hungry and not all projects use them, you can +disable the status header at the repository-level with @samp{H ~}; see the Status +Checks section for more information. + +@node Dispatch Popup +@chapter Dispatch Popup + +Much of Magithub's functionality, including configuration options, is behind +this popup. In Magit status buffers, it's bound to @samp{H}. + +@table @asis +@kindex d +@cindex magithub-dashboard +@item @kbd{d} @tie{}@tie{}@tie{}@tie{}(@code{magithub-dashboard}) + +See @ref{Dashboard}. + +@kindex c +@cindex magithub-create +@item @kbd{c} @tie{}@tie{}@tie{}@tie{}(@code{magithub-create}) + +Push a local repository up to GitHub. + +@kindex H +@cindex magithub-browse +@item @kbd{H} @tie{}@tie{}@tie{}@tie{}(@code{magithub-browse}) + +Open the current repository in your browser. + +@kindex f +@cindex magithub-fork +@item @kbd{f} @tie{}@tie{}@tie{}@tie{}(@code{magithub-fork}) + +Fork this repository on GitHub. This will add your fork as a remote under +your username. For example, if user @samp{octocat} forked Magit, we would see a +new remote called @samp{octocat} pointing to @samp{octocat/magit}. + +@kindex i +@cindex magithub-issue-new +@item @kbd{i} @tie{}@tie{}@tie{}@tie{}(@code{magithub-issue-new}) +@kindex p +@cindex magithub-pull-request-new +@item @kbd{p} @tie{}@tie{}@tie{}@tie{}(@code{magithub-pull-request-new}) + +Open a new buffer to create an issue or open a pull request. See +@ref{Creating Content}. +@end table + +@menu +* Configuration:: +* Meta:: +@end menu + +@node Configuration +@section Configuration + +Per-repository configuration is controlled via git variables reachable from +the dispatch popup via @samp{H C}. Use @samp{? } to get online help for each +variable in that popup. + +@table @asis +@kindex C e +@cindex FIXME +@item @kbd{C e} @tie{}@tie{}@tie{}@tie{}(@code{FIXME}) + +Turn Magithub on/off (completely). + +@kindex C s +@cindex FIXME +@item @kbd{C s} @tie{}@tie{}@tie{}@tie{}(@code{FIXME}) + +Turn the project status header on/off. + +@kindex C c +@cindex FIXME +@item @kbd{C c} @tie{}@tie{}@tie{}@tie{}(@code{FIXME}) + +Control whether Magithub is considered 'online'. This controls the +behavior of the the cache. This may go away in the future. See +Controlling the Cache for more details. FIXME there is no such node. + +@kindex C i +@cindex FIXME +@item @kbd{C i} @tie{}@tie{}@tie{}@tie{}(@code{FIXME}) + +Toggle the issues section. + +@kindex C p +@cindex FIXME +@item @kbd{C p} @tie{}@tie{}@tie{}@tie{}(@code{FIXME}) + +Toggle the pull requests section. + +@kindex C x +@cindex FIXME +@item @kbd{C x} @tie{}@tie{}@tie{}@tie{}(@code{FIXME}) + +Set the 'proxy' used for this repository. See @ref{Proxies}. +@end table + +@node Meta +@section Meta + +Since Magithub is so integrated with Magit, there's often confusion about +whom to ask for support (especially for users of preconfigured Emacsen like +Spacemacs and Prelude). Hopefully, these functions can direct you to the +appropriate spot. + +@table @asis +@kindex & +@cindex magithub--meta-new-issue +@item @kbd{&} @tie{}@tie{}@tie{}@tie{}(@code{magithub--meta-new-issue}) + +Open the browser to create a new issue for Magithub functionality +described in this document. + +@kindex h +@cindex magithub--meta-help +@item @kbd{h} @tie{}@tie{}@tie{}@tie{}(@code{magithub--meta-help}) + +Open the browser to ask for help on Gitter, a GitHub-focused chatroom. +@end table + +@node 'Features' +@chapter 'Features' + +Given that some features of Magithub are not desired by or appropriate for +every type of user, there are features that are not turned on by default. +These are features that are injected into standard Magit popups. + +The list of available features is available in constant +@code{magithub-feature-list}. Despite its name, this is an alist of symbols (i.e., +'features') to functions that install the feature. While the documentation +for each feature lives in that symbol, you would normally not otherwise +interact with it. + +@defun magithub-feature-autoinject + +This function is the expected interface to install features. You will +normally use +@lisp +(magithub-feature-autoinject t) +@end lisp +in your configuration to install all features, but you have the option of +installing them one at a time using the symbols from constant +@code{magithub-feature-list} or as a list of those symbols: +@lisp +(magithub-feature-autoinject 'commit-browse) +(magithub-feature-autoinject '(commit-browse pull-request-merge)) +@end lisp +@end defun + +@node Cloning +@chapter Cloning + +@cindex magithub-clone +@deffn Command magithub-clone + +Clone a repository from GitHub. +@end deffn + +@defopt magithub-clone-default-directory + +The default destination directory to use for cloning. +@end defopt + +@defopt magithub-preferred-remote-method + +This option is a symbol indicating the preferred cloning method (between +HTTPS, SSH, and the @samp{git://} protocol). +@end defopt + +@node Dashboard +@chapter Dashboard + +The dashboard shows you information pertaining to @emph{you}: +@itemize +@item +notifications + +@item +issues and pull requests you're assigned per repository +@end itemize +as well as contextual information like the logged-in user and @uref{https://developer.github.com/v3/#rate-limiting, rate-limiting} +information. + +@cindex magithub-dashboard +@deffn Command magithub-dashboard + +View your dashboard. +@end deffn + +@table @asis +@kindex ; +@cindex magithub-dashboard-popup +@item @kbd{;} @tie{}@tie{}@tie{}@tie{}(@code{magithub-dashboard-popup}) + +Configure your global dashboard settings. + +@end table + +@defopt magithub-dashboard-show-read-notifications + +When non-nil, we'll show read notifications in the dashboard. +@end defopt + +@node Creating Content +@chapter Creating Content + +It's great to read about what's been happening, but it's even better to +contribute your own thoughts and activity! + +@table @asis +@kindex H i +@cindex magithub-issue-new +@item @kbd{H i} @tie{}@tie{}@tie{}@tie{}(@code{magithub-issue-new}) +@kindex H p +@cindex magithub-pull-request-new +@item @kbd{H p} @tie{}@tie{}@tie{}@tie{}(@code{magithub-pull-request-new}) + +Create issues and pull requests. If you have push access to the +repository, you'll have the opportunity to add labels before you submit +the issue. + +Creating a pull request requires a HEAD branch, a BASE branch, and to know +which remote points to your fork. + +@kindex r +@cindex magithub-comment-new +@item @kbd{r} @tie{}@tie{}@tie{}@tie{}(@code{magithub-comment-new}) +@kindex r +@cindex magithub-comment-reply +@item @kbd{r} @tie{}@tie{}@tie{}@tie{}(@code{magithub-comment-reply}) + +On an issue or pull request section, @code{magithub-comment-new} will allow you +to post a comment to that issue/PR. If point is already on a comment, +@code{magithub-comment-reply} will quote the comment at point for you. +@end table + +@node Caching +@chapter Caching + +Caching is a complicated topic with a long Magithub history of, well, +failure. As of today, all data retrieved from the API is cached by +default. Using @samp{g} on Magithub sections will usually refresh the information +in the buffer pertaining to that section. Otherwise, @samp{C-u g} in any Magit +buffer will refresh all GitHub data in that buffer. + +This behavior may change in the future, but for now, it's the most stable +option. See + +@node Proxies +@chapter Proxies + +It's not uncommon to have repositories where the bug-tracker is in a +separate repository. For these cases, you can use the idea of 'proxies'. A +proxy is a remote (with a GitHub-associated URL) that you choose to use for +all GitHub API requests concerning the @emph{actual} current repository. This is +manifest in the git variable @samp{magithub.proxy}. + +@defun magithub-proxy-set-default + +If you consistently use a specific remote name for the bug tracker, you +can set it globally. +@end defun + +All GitHub requests specific to the current repository context are routed +through @code{magithub-repo} which respects this proxy. + +@node Unfiled +@chapter Unfiled + +@menu +* Content:: +* 'Tricks':: +@end menu + +@node Content +@section Content + +@menu +* Working with Repositories:: +* Your Dashboard:: +@end menu + +@node Working with Repositories +@subsection Working with Repositories + +@menu +* General:: +* Issues:: +* Forking and Pull Requests:: +* Labels:: +* Status Checks:: +@end menu + +@node General +@subsubsection @strong{DONE} General + +@multitable {aaaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@headitem Default Key +@tab Description +@item @samp{H H} +@tab Opens the current repository in the browser +@item @samp{H c} +@tab Creates the current local repository on GitHub +@item @samp{M-x magithub-clone} +@tab Clone a repository +@end multitable + +@samp{magithub-clone} may appear to be a thin wrapper over @samp{magit-clone}, but it's +quite a bit smarter than that. We'll of course respect +@samp{magithub-preferred-remote-method} when cloning the repository, but we can +also detect when the repository is a fork and can create and set an upstream +remote accordingly (similar to @samp{M-x magithub-fork}). + +@node Issues +@subsubsection @strong{DONE} Issues + +@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaa} +@headitem Default Key +@tab Description +@item @samp{H i} +@tab Create a new issue +@item @samp{RET} +@tab Open the issue in GitHub +@end multitable + +You can filter issues with @samp{magithub-issue-issue-filter-functions}: +@lisp +(setq magithub-issue-issue-filter-functions + (list (lambda (issue) ; don't show enhancement requests + (not + (member "enhancement" + (let-alist issue + (ghubp-get-in-all '(name) .labels))))))) +@end lisp +Each function in the @samp{*-functions} list must return non-nil for the issue to +appear in the issue list. See also the documentation for that variable. + +@node Forking and Pull Requests +@subsubsection @strong{DONE} Forking and Pull Requests + +@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@headitem Default Key +@tab Description +@item @samp{H f} +@tab Fork the current repository +@item @samp{H p} +@tab Submit pull requests upstream +@end multitable + +You can also filter pull requests with +@samp{magithub-issue-pull-request-filter-functions}. See the section on +issue-filtering for an example. + +@node Labels +@subsubsection @strong{TODO} Labels + +@multitable {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@headitem Default Key +@tab Description +@item @samp{M-x magithub-label-color-replace} +@tab Choose a new color for the label at point +@end multitable + +By default, Magithub will adopt the color used by GitHub when showing +labels. In some themes, this doesn't provide enough contrast. Use @samp{M-x +magithub-label-color-replace} to replace the current label's color with +another one. (This will apply to all labels in all repositories, but will +of course not apply to all @emph{shades} of the original color.) + +@node Status Checks +@subsubsection @strong{TODO} Status Checks + +@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@headitem Default Key +@tab Description +@item @samp{RET} +@tab Visit the status's dashboard in your browser +@item @samp{TAB} +@tab On the status header, show individual CI details +@item @samp{H ~} +@tab Toggle status integration for this repository +@end multitable + +When the status buffer first opens, the status header is inserted at the top +and probably looks something like this: +@example +Status: Success +@end example + +You can get a breakdown of which checks succeeded and which failed by using +@samp{TAB}: +@example +Status: Success + Checks for ref: develop + Success The Travis CI build passed continuous-integration/travis-ci/push +@end example + +Pressing @samp{RET} on the header will take you to the dashboard associated with +that status check. If there's more than one status check here, you'll be +prompted to choose a check (e.g., Travis, Circle, CLA, @dots{}). Of course, if +you expand the header to show the individual checks, @samp{RET} on those will take +you straight to that check. + +@node Your Dashboard +@subsection @strong{TODO} Your Dashboard + +Check out @samp{M-x magithub-dashboard} to view your notifications and issues +assigned to you + +@node 'Tricks' +@section @strong{TODO} 'Tricks' + +Most of Magithub is implemented in pure Elisp now, but there are a few +lingering goodies that haven't been ported (since their real logic is +non-trivial). These definitions are relegated to @samp{magithub-issue-tricks.el}. + +Make sure to install @uref{https://hub.github.com, @samp{hub}} and add it to your @code{exec-path} if you intend to use +these functions. After installation, use @samp{hub browse} from a directory with a +GitHub repository to force the program to authenticate -- this avoids some +weirdness on the Emacs side of things. + +@bye diff -Nru magithub-0.1.5/magithub-user.el magithub-0.1.7/magithub-user.el --- magithub-0.1.5/magithub-user.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/magithub-user.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ -;;; magithub-user.el --- Inspect users -*- lexical-binding: t; -*- +;;; magithub-user.el --- Inspect users -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Sean Allred +;; Copyright (C) 2016-2018 Sean Allred ;; Author: Sean Allred ;; Keywords: lisp @@ -20,12 +20,13 @@ ;;; Commentary: -;; Code for dealing with the current users and other users +;; Code for dealing with the current user and other users. ;;; Code: (require 'ghub+) (require 'cl-lib) +(require 'thingatpt) (require 'magithub-core) @@ -72,35 +73,32 @@ (let-alist `((repo . ,(magithub-issue-repo issue)) (issue . ,issue) (user . ,user)) - (if (yes-or-no-p (format "Assign '%s' to %s#%d? " - .user.login - (magithub-repo-name .repo) - .issue.number)) - (prog1 (magithub-request - (ghubp-post-repos-owner-repo-issues-number-assignees - .repo .issue (list .user))) - (let ((sec (magit-current-section))) - (magithub-cache-without-cache :issues - (magit-refresh-buffer)) - (magit-section-show sec))) - (user-error "Aborted")))) + (magithub-confirm 'assignee-add + .user.login + (magithub-repo-name .repo) + .issue.number) + (prog1 (magithub-request + (ghubp-post-repos-owner-repo-issues-number-assignees + .repo .issue (list .user))) + (let ((sec (magit-current-section))) + (magithub-cache-without-cache :issues + (magit-refresh-buffer)) + (magit-section-show sec))))) (defun magithub-assignee-remove (issue user) (interactive (when (magithub-assignee--verify-manage) - (list (magithub-thing-at-point 'issue) - (magithub-thing-at-point 'user)))) + (list (thing-at-point 'github-issue) + (thing-at-point 'github-user)))) (let-alist `((repo . ,(magithub-issue-repo issue)) (issue . ,issue) (user . ,user)) - (if (yes-or-no-p (format "Remove '%s' from %s#%d? " - .user.login - (magithub-repo-name .repo) - .issue.number)) - (prog1 (magithub-request - (ghubp-delete-repos-owner-repo-issues-number-assignees .repo .issue (list .user))) - (magithub-cache-without-cache :issues - (magit-refresh-buffer))) - (user-error "Aborted")))) + (magithub-confirm .user.login + (magithub-repo-name .repo) + .issue.number) + (prog1 (magithub-request + (ghubp-delete-repos-owner-repo-issues-number-assignees .repo .issue (list .user))) + (magithub-cache-without-cache :issues + (magit-refresh-buffer))))) (defun magithub-user-choose (prompt &optional default-user) (let (ret-user new-username) @@ -127,27 +125,25 @@ (defalias 'magithub-user-visit #'magithub-user-browse) (defun magithub-user-browse (user) - "Open USER on Github." - (interactive (list (magithub-thing-at-point 'user))) + "Open USER on GitHub." + (interactive (list (thing-at-point 'github-user))) (if user (browse-url (alist-get 'html_url user)) (user-error "No user here"))) (defun magithub-user-email (user) "Email USER." - (interactive (list (magithub-thing-at-point 'user))) - (when (and (string= (alist-get 'login (magithub-user-me)) - (alist-get 'login user)) - (not (y-or-n-p "Email yourself? "))) - (user-error "Aborted")) + (interactive (list (thing-at-point 'github-user))) + (when (string= (alist-get 'login (magithub-user-me)) + (alist-get 'login user)) + (magithub-confirm 'user-email-self)) (unless user (user-error "No user here")) (let-alist user (unless .email (user-error "No email found; target user may be private")) - (if (y-or-n-p (format "Email @%s at \"%s\"? " .login .email)) - (browse-url (format "mailto:%s" .email)) - (user-error "Aborted")))) + (magithub-confirm 'user-email .login .email) + (browse-url (format "mailto:%s" .email)))) (provide 'magithub-user) ;;; magithub-user.el ends here diff -Nru magithub-0.1.5/Makefile magithub-0.1.7/Makefile --- magithub-0.1.5/Makefile 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/Makefile 2018-06-03 00:42:26.000000000 +0000 @@ -1,34 +1,112 @@ +-include config.mk + +PKG = magithub + +EMACS ?= emacs +EMACS_ARGS ?= + +ifndef ORG_LOAD_PATH +ORG_LOAD_PATH = -L ../dash +ORG_LOAD_PATH += -L ../org/lisp +ORG_LOAD_PATH += -L ../org/contrib/lisp +ORG_LOAD_PATH += -L ../ox-texinfo+ +endif + +INSTALL_INFO ?= $(shell command -v ginstall-info || printf install-info) +MAKEINFO ?= makeinfo +MANUAL_HTML_ARGS ?= --css-ref /assets/the.css + +EENVS = PACKAGE_FILE="magithub.el" +EENVS += PACKAGE_TESTS="test/magithub-test.el" +EENVS += PACKAGE_LISP="$(wildcard magithub*.el)" + +ifeq ($(MELPA_STABLE),true) +EENVS += PACKAGE_ARCHIVES="gnu melpa-stable" +else +EENVS += PACKAGE_ARCHIVES="gnu melpa" +endif + +EMAKE := $(EENVS) emacs -batch -l emake.el --eval "(emake (pop argv))" + +doc: info html html-dir pdf + +help: + $(info make doc - generate most manual formats) + $(info make texi - generate texi manual (see comments)) + $(info make info - generate info manual) + $(info make html - generate html manual file) + $(info make html-dir - generate html manual directory) + $(info make pdf - generate pdf manual) + @printf "\n" + .PHONY: clean install build test clean: rm -f *.elc - rm -rf .cask/ + rm -rf .elpa/ -install: .cask/ -.cask/: - cask --verbose install +emake.el: + wget 'https://raw.githubusercontent.com/vermiculus/emake.el/master/emake.el' -build: .cask/ - cask build 2>&1 | tee build.log +.elpa/: emake.el + $(EMAKE) install -test: test-build test-tag test-ert +install: .elpa/ -# make sure the build errors if the package-version isn't up-to-date -test-tag: - grep "Package-Version: $(shell git describe --tags | rev | cut -d- -f3- | rev)" magithub.el +build: install + $(EMAKE) compile ~error-on-warn -# make sure there were no compile errors/warnings -test-build: build - ! grep -oe '.*:\(Error\|Warning\):.*' build.log +test: build test-ert # run ERT tests -test-ert: - cask exec ert-runner +test-ert: emake.el + $(EMAKE) test ert setup-CI: export PATH="$(HOME)/bin:$(PATH)" wget 'https://raw.githubusercontent.com/flycheck/emacs-travis/master/emacs-travis.mk' make -f emacs-travis.mk install_emacs - make -f emacs-travis.mk install_cask emacs --version - cask exec emacs --version + +info: $(PKG).info dir +html: $(PKG).html +pdf: $(PKG).pdf + +ORG_ARGS = --batch -Q $(ORG_LOAD_PATH) -l ox-extra -l ox-texinfo+.el +ORG_EVAL = --eval "(ox-extras-activate '(ignore-headlines))" +ORG_EVAL += --eval "(setq indent-tabs-mode nil)" +ORG_EVAL += --eval "(setq org-src-preserve-indentation nil)" +ORG_EVAL += --funcall org-texinfo-export-to-texinfo + +# This target first bumps version strings in the Org source. The +# necessary tools might be missing so other targets do not depend +# on this target and it has to be run explicitly when appropriate. +# +# AMEND=t make texi Update manual to be amended to HEAD. +# VERSION=N make texi Update manual for release. +# +.PHONY: texi +texi: + @$(EMACS) $(ORG_ARGS) $(PKG).org $(ORG_EVAL) + @printf "\n" >> $(PKG).texi + @rm -f $(PKG).texi~ + +%.info: %.texi + @printf "Generating $@\n" + @$(MAKEINFO) --no-split $< -o $@ + +dir: $(PKG).info + @printf "Generating $@\n" + @printf "%s" $^ | xargs -n 1 $(INSTALL_INFO) --dir=$@ + +%.html: %.texi + @printf "Generating $@\n" + @$(MAKEINFO) --html --no-split $(MANUAL_HTML_ARGS) $< + +html-dir: $(PKG).texi + @printf "Generating $(PKG)/*.html\n" + @$(MAKEINFO) --html $(MANUAL_HTML_ARGS) $< + +%.pdf: %.texi + @printf "Generating $@\n" + @texi2pdf --clean $< > /dev/null diff -Nru magithub-0.1.5/README.md magithub-0.1.7/README.md --- magithub-0.1.5/README.md 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/README.md 2018-06-03 00:42:26.000000000 +0000 @@ -6,18 +6,18 @@ [![Build Status](https://travis-ci.org/vermiculus/magithub.svg?branch=master)](https://travis-ci.org/vermiculus/magithub) [![Gitter](https://badges.gitter.im/vermiculus/magithub.svg)](https://gitter.im/vermiculus/magithub) [![MELPA Stable Status](http://melpa-stable.milkbox.net/packages/magithub-badge.svg)](http://melpa-stable.milkbox.net/#/magithub) -[![GitHub Commits](https://img.shields.io/github/commits-since/vermiculus/magithub/0.1.3.svg)](//github.com/vermiculus/magithub/releases) +[![GitHub Commits](https://img.shields.io/github/commits-since/vermiculus/magithub/0.1.6.svg)](//github.com/vermiculus/magithub/releases) Magithub is a collection of interfaces to GitHub integrated into [Magit][magit] workflows: -- Repository creation -- Forking -- Pull request listing and creation -- Issue listing and creation -- Labeling -- Status checks -- Notifications +- Push new repositories +- Fork existing ones +- List and create issues and pull requests +- Keep offline notes for your eyes only +- Write comments +- Manage labels and assignees +- Stay up-to-date with status checks (e.g., CI) and notifications - ... as well as support for working offline. @@ -34,7 +34,9 @@ ```elisp (use-package magithub :after magit - :config (magithub-feature-autoinject t)) + :config + (magithub-feature-autoinject t) + (setq magithub-clone-default-directory "~/github")) ``` See [the full documentation][magithub-org] for more details. diff -Nru magithub-0.1.5/RelNotes/0.1.6.org magithub-0.1.7/RelNotes/0.1.6.org --- magithub-0.1.5/RelNotes/0.1.6.org 1970-01-01 00:00:00.000000000 +0000 +++ magithub-0.1.7/RelNotes/0.1.6.org 2018-06-03 00:42:26.000000000 +0000 @@ -0,0 +1,144 @@ +#+Title: Magithub Release 0.1.6 +#+Date: [2018-06-02 Sat] + +#+LINK: PR https://www.github.com/vermiculus/magithub/pull/%s +#+LINK: BUG https://www.github.com/vermiculus/magithub/issues/%s + +* Breaking Changes +- If you were using ~magit-header-line~ to customize the appearance of + the =Issues= and =Pull Requests= section headers, those now use the + ~magit-section-heading~ face. [[PR:196]] +- Many functions related to issue/post creation have been reworked. + Instead of the widget framework, we now use =magithub-edit-mode=. See + more details in 'New Features'. [[PR:204]] +- =magithub-dashboard-show-unread-notifications= is now called + =magithub-dashboard-show-read-notifications= and all functionality + pertaining to that variable has been updated. [[PR:251]] +- Most settings, like the inclusion of sections in ~magit-status~, are + now controlled by various =git config= properties. These settings are + reachable under =H C=. The following functions/variables no longer + exist: + - ~magithub-ci-enabled-p~ (now ~magithub-settings-include-status-p~) + - ~magithub-ci--set-enabled~ + - ~magithub-ci-disable~ + - ~magithub-ci-enable~ + - ~magithub-toggle-ci-status-header~ + - =magithub-cache= (now =magithub-settings-cache-behavior-override=; + ~magithub-settings-cache-behavior~) + - ~magithub-toggle-online~ + - ~magithub-go-online~ + - ~magithub-go-offline~ + - ~magithub-source--remote~ + - ~magithub--deftoggle~ + - ~magithub-toggle-pull-requests~ + - ~magithub-toggle-issues~ + - ~magithub-proxy-set~ + - ~magithub-proxy-set-default~ + - ~magithub-enable~ + - ~magithub-disable~ + - ~magithub-enabled-toggle~ + - =magithub-enabled-by-default= + + The various integration sections are now added to the appropriate + hooks by ~magithub-feature-autoinject~ via =magithub-feature-list=. + + For more details on how to set configure Magithub now, consult the + documentation inside ~magithub-settings-popup~ (=? KEY=) or read + =magithub-settings.el=. [[PR:265]] +- =hub.host= is no longer respected and has been replaced by user option + ~magithub-github-hosts~. This most directly impacts GitHub Enterprise + support. +** Caching [[PR:328]] +Caching has been reworked mostly from the ground-up. 'Offline mode' +is now manifest in a single, Boolean-valued git variable +"magithub.online", which see ~magithub-settings--set-magithub.online~ +for that behavior. + +- ~magithub-cache-invalidate~ was not used, so it is no longer + available. +- ~magithub-issue-refresh~ no longer takes parameters. + +* New Features +- Browse commits by using =w= on a commit section. If the current + section's value cannot be understood as a valid commit, use the + =git-revision= at point. +- ~magithub-feature-autoinject~ can now take a list of features to load. +- Many symbols are now supported by ~thing-at-point~: + - =github-user= + - =github-issue= + - =github-label= + - =github-comment= + - =github-repository= + - =github-pull-request= + - =github-notification= + These symbols should allow other GitHub-sensitive packages to use + the work Magithub has already done without depending on Magithub + directly. [[PR:201]] +- The widget interface for writing issues and pull requests is gone! + Now, everything uses the framework debuted for writing comments. + For issues and pull requests, the first line (i.e., everything up to + the first newline character) is parsed as the title; everything else + as the body. Now issues, pull requests, and comments use a common + interface that supports submitting, canceling, and saving drafts to + finish later. [[PR:204]] +- You can now edit comments using =e= on a comment section. [[PR:206]] +- When submitting pull requests of a single commit, the commit message + is defaulted into the pull request body. Multiple commits? + ~magit-log~ shows you the changes you want to merge. [[PR:239]] +- Headers in issue-view mode are now easier to navigate. [[PR:250]] +- Notifications are marked as read when visited in Emacs. [[PR:252]] +- ~magithub-repo~ can now take a string of the form =user/repo=. This is + helpful when writing other code that uses Magithub functionality. [[PR:253]] +- New command ~magithub-pull-request-new-from-issue~ can create pull + requests from issues. This creates a new pull request by copying + the title/body from the source issue. (To be honest, this API + endpoint is not what I thought it would be.) [[PR:256]] +- Confirmation messages can now be skipped (or the default question + behavior otherwise altered) using =git config= properties. See + ~magithub-confirm-set-default-behavior~ or configure your settings + locally (or globally) interactively when they're asked. [[PR:268]] + [[PR:270]] +- Use default branch of the repository as =BASE= if there's no upstream + for the current branch. [[PR:269]] +- Completion of issue numbers ("#123"), and user names ("@purcell") is + supported in edit and commit message buffers via the standard + ~completion-at-point~ mechanism, and therefore also via ~company~'s ~capf~ + backend. This is enabled by default in certain buffers via the + ~magithub-features~ mechanism. [[PR:263]], [[PR:278]] + +* Bug Fixes +- In ~magithub-repo~, an API request is no longer made when the + repository context cannot be determined. +- The list of labels is now correctly cached per-repository. [[PR:203]] +- The full list of labels is now available for use when modifying + issues and pull requests. [[PR:203]] +- The cache (and other files in =magithub-dir=) are no longer added to + the =recentf= list. [[PR:210]] +- Consistently use ~magithub-request~. [[PR:229]] +- ~magit-magithub-pull-request-section-map~ is now defined in terms of + ~magit-magithub-issue-section-map~. [[PR:238]] +- Fix autoloads to load and install the dispatch with Magit. [[PR:238]] +- Remove awkward blank lines from the end of the dashboard. [[PR:238]] +- Issue/PR drafts are deleted appropriately after successful + submission. [[PR:247]] +- Various performance improvements. [[PR:255]] +- Ghub+ is now required in core. This should help users who utilize + deferred loading. [[PR:260]] +- Submitting pull requests to other repositories in some scenarios + should now be fixed. [[PR:272]] +- ~magithub-clone~ now correctly provides a default destination. [[PR:273]] +- ~magithub-pull-request-new~ now uses a better check to test for pull + requests of a single commit: [[PR:274]] + #+BEGIN_SRC sh + git rev-list --count BASE.. + #+END_SRC +- Authenticate correctly when marking notifications as read. [[PR:277]] +- Don't call ~magit-get~ in a non-existent directory in ~magithub-clone~. + [[PR:282]] +- Pull requests now work in repositories with remotes that point to + non-GitHub locations. [[PR:285]] +- We now only prompt to refresh GitHub data when the command being run + by the user is solely intended to refresh the buffer. [[PR:318]] +- We no longer ever call =/rate_limit= directly, instead relying on an + improved version of ~ghubp-ratelimit~ that handles GitHub Enterprise + sanely. [[BUG:327]] diff -Nru magithub-0.1.5/RelNotes/0.2.org magithub-0.1.7/RelNotes/0.2.org --- magithub-0.1.5/RelNotes/0.2.org 1970-01-01 00:00:00.000000000 +0000 +++ magithub-0.1.7/RelNotes/0.2.org 2018-06-03 00:42:26.000000000 +0000 @@ -0,0 +1,5 @@ +#+Title: Magithub Release 0.2 +#+Date: + +#+LINK: PR https://www.github.com/vermiculus/magithub/pull/%s +#+LINK: BUG https://www.github.com/vermiculus/magithub/issues/%s diff -Nru magithub-0.1.5/test/magithub-test.el magithub-0.1.7/test/magithub-test.el --- magithub-0.1.5/test/magithub-test.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/test/magithub-test.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ ;;; magithub-tests.el --- tests for Magithub -;; Copyright (C) 2016 Sean Allred +;; Copyright (C) 2016-2018 Sean Allred ;; ;; License: GPLv3 @@ -10,6 +10,8 @@ (require 'magithub-core) (require 'ghub+) +(load "test-helper.el") + (setq ghubp-request-override-function #'magithub-mock-ghub-request) @@ -40,9 +42,8 @@ (let ((magithub--api-last-checked (current-time))) (should (magithub-source--sparse-repo)) (should (magithub-repo)) - (should (let ((magithub-cache t)) ; force API call + (should (let ((magithub-cache--refresh t)) ; force API call (magithub-repo))) - (should (let ((magithub-cache nil)) ; force cache read - (magithub-repo))))) + (should (magithub-repo)))) ; force cache read ;;; magithub-test.el ends here diff -Nru magithub-0.1.5/test/test-helper.el magithub-0.1.7/test/test-helper.el --- magithub-0.1.5/test/test-helper.el 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/test/test-helper.el 2018-06-03 00:42:26.000000000 +0000 @@ -1,12 +1,6 @@ ;;; Allow loading package files -(require 'cask) (require 'cl-lib) -(let ((source-directory (locate-dominating-file load-file-name "Cask"))) - (cask-initialize source-directory) - (add-to-list 'load-path source-directory) - (setq magithub-dir (expand-file-name ".cask" source-directory))) - (defun magithub-in-test-dir (file) "Expand FILE in the test directory." (let ((dir default-directory)) @@ -32,8 +26,8 @@ response." (message "(mock-ghub-request %S %S %S :query %S :payload %S :headers %S :unpaginate %S :noerror %S :reader %S :username %S :auth %S :host %S)" method resource params query payload headers unpaginate noerror reader username auth host) - (when (eq t magithub-cache) - (error "Did not respect cache")) + (when (not (magithub-online-p)) + (error "Did not respect online/offline")) (let* ((parts (cdr (s-split "/" resource))) (directory (mapconcat (lambda (s) (concat s ".d")) (butlast parts) "/")) diff -Nru magithub-0.1.5/.travis.yml magithub-0.1.7/.travis.yml --- magithub-0.1.5/.travis.yml 2017-12-24 16:45:27.000000000 +0000 +++ magithub-0.1.7/.travis.yml 2018-06-03 00:42:26.000000000 +0000 @@ -1,6 +1,6 @@ -#language: emacs-lisp -language: python -sudo: false +language: emacs-lisp +sudo: required +dist: trusty cache: - directories: - "$HOME/emacs" @@ -8,11 +8,14 @@ fast_finish: true allow_failures: - env: EMACS_VERSION=snapshot + - env: EMACS_VERSION=26.1 MELPA_STABLE=true env: matrix: - EMACS_VERSION=25.1 - EMACS_VERSION=25.2 - EMACS_VERSION=25.3 + - EMACS_VERSION=26.1 + - EMACS_VERSION=26.1 MELPA_STABLE=true - EMACS_VERSION=snapshot before_install: - make setup-CI