diff --git a/elpa/magithub-0.2/magithub-autoloads.el b/elpa/magithub-0.2/magithub-autoloads.el deleted file mode 100644 index 8e6b079..0000000 --- a/elpa/magithub-0.2/magithub-autoloads.el +++ /dev/null @@ -1,36 +0,0 @@ -;;; magithub-autoloads.el --- automatically extracted autoloads -;; -;;; Code: - - -;;;### (autoloads (magithub-clone) "magithub" "magithub.el" (21539 -;;;;;; 11653 143280 560000)) -;;; Generated autoloads from magithub.el - -(autoload 'magithub-clone "magithub" "\ -Clone GitHub repo USERNAME/REPO into directory DIR. -If SSHP is non-nil, clone it using the SSH URL. Once the repo is -cloned, switch to a `magit-status' buffer for it. - -Interactively, prompts for the repo name and directory. With a -prefix arg, clone using SSH. - -\(fn USERNAME REPO DIR &optional SSHP)" t nil) - -(eval-after-load 'magit '(unless (featurep 'magithub) (require 'magithub))) - -;;;*** - -;;;### (autoloads nil nil ("magithub-pkg.el") (21539 11653 223834 -;;;;;; 758000)) - -;;;*** - -(provide 'magithub-autoloads) -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; coding: utf-8 -;; End: -;;; magithub-autoloads.el ends here diff --git a/elpa/magithub-0.2/magithub-pkg.el b/elpa/magithub-0.2/magithub-pkg.el deleted file mode 100644 index 75c1377..0000000 --- a/elpa/magithub-0.2/magithub-pkg.el +++ /dev/null @@ -1 +0,0 @@ -(define-package "magithub" "0.2" "Magit extensions for using GitHub" (quote ((magit "0.8") (json "1.2")))) diff --git a/elpa/magithub-0.2/magithub.el b/elpa/magithub-0.2/magithub.el deleted file mode 100644 index da6b63c..0000000 --- a/elpa/magithub-0.2/magithub.el +++ /dev/null @@ -1,1165 +0,0 @@ -;;; magithub.el --- Magit extensions for using GitHub - -;; Copyright (c) 2010 Nathan Weizenbaum -;; Licensed under the same terms as Emacs. - -;; Author: Nathan Weizenbaum -;; URL: http://github.com/nex3/magithub -;; Version: 0.2 -;; Created: 2010-06-06 -;; By: Nathan Weizenbaum -;; Keywords: git, github, magit -;; Package-Requires: ((magit "0.8") (json "1.2")) - -;;; Commentary: - -;; This package does two things. First, it extends Magit's UI with -;; assorted GitHub-related functionality, similar to the github-gem -;; tool (http://github.com/defunkt/github-gem). Second, it uses -;; Magit's excellent Git library to build an Elisp library for -;; interfacing with GitHub's API. - -(require 'magit) -(require 'url) -(require 'json) -(require 'crm) -(eval-when-compile (require 'cl)) - - -;;; Variables - -(defvar magithub-api-base "https://github.com/api/v2/json/" - "The base URL for accessing the GitHub API.") - -(defvar magithub-github-url "https://github.com/" - "The URL for the main GitHub site. - -This is used for some calls that aren't supported by the official API.") - -(defvar magithub-use-ssl nil - "If non-nil, access GitHub via HTTPS. -This is more secure, but slower.") - -(defvar magithub-gist-url "http://gist.github.com/" - "The URL for the Gist site.") - -(defvar magithub-view-gist t - "Whether or not to open new Gists in the browser.") - -(defvar magithub-request-data nil - "An assoc list of parameter names to values. - -This is meant to be dynamically bound around `magithub-retrieve' -and `magithub-retrieve-synchronously'.") - -(defvar magithub-parse-response t - "Whether to parse responses from GitHub as JSON. -Used by `magithub-retrieve' and `magithub-retrieve-synchronously'. -This should only ever be `let'-bound, not set outright.") - -(defvar magithub-users-history nil - "A list of users selected via `magithub-read-user'.") - -(defvar magithub-repos-history nil - "A list of repos selected via `magithub-read-repo'.") - -(defvar magithub--repo-obj-cache (make-hash-table :test 'equal) - "A hash from (USERNAME . REPONAME) to decoded JSON repo objects (plists). -This caches the result of `magithub-repo-obj' and -`magithub-cached-repo-obj'.") - - -;;; Utilities - -(defun magithub--remove-if (predicate seq) - "Remove all items satisfying PREDICATE from SEQ. -Like `remove-if', but without the cl runtime dependency." - (loop for el being the elements of seq - if (not (funcall predicate el)) collect el into els - finally return els)) - -(defun magithub--position (item seq) - "Return the index of ITEM in SEQ. -Like `position', but without the cl runtime dependency. - -Comparison is done with `eq'." - (loop for el in seq until (eq el item) count t)) - -(defun magithub--cache-function (fn) - "Return a lambda that will run FN but cache its return values. -The cache is a very naive assoc from arguments to returns. -The cache will only last as long as the lambda does. - -FN may call magithub--use-cache, which will use a pre-cached -value if available or recursively call FN if not." - (lexical-let ((fn fn) cache cache-fn) - (setq cache-fn - (lambda (&rest args) - (let ((cached (assoc args cache))) - (if cached (cdr cached) - (flet ((magithub--use-cache (&rest args) (apply cache-fn args))) - (let ((val (apply fn args))) - (push (cons args val) cache) - val)))))))) - -(defun magithub-make-query-string (params) - "Return a query string constructed from PARAMS. -PARAMS is an assoc list of parameter names to values. - -Any parameters with a nil values are ignored." - (replace-regexp-in-string - "&+" "&" - (mapconcat - (lambda (param) - (when (cdr param) - (concat (url-hexify-string (car param)) "=" - (url-hexify-string (cdr param))))) - params "&"))) - -(defun magithub-parse-repo (repo) - "Parse a REPO string of the form \"username/repo\". -Return (USERNAME . REPO), or raise an error if the format is -incorrect." - (condition-case err - (destructuring-bind (username repo) (split-string repo "/") - (cons username repo)) - (wrong-number-of-arguments (error "Invalid GitHub repository %s" repo)))) - -(defun magithub-repo-url (username repo &optional sshp) - "Return the repository URL for USERNAME/REPO. -If SSHP is non-nil, return the SSH URL instead. Otherwise, -return the HTTP URL." - (format (if sshp "git@github.com:%s/%s.git" "http://github.com/%s/%s.git") - username repo)) - -(defun magithub-remote-info (remote) - "Return (USERNAME REPONAME SSHP) for the given REMOTE. -Return nil if REMOTE isn't a GitHub remote. - -USERNAME is the owner of the repo, REPONAME is the name of the -repo, and SSH is non-nil if it's checked out via SSH." - (block nil - (let ((url (magit-get "remote" remote "url"))) - (unless url (return)) - (when (string-match "\\(?:git\\|https?\\)://github\\.com/\\(.*?\\)/\\(.*\\)\.git" url) - (return (list (match-string 1 url) (match-string 2 url) nil))) - (when (string-match "git@github\\.com:\\(.*?\\)/\\(.*\\)\\.git" url) - (return (list (match-string 1 url) (match-string 2 url) t))) - (return)))) - -(defun magithub-remote-for-commit (commit) - "Return the name of the remote that contains COMMIT. -If no remote does, return nil. COMMIT should be the full SHA1 -commit hash. - -If origin contains the commit, it takes precedence. Otherwise -the priority is nondeterministic." - (flet ((name-rev (remote commit) - (magit-git-string "name-rev" "--name-only" "--no-undefined" "--refs" - ;; I'm not sure why the initial * is required, - ;; but if it's not there this always returns nil - (format "*remotes/%s/*" remote) commit))) - (let ((remote (or (name-rev "origin" commit) (name-rev "*" commit)))) - (when (and remote (string-match "^remotes/\\(.*?\\)/" remote)) - (match-string 1 remote))))) - -(defun magithub-remote-info-for-commit (commit) - "Return information about the GitHub repo for the remote that contains COMMIT. -If no remote does, return nil. COMMIT should be the full SHA1 -commit hash. - -The information is of the form returned by `magithub-remote-info'. - -If origin contains the commit, it takes precedence. Otherwise -the priority is nondeterministic." - (let ((remote (magithub-remote-for-commit commit))) - (when remote (magithub-remote-info remote)))) - -(defun magithub-branches-for-remote (remote) - "Return a list of branches in REMOTE, as of the last fetch." - (let ((lines (magit-git-lines "remote" "show" "-n" remote)) branches) - (while (not (string-match-p "^ Remote branches:" (pop lines))) - (unless lines (error "Unknown output from `git remote show'"))) - (while (string-match "^ \\(.*\\)" (car lines)) - (push (match-string 1 (pop lines)) branches)) - branches)) - -(defun magithub-repo-relative-path () - "Return the path to the current file relative to the repository root. -Only works within `magithub-minor-mode'." - (let ((filename buffer-file-name)) - (with-current-buffer magithub-status-buffer - (file-relative-name filename default-directory)))) - -(defun magithub-name-rev-for-remote (rev remote) - "Return a human-readable name for REV that's valid in REMOTE. -Like `magit-name-rev', but sanitizes things referring to remotes -and errors out on local-only revs." - (setq rev (magit-name-rev rev)) - (if (and (string-match "^\\(remotes/\\)?\\(.*?\\)/\\(.*\\)" rev) - (equal (match-string 2 rev) remote)) - (match-string 3 rev) - (unless (magithub-remote-contains-p remote rev) - (error "Commit %s hasn't been pushed" - (substring (magit-git-string "rev-parse" rev) 0 8))) - (cond - ;; Assume the GitHub repo will have all the same tags as we do, - ;; since we can't actually check without performing an HTTP request. - ((string-match "^tags/\\(.*\\)" rev) (match-string 1 rev)) - ((and (not (string-match-p "^remotes/" rev)) - (member rev (magithub-branches-for-remote remote)) - (magithub-ref= rev (concat remote "/" rev))) - rev) - (t (magit-git-string "rev-parse" rev))))) - -(defun magithub-remotes-containing-ref (ref) - "Return a list of remotes containing REF." - (loop with remotes - for line in (magit-git-lines "branch" "-r" "--contains" ref) - if (and (string-match "^ *\\(.+?\\)/" line) - (not (string= (match-string 1 line) (car remotes)))) - do (push (match-string 1 line) remotes) - finally return remotes)) - -(defun magithub-remote-contains-p (remote ref) - "Return whether REF exists in REMOTE, in any branch. -This does not fetch origin before determining existence, so it's -possible that its result is based on stale data." - (member remote (magithub-remotes-containing-ref ref))) - -(defun magithub-ref= (ref1 ref2) - "Return whether REF1 refers to the same commit as REF2." - (string= (magit-rev-parse ref1) (magit-rev-parse ref2))) - - -;;; Reading Input - -(defun magithub--lazy-completion-callback (fn &optional noarg) - "Converts a simple string-listing FN into a lazy-loading completion callback. -FN should take a string (the contents of the minibuffer) and -return a list of strings (the candidates for completion). This -method takes care of any caching and makes sure FN isn't called -until completion needs to happen. - -If NOARG is non-nil, don't pass a string to FN." - (lexical-let ((fn (magithub--cache-function fn)) (noarg noarg)) - (lambda (string predicate allp) - (let ((strs (if noarg (funcall fn) (funcall fn string)))) - (if allp (all-completions string strs predicate) - (try-completion string strs predicate)))))) - -(defun magithub-read-user (&optional prompt predicate require-match initial-input - hist def inherit-input-method) - "Read a GitHub username from the minibuffer with completion. - -PROMPT, PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF, and -INHERIT-INPUT-METHOD work as in `completing-read'. PROMPT -defaults to \"GitHub user: \". HIST defaults to -'magithub-users-history. - -WARNING: This function currently doesn't work fully, since -GitHub's user search API only returns an apparently random subset -of users." - (setq hist (or hist 'magithub-users-history)) - (completing-read (or prompt "GitHub user: ") - (magithub--lazy-completion-callback - (lambda (s) - (mapcar (lambda (user) (plist-get user :name)) - (magithub-user-search s)))) - predicate require-match initial-input hist def inherit-input-method)) - -(defun magithub-read-repo-for-user (user &optional prompt predicate require-match - initial-input hist def inherit-input-method) - "Read a GitHub repository from the minibuffer with completion. -USER is the owner of the repository. - -PROMPT, PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF, and -INHERIT-INPUT-METHOD work as in `completing-read'. PROMPT -defaults to \"GitHub repo: /\"." - (lexical-let ((user user)) - (completing-read (or prompt (concat "GitHub repo: " user "/")) - (magithub--lazy-completion-callback - (lambda () - (mapcar (lambda (repo) (plist-get repo :name)) - (magithub-repos-for-user user))) - 'noarg) - predicate require-match initial-input hist def - inherit-input-method))) - -(defun magithub-read-repo (&optional prompt predicate require-match initial-input - hist def inherit-input-method) - "Read a GitHub user-repository pair with completion. -Return (USERNAME . REPO), or nil if the user enters no input. - -PROMPT, PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF, and -INHERIT-INPUT-METHOD work as in `completing-read'. PROMPT -defaults to \"GitHub repo (user/repo): \". HIST defaults to -'magithub-repos-history. If REQUIRE-MATCH is non-nil and the -user enters no input, raises an error. - -WARNING: This function currently doesn't work fully, since -GitHub's user search API only returns an apparently random subset -of users, and also has no way to search for users whose names -begin with certain characters." - (setq hist (or hist 'magithub-repos-history)) - (let ((result (completing-read - (or prompt "GitHub repo (user/repo): ") - (magithub--lazy-completion-callback 'magithub--repo-completions) - predicate require-match initial-input hist def inherit-input-method))) - (if (string= result "") - (when require-match (error "No repository given")) - (magithub-parse-repo result)))) - -(defun magithub--repo-completions (string) - "Try completing the given GitHub user/repository pair. -STRING is the text already in the minibuffer, PREDICATE is a -predicate that the string must satisfy." - (destructuring-bind (username . rest) (split-string string "/") - (if (not rest) ;; Need to complete username before we start completing repo - (mapcar (lambda (user) (concat (plist-get user :name) "/")) - (magithub-user-search username)) - (if (not (string= (car rest) "")) - (magithub--use-cache (concat username "/")) - (mapcar (lambda (repo) (concat username "/" (plist-get repo :name))) - (magithub-repos-for-user username)))))) - -(defun magithub-read-pull-request-recipients () - "Read a list of recipients for a GitHub pull request." - (let ((collabs (magithub-repo-parent-collaborators)) - (network (magithub-repo-network))) - (magithub--remove-if - (lambda (s) (string= s "")) - (completing-read-multiple - "Send pull request to: " - (mapcar (lambda (repo) (plist-get repo :owner)) (magithub-repo-network)) - nil nil (concat (mapconcat 'identity collabs crm-separator) - (if (= (length collabs) (length network)) "" crm-separator)))))) - -(defun magithub-read-untracked-fork () - "Read the name of a fork of this repo that we aren't yet tracking. -This will accept either a username or a username/repo pair, -and return (USERNAME . REPONAME)." - (let ((fork - (completing-read - "Track fork (user or user/repo): " - (magithub--lazy-completion-callback - (lambda () - (mapcar (lambda (repo) (concat (plist-get repo :owner) "/" - (plist-get repo :name))) - (magithub-untracked-forks))) - 'noarg) - nil nil nil 'magithub-repos-history))) - (cond - ((string= fork "") (error "No fork given")) - ((string-match "/" fork) (magithub-parse-repo fork)) - (t (cons fork (magithub-repo-name)))))) - - -;;; Bindings - -(define-prefix-command 'magithub-prefix 'magithub-map) -(define-key magithub-map (kbd "C") 'magithub-create-from-local) -(define-key magithub-map (kbd "c") 'magithub-clone) -(define-key magithub-map (kbd "f") 'magithub-fork-current) -(define-key magithub-map (kbd "p") 'magithub-pull-request) -(define-key magithub-map (kbd "t") 'magithub-track) -(define-key magithub-map (kbd "g") 'magithub-gist-repo) -(define-key magithub-map (kbd "S") 'magithub-toggle-ssh) -(define-key magithub-map (kbd "b") 'magithub-browse-item) -(define-key magit-mode-map (kbd "'") 'magithub-prefix) - - -;;; Requests - -(defun magit-request-url (path) - "Return the full GitHub URL for the resource PATH. - -PATH can either be a string or a list of strings. -In the latter case, they're URL-escaped and joined with \"/\". - -If `url-request-method' is GET, the returned URL will include -`url-request-data' as the query string." - (let ((url - (concat magithub-api-base - (if (stringp path) path (mapconcat 'url-hexify-string path "/")) - (if (string= url-request-method "GET") - (concat "?" url-request-data) - "")))) - (if magithub-use-ssl url - (replace-regexp-in-string "^https" "http" url)))) - -(defmacro magithub-with-auth (&rest body) - "Runs BODY with GitHub authorization info in `magithub-request-data'." - (declare (indent 0)) - (let ((auth (gensym))) - `(let* ((,auth (magithub-auth-info)) - (magithub-request-data (append (list - (cons "login" (car ,auth)) - (cons "token" (cdr ,auth))) - magithub-request-data))) - ,@body))) - -(defun magithub-handle-errors (status) - "Handle any errors reported in a `url-retrieve' callback. -STATUS is the first argument passed to the callback. - -If there is an error and GitHub returns an error message, that -message is printed with `error'. Otherwise, the HTTP error is -signaled." - (loop for (name val) on status by 'cddr - do (when (eq name :error) - (if (not magithub-handle-errors) - (signal (car val) (cdr val)) - (condition-case err - (let* ((json-object-type 'plist) - (data (json-read)) - (err (plist-get data :error))) - (unless err (signal 'json-readtable-error nil)) - (error "GitHub error: %s" err)) - (json-readtable-error (signal (car val) (cdr val)))))))) - -(defun magithub-retrieve (path callback &optional cbargs) - "Retrieve GitHub API PATH asynchronously. -Call CALLBACK with CBARGS when finished. - -PATH can either be a string or a list of strings. -In the latter case, they're URL-escaped and joined with \"/\". - -Like `url-retrieve', except for the following: -* PATH is an API resource path, not a full URL. -* GitHub authorization is automatically enabled. -* `magithub-request-data' is used instead of `url-request-data'. -* CALLBACK is passed a decoded JSON object (as a plist) rather - than a list of statuses. Basic error handling is done by `magithub-retrieve'. - -If `magithub-parse-response' is nil, CALLBACK is just passed nil -rather than the JSON response object." - (magithub-with-auth - (let ((url-request-data (magithub-make-query-string magithub-request-data))) - (lexical-let ((callback callback) (magithub-parse-response magithub-parse-response)) - (url-retrieve (magit-request-url path) - (lambda (status &rest cbargs) - (when magithub-parse-response - (search-forward "\n\n" nil t)) ; Move past headers - (magithub-handle-errors status) - (apply callback - (if (not magithub-parse-response) - (current-buffer) - (let* ((json-object-type 'plist) - (obj (json-read))) - (kill-buffer) - obj)) - cbargs)) - cbargs))))) - -(defun magithub-retrieve-synchronously (path) - "Retrieve GitHub API PATH synchronously. - -PATH can either be a string or a list of strings. -In the latter case, they're URL-escaped and joined with \"/\". - -Like `url-retrieve-synchronously', except for the following: -* PATH is an API resource path, not a full URL. -* GitHub authorization is automatically enabled. -* `magithub-request-data' is used instead of `url-request-data'. -* Return a decoded JSON object (as a plist) rather than a buffer - containing the response unless `magithub-parse-response' is nil." - (magithub-with-auth - (let ((url-request-data (magithub-make-query-string magithub-request-data))) - (with-current-buffer (url-retrieve-synchronously (magit-request-url path)) - (goto-char (point-min)) - (if (not magithub-parse-response) (current-buffer) - (search-forward "\n\n" nil t) ; Move past headers - (let* ((data (let ((json-object-type 'plist)) (json-read))) - (err (plist-get data :error))) - (when err (error "GitHub error: %s" err)) - (kill-buffer) - data)))))) - - -;;; Configuration -;; This API was taken from gist.el (http://github.com/defunkt/gist.el), -;; and renamed to avoid conflict. The code also uses Magit rather -;; than relying on the Git executable directly. - -(defun magithub-config (key) - "Returns a GitHub specific value from the global Git config." - (magit-git-string "config" "--global" (concat "github." key))) - -(defun magithub-set-config (key value) - "Sets a GitHub specific value to the global Git config." - (magit-git-string "config" "--global" (concat "github." key) value)) - -(defun magithub-auth-info () - "Returns the user's GitHub authorization information. -Searches for a GitHub username and token in the global git config, -and returns (USERNAME . TOKEN). If nothing is found, prompts -for the info then sets it to the git config." - (interactive) - - (let* ((user (magithub-config "user")) - (token (magithub-config "token"))) - - (when (not user) - (setq user (read-string "GitHub username: ")) - (magithub-set-config "user" user)) - - (when (not token) - (setq token (read-string "GitHub API token: ")) - (magithub-set-config "token" token)) - - (cons user token))) - - -;;; GitHub Information - -(defun magithub-repos-for-user (user) - "Return an array of all repos owned by USER. -The repos are decoded JSON objects (plists)." - (let ((url-request-method "GET")) - (plist-get - (magithub-retrieve-synchronously - (list "repos" "show" user)) - :repositories))) - -(defun magithub-user-search (user) - "Run a GitHub user search for USER. -Return an array of all matching users. - -WARNING: WARNING: This function currently doesn't work fully, -since GitHub's user search API only returns an apparently random -subset of users." - (if (string= user "") [] - (let ((url-request-method "GET")) - (plist-get - (magithub-retrieve-synchronously - (list "user" "search" string)) - :users)))) - -(defun magithub-repo-obj (&optional username repo) - "Return an object representing the repo USERNAME/REPO. -Defaults to the current repo. - -The returned object is a decoded JSON object (plist)." - (setq username (or username (magithub-repo-owner))) - (setq repo (or repo (magithub-repo-name))) - (remhash (cons username repo) magithub--repo-obj-cache) - (magithub-cached-repo-obj username repo)) - -(defun magithub-cached-repo-obj (&optional username repo) - "Return a (possibly cached) object representing the repo USERNAME/REPO. -Defaults to the current repo. - -The returned object is a decoded JSON object (plist). - -This differs from `magithub-repo-obj' in that it returns a cached -copy of the repo object if one exists. This is useful for -properties such as :parent and :fork that are highly unlikely to -change." - (setq username (or username (magithub-repo-owner))) - (setq repo (or repo (magithub-repo-name))) - (let ((cached (gethash (cons username repo) magithub--repo-obj-cache))) - (or cached - (let* ((url-request-method "GET") - (obj (plist-get - (magithub-retrieve-synchronously - (list "repos" "show" username repo)) - :repository))) - (puthash (cons username repo) obj magithub--repo-obj-cache) - obj)))) - -(defun magithub-repo-collaborators (&optional username repo) - "Return an array of names of collaborators on USERNAME/REPO. -Defaults to the current repo." - (setq username (or username (magithub-repo-owner))) - (setq repo (or repo (magithub-repo-name))) - (let ((url-request-method "GET")) - (plist-get - (magithub-retrieve-synchronously - (list "repos" "show" username repo "collaborators")) - :collaborators))) - -(defun magithub-repo-network (&optional username repo) - "Return an array of forks and/or parents of USERNAME/REPO. -Defaults to the current repo. - -Each fork is a decoded JSON object (plist)." - (setq username (or username (magithub-repo-owner))) - (setq repo (or repo (magithub-repo-name))) - (let ((url-request-method "GET")) - (plist-get - (magithub-retrieve-synchronously - (list "repos" "show" username repo "network")) - :network))) - -(defun magithub-repo-parent-collaborators (&optional username repo) - "Return an array of names of collaborators on the parent of USERNAME/REPO. -These are the default recipients of a pull request for this repo. -Defaults to the current repo. - -If this repo has no parents, return the collaborators for it instead." - (let ((parent (plist-get (magithub-cached-repo-obj username repo) :parent))) - (if (not parent) (magithub-repo-collaborators username repo) - (destructuring-bind (parent-owner . parent-repo) (magithub-parse-repo parent) - (magithub-repo-collaborators parent-owner parent-repo))))) - -(defun magithub-untracked-forks () - "Return a list of forks of this repo that aren't being tracked as remotes. -Returned repos are decoded JSON objects (plists)." - (lexical-let ((remotes (magit-git-lines "remote"))) - (delq "origin" remotes) - (push (magithub-repo-owner) remotes) - (magithub--remove-if - (lambda (repo) (member-ignore-case (plist-get repo :owner) remotes)) - (magithub-repo-network)))) - - -;;; Local Repo Information - -(defun magithub-repo-info () - "Return information about this GitHub repo. -This is of the form given by `magithub-remote-info'. - -Error out if this isn't a GitHub repo." - (or (magithub-remote-info "origin") - (error "Not in a GitHub repo"))) - -(defun magithub-repo-owner () - "Return the name of the owner of this GitHub repo. - -Error out if this isn't a GitHub repo." - (car (magithub-repo-info))) - -(defun magithub-repo-name () - "Return the name of this GitHub repo. - -Error out if this isn't a GitHub repo." - (cadr (magithub-repo-info))) - -(defun magithub-repo-ssh-p () - "Return non-nil if this GitHub repo is checked out via SSH. - -Error out if this isn't a GitHub repo." - (caddr (magithub-repo-info))) - - -;;; Diff Information - -(defun magithub-section-index (section) - "Return the index of SECTION as a child of its parent section." - (magithub--position section (magit-section-children (magit-section-parent section)))) - -(defun magithub-hunk-lines () - "Return the two line numbers for the current line (which should be in a hunk). -The first number is the line number in the original file, the -second is the line number in the new file. They're returned -as (L1 L2). If either doesn't exist, it will be nil. - -If something goes wrong (e.g. we're not in a hunk or it's in an -unknown format), return nil." - (block nil - (let ((point (point))) - (save-excursion - (beginning-of-line) - (when (looking-at "@@") ;; Annotations don't have line numbers, - (forward-line) ;; so we'll approximate with the next line. - (setq point (point))) - (goto-char (magit-section-beginning (magit-current-section))) - (unless (looking-at "@@ -\\([0-9]+\\)\\(?:,[0-9]+\\)? \\+\\([0-9]+\\)") (return)) - (let ((l (- (string-to-number (match-string 1)) 1)) - (r (- (string-to-number (match-string 2)) 1))) - (forward-line) - (while (<= (point) point) - (unless (looking-at "\\+") (incf l)) - (unless (looking-at "-") (incf r)) - (forward-line)) - (forward-line -1) - (list (unless (looking-at "\\+") l) (unless (looking-at "-") r))))))) - - -;;; Network - -(defun magithub-track (username &optional repo fetch) - "Track USERNAME/REPO as a remote. -If FETCH is non-nil, fetch that remote. - -Interactively, prompts for the username and repo. With a prefix -arg, fetches the remote." - (interactive - (destructuring-bind (username . repo) (magithub-read-untracked-fork) - (list username repo current-prefix-arg))) - (magit-run-git "remote" "add" username (magithub-repo-url username repo)) - (when fetch (magit-run-git-async "remote" "update" username)) - (message "Tracking %s/%s%s" username repo - (if fetch ", fetching..." ""))) - - -;;; Browsing - -(defun magithub-browse (&rest path-and-anchor) - "Load http://github.com/PATH#ANCHOR in a web browser and add it to the kill ring. -Any nil elements of PATH are ignored. - -\n(fn &rest PATH [:anchor ANCHOR])" - (destructuring-bind (path anchor) - (loop for el on path-and-anchor - if (car el) - unless (eq (car el) :anchor) collect (car el) into path - else return (list path (cadr el)) - finally return (list path nil)) - (let ((url (concat "http://github.com/" (mapconcat 'identity path "/")))) - (when anchor (setq url (concat url "#" anchor))) - (kill-new url) - (browse-url url)))) - -(defun magithub-browse-current (&rest path-and-anchor) - "Load http://github.com/USER/REPO/PATH#ANCHOR in a web browser. -With ANCHOR, loads the URL with that anchor. - -USER is `magithub-repo-owner' and REPO is `magithub-repo-name'. - -\n(fn &rest PATH [:anchor ANCHOR])" - (apply 'magithub-browse (magithub-repo-owner) (magithub-repo-name) path-and-anchor)) - -(defun magithub-browse-repo () - "Show the GitHub webpage for the current branch of this repository." - ;; Don't use name-rev-for-remote here because we want it to work - ;; even if the branches are out-of-sync. - (magithub-browse-current "tree" (magit-name-rev "HEAD"))) - -(defun magithub-browse-commit (commit &optional anchor) - "Show the GitHub webpage for COMMIT. -COMMIT should be the SHA of a commit. - -If ANCHOR is given, it's used as the anchor in the URL." - (let ((info (magithub-remote-info-for-commit commit))) - (if info (magithub-browse (car info) (cadr info) "commit" commit :anchor anchor) - (error "Commit %s hasn't been pushed" (substring commit 0 8))))) - -(defun magithub-browse-commit-diff (diff-section) - "Show the GitHub webpage for the diff displayed in DIFF-SECTION. -This must be a diff for `magit-currently-shown-commit'." - (magithub-browse-commit - magit-currently-shown-commit - (format "diff-%d" (magithub-section-index diff-section)))) - -(defun magithub-browse-commit-hunk-at-point () - "Show the GitHub webpage for the hunk at point. -This must be a hunk for `magit-currently-shown-commit'." - (destructuring-bind (l r) (magithub-hunk-lines) - (magithub-browse-commit - magit-currently-shown-commit - (format "L%d%s" (magithub-section-index (magit-section-parent - (magit-current-section))) - (if l (format "L%d" l) (format "R%d" r)))))) - -(defun magithub-name-ref-for-compare (ref remote) - "Return a human-readable name for REF that's valid in the compare view for REMOTE. -This is like `magithub-name-rev-for-remote', but takes into -account comparing across repos. - -To avoid making an HTTP request, this method assumes that if REV -is in a remote, that repo is a GitHub fork." - (let ((remotes (magithub-remotes-containing-ref ref))) - ;; If remotes is empty, we let magithub-name-rev-for-remote's - ;; error-handling deal with it. - (if (or (member remote remotes) (null remotes)) - (magithub-name-rev-for-remote ref remote) - (let ((remote-for-ref (car remotes))) - (concat remote-for-ref ":" - (magithub-name-rev-for-remote ref remote-for-ref)))))) - -(defun magithub-browse-compare (from to &optional anchor) - "Show the GitHub webpage comparing refs FROM and TO. - -If ANCHOR is given, it's used as the anchor in the URL." - (magithub-browse-current - "compare" (format "%s...%s" - (magithub-name-ref-for-compare from "origin") - (magithub-name-ref-for-compare to "origin")) - :anchor anchor)) - -(defun magithub-browse-diffbuff (&optional anchor) - "Show the GitHub webpage comparing refs corresponding to the current diff buffer. - -If ANCHOR is given, it's used as the anchor in the URL." - (when (and (listp magit-current-range) (null (cdr magit-current-range))) - (setq magit-current-range (car magit-current-range))) - (if (stringp magit-current-range) - (progn - (unless (magit-everything-clean-p) - (error "Diff includes dirty working directory")) - (magithub-browse-compare magit-current-range - (magithub-name-rev-for-remote "HEAD" "origin") - anchor)) - (magithub-browse-compare (car magit-current-range) (cdr magit-current-range) anchor))) - -(defun magithub-browse-diff (section) - "Show the GitHub webpage for the diff displayed in DIFF-SECTION. -This must be a diff from a *magit-diff* buffer." - (magithub-browse-diffbuff (format "diff-%d" (magithub-section-index diff-section)))) - -(defun magithub-browse-hunk-at-point () - "Show the GitHub webpage for the hunk at point. -This must be a hunk from a *magit-diff* buffer." - (destructuring-bind (l r) (magithub-hunk-lines) - (magithub-browse-diffbuff - (format "L%d%s" (magithub-section-index (magit-section-parent - (magit-current-section))) - (if l (format "L%d" l) (format "R%d" r)))))) - -(defun magithub-browse-blob (path &optional anchor) - "Show the GitHub webpage for the blob at PATH. - -If ANCHOR is given, it's used as the anchor in the URL." - (magithub-browse-current "blob" (magithub-name-rev-for-remote "HEAD" "origin") - path :anchor anchor)) - -(defun magithub-browse-item () - "Load a GitHub webpage describing the item at point. -The URL of the webpage is added to the kill ring." - (interactive) - (or - (magit-section-action (item info "browse") - ((commit) (magithub-browse-commit info)) - ((diff) - (case magit-submode - (commit (magithub-browse-commit-diff (magit-current-section))) - (diff (magithub-browse-diff (magit-current-section))))) - ((hunk) - (case magit-submode - (commit (magithub-browse-commit-hunk-at-point)) - (diff (magithub-browse-hunk-at-point)))) - (t - (case magit-submode - (commit (magithub-browse-commit magit-currently-shown-commit)) - (diff (magithub-browse-diffbuff))))) - (magithub-browse-repo))) - -(defun magithub-browse-file () - "Show the GitHub webpage for the current file. -The URL for the webpage is added to the kill ring. This only -works within `magithub-minor-mode'. - -In Transient Mark mode, if the mark is active, highlight the -contents of the region." - (interactive) - (let ((path (magithub-repo-relative-path)) - (start (line-number-at-pos (region-beginning))) - (end (line-number-at-pos (region-end)))) - (when (eq (char-before (region-end)) ?\n) (decf end)) - (with-current-buffer magithub-status-buffer - (magithub-browse-blob - path (when (and transient-mark-mode mark-active) - (if (eq start end) (format "L%d" start) - (format "L%d-%d" start end))))))) - - -;;; Creating Repos - -(defun magithub-gist-repo (&optional private) - "Upload the current repo as a Gist. -If PRIVATE is non-nil or with a prefix arg, the Gist is private. - -Copies the URL of the Gist into the kill ring. If -`magithub-view-gist' is non-nil (the default), opens the gist in -the browser with `browse-url'." - (interactive "P") - (let ((url-max-redirections 0) - (url-request-method "POST") - (magithub-api-base magithub-gist-url) - (magithub-request-data - `(,@(if private '(("private" . "1"))) - ("file_ext[gistfile1]" . ".dummy") - ("file_name[gistfile1]" . "dummy") - ("file_contents[gistfile1]" . - "Dummy Gist created by Magithub. To be replaced with a real repo."))) - magithub-parse-response) - (let (url) - (with-current-buffer (magithub-retrieve-synchronously "gists") - (goto-char (point-min)) - (re-search-forward "^Location: \\(.*\\)$") - (setq url (match-string 1)) - (kill-buffer)) - (kill-new url) - (let ((ssh-url (replace-regexp-in-string - "^http://gist\\.github\\.com/" - "git@gist.github.com:" url))) - (magit-run-git "remote" "add" "origin" ssh-url) - (magit-set "origin" "branch" "master" "remote") - (magit-set "refs/heads/master" "branch" "master" "merge") - (magit-run-git-async "push" "-v" "-f" "origin" "master") - (when magithub-view-gist (browse-url url)) - (message "Gist created: %s" url))))) - -(defun magithub-create-from-local (name &optional description homepage private) - "Create a new GitHub repository for the current Git repository. -NAME is the name of the GitHub repository, DESCRIPTION describes -the repository, URL is the location of the homepage. If PRIVATE -is non-nil, a private repo is created. - -When called interactively, prompts for NAME, DESCRIPTION, and -HOMEPAGE. NAME defaults to the name of the current Git -directory. By default, creates a public repo; with a prefix arg, -creates a private repo." - (interactive - (list (read-string "Repository name: " - (file-name-nondirectory - (directory-file-name - (expand-file-name - (magit-get-top-dir default-directory))))) - (read-string "Description: ") - (read-string "Homepage: ") - current-prefix-arg)) - - (let ((url-request-method "POST") - (magithub-request-data `(("name" . ,name) - ("description" . ,description) - ("homepage" . ,homepage) - ("private" . ,(if private "0" "1"))))) - (magithub-retrieve "repos/create" - (lambda (data name) - (magit-git-string - "remote" "add" "origin" - (magithub-repo-url (magithub-config "user") name 'ssh)) - (magit-set "origin" "branch" "master" "remote") - (magit-set "refs/heads/master" "branch" "master" "merge") - (magit-run-git-async "push" "-v" "origin" "master") - (message "GitHub repository created: %s" - (plist-get (plist-get data :repository) :url))) - (list name)))) - -;;;###autoload -(defun magithub-clone (username repo dir &optional sshp) - "Clone GitHub repo USERNAME/REPO into directory DIR. -If SSHP is non-nil, clone it using the SSH URL. Once the repo is -cloned, switch to a `magit-status' buffer for it. - -Interactively, prompts for the repo name and directory. With a -prefix arg, clone using SSH." - (interactive - (destructuring-bind (username . repo) (magithub-read-repo "Clone repo (user/repo): ") - (list username repo (read-directory-name "Parent directory: ") current-prefix-arg))) - ;; The trailing slash is necessary for Magit to be able to figure out - ;; that this is actually a directory, not a file - (let ((dir (concat (directory-file-name (expand-file-name dir)) "/" repo "/"))) - (magit-run-git "clone" (magithub-repo-url username repo sshp) dir) - (magit-status dir))) - - -;;; Message Mode - -(defvar magithub-message-mode-hook nil "Hook run by `magithub-message-mode'.") - -(defvar magithub-message-confirm-cancellation magit-log-edit-confirm-cancellation - "If non-nil, confirm when cancelling the editing of a `magithub-message-mode' buffer.") - -(defconst magithub-message-buffer-name "*magithub-edit-message*" - "Buffer name for composing messages.") - -(defconst magithub-message-header-end "-- End of Magithub header --\n") - -(defvar magithub-message-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-c") 'magithub-message-send) - (define-key map (kbd "C-c C-k") 'magithub-message-cancel) - (define-key map (kbd "C-c C-]") 'magithub-message-cancel) - map) - "The keymap for `magithub-message-mode'.") - -(defvar magithub-pre-message-window-configuration nil) - -(macrolet - ((define-it (parent-mode) - `(define-derived-mode magithub-message-mode ,parent-mode "Magithub Message Edit" - "A mode for editing pull requests and other GitHub messages." - (run-mode-hooks 'magithub-message-mode-hook)))) - (if (featurep 'markdown-mode) (define-it markdown-mode) - (define-it text-mode))) - -(defmacro with-magithub-message-mode (&rest body) - "Runs BODY with Magit's log-edit functions usable with Magithub's message mode." - (declare (indent 0)) - `(let ((magit-log-edit-buffer-name magithub-message-buffer-name) - (magit-log-header-end magithub-message-header-end) - (magit-log-edit-confirm-cancellation - magithub-message-confirm-cancellation) - (magit-pre-log-edit-window-configuration - magithub-pre-message-window-configuration)) - (unwind-protect (progn ,@body) - (setq magithub-pre-message-window-configuration - magit-pre-log-edit-window-configuration)))) - -(defun magithub-pop-to-message (operation) - "Open up a `magithub-message-mode' buffer and switch to it. -OPERATION is the name of what will happen when C-c C-c is used, -printed as a message when the buffer is opened." - (let ((dir default-directory) - (buf (get-buffer-create magithub-message-buffer-name))) - (setq magithub-pre-message-window-configuration - (current-window-configuration)) - (pop-to-buffer buf) - (setq default-directory dir) - (magithub-message-mode) - (message "Type C-c C-c to %s (C-c C-k to cancel)." operation))) - -(defun magithub-message-send () - "Finish writing the message and send it." - (interactive) - (let ((recipients (with-magithub-message-mode - (magit-log-edit-get-field 'recipients)))) - (with-magithub-message-mode (magit-log-edit-set-fields nil)) - (magithub-send-pull-request - (buffer-string) (split-string recipients crm-separator)) - (let (magithub-message-confirm-cancellation) - (magithub-message-cancel)))) - -(defun magithub-message-cancel () - "Abort and erase message being composed." - (interactive) - (with-magithub-message-mode (magit-log-edit-cancel-log-message))) - - -;;; Forking Repos - -(defun magithub-fork-current () - "Fork the current repository in place." - (interactive) - (destructuring-bind (owner repo _) (magithub-repo-info) - (let ((url-request-method "POST")) - (magithub-retrieve (list "repos" "fork" owner repo) - (lambda (obj repo buffer) - (with-current-buffer buffer - (magit-with-refresh - (magit-set (magithub-repo-url - (car (magithub-auth-info)) - repo 'ssh) - "remote" "origin" "url"))) - (message "Forked %s/%s" owner repo)) - (list repo (current-buffer)))))) - -(defun magithub-send-pull-request (text recipients) - "Send a pull request with text TEXT to RECIPIENTS. -RECIPIENTS should be a list of usernames." - (let ((url-request-method "POST") - (magithub-request-data (cons (cons "message[body]" text) - (mapcar (lambda (recipient) - (cons "message[to][]" recipient)) - recipients))) - (magithub-api-base magithub-github-url) - (url-max-redirections 0) ;; GitHub will try to redirect, but we don't care - magithub-parse-response) - (magithub-retrieve (list (magithub-repo-owner) (magithub-repo-name) - "pull_request" (magithub-name-rev-for-remote "HEAD" "origin")) - (lambda (_) - (kill-buffer) - (message "Your pull request was sent."))))) - -(defun magithub-pull-request (recipients) - "Compose a pull request and send it to RECIPIENTS. -RECIPIENTS should be a list of usernames. - -Interactively, reads RECIPIENTS via `magithub-read-pull-request-recipients'. -For non-interactive pull requests, see `magithub-send-pull-request'." - (interactive (list (magithub-read-pull-request-recipients))) - (with-magithub-message-mode - (magit-log-edit-set-field - 'recipients (mapconcat 'identity recipients crm-separator))) - (magithub-pop-to-message "send pull request")) - -(defun magithub-toggle-ssh (&optional arg) - "Toggle whether the current repo is checked out via SSH. -With ARG, use SSH if and only if ARG is positive." - (interactive "P") - (if (null arg) (setq arg (if (magithub-repo-ssh-p) -1 1)) - (setq arg (prefix-numeric-value arg))) - (magit-set (magithub-repo-url (magithub-repo-owner) (magithub-repo-name) (> arg 0)) - "remote" "origin" "url") - (magit-refresh-status)) - - -;;; Minor Mode - -(defvar magithub-minor-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c ' b") 'magithub-browse-file) - map)) - -(defvar magithub-status-buffer nil - "The Magit status buffer for the current buffer's Git repository.") -(make-variable-buffer-local 'magithub-status-buffer) - -(define-minor-mode magithub-minor-mode - "Minor mode for files in a GitHub repository. - -\\{magithub-minor-mode-map}" - :keymap magithub-minor-mode-map) - -(defun magithub-try-enabling-minor-mode () - "Activate `magithub-minor-mode' in this buffer if it's a Git buffer. -This means it's visiting a Git-controlled file and a Magit buffer -is open for that file's repo." - (block nil - (if magithub-minor-mode (return)) - (unless buffer-file-name (return)) - ;; Try to find the Magit status buffer for this file. - ;; If it doesn't exist, don't activate magithub-minor-mode. - (let* ((topdir (magit-get-top-dir (file-name-directory buffer-file-name))) - (status (magit-find-buffer 'status topdir))) - (unless status (return)) - (magithub-minor-mode 1) - (setq magithub-status-buffer status)))) - -(defun magithub-try-disabling-minor-mode () - "Deactivate `magithub-minor-mode' in this buffer if it's no longer a Git buffer. -See `magithub-try-enabling-minor-mode'." - (when (and magithub-minor-mode (buffer-live-p magithub-status-buffer)) - (magithub-minor-mode -1))) - -(defun magithub-try-enabling-minor-mode-everywhere () - "Run `magithub-try-enabling-minor-mode' on all buffers." - (dolist (buf (buffer-list)) - (with-current-buffer buf (magithub-try-enabling-minor-mode)))) - -(defun magithub-try-disabling-minor-mode-everywhere () - "Run `magithub-try-disabling-minor-mode' on all buffers." - (dolist (buf (buffer-list)) - (with-current-buffer buf (magithub-try-disabling-minor-mode)))) - -(magithub-try-enabling-minor-mode-everywhere) - - -;;; Hooks into Magit and Emacs - -(defun magithub-magit-init-hook () - (when (y-or-n-p "Create GitHub repo? ") - (call-interactively 'magithub-create-from-local))) -(add-hook 'magit-init-hook 'magithub-magit-init-hook) - -(defun magithub-magit-mode-hook () - "Enable `magithub-minor-mode' in buffers that are now in a Magit repo. -If the new `magit-mode' buffer is a status buffer, try enabling -`magithub-minor-mode' in all buffers." - (when (derived-mode-p 'magit-status-mode) - (magithub-try-enabling-minor-mode-everywhere))) -(add-hook 'magit-mode-hook 'magithub-magit-mode-hook) - -(defun magithub-kill-buffer-hook () - "Clean up `magithub-minor-mode'. -That is, if the buffer being killed is a Magit status buffer, -deactivate `magithub-minor-mode' on all buffers in its repository." - (when (and (eq major-mode 'magit-mode) (derived-mode-p 'magit-status-mode)) - (magithub-try-disabling-minor-mode-everywhere))) -(add-hook 'kill-buffer-hook 'magithub-kill-buffer-hook) - -(add-hook 'find-file-hook 'magithub-try-enabling-minor-mode) - - -(provide 'magithub) - -;;;###autoload -(eval-after-load 'magit - '(unless (featurep 'magithub) - (require 'magithub))) - -;;; magithub.el ends here