Switch branches/tags
Find file
951ad0c Aug 15, 2014
@sigma @tarsius @daimrod
284 lines (249 sloc) 10.3 KB
;;; org-magit.el --- basic support for magit links
;; Copyright (C) 2011, 2012 Yann Hodique.
;; Author: Yann Hodique <>
;; Keywords: git, magit, outlines
;; Version: 0.2.2
;; Package-Requires: ((magit "1.2.0") (org "6.01"))
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; This module adds support for magit links in org buffers. The following links
;; are supported:
;; - magit:/path/to/repo::commit@<hash>
;; - magit:/path/to/repo::status
;; - magit:/path/to/repo::log
;; Of course those links can be stored as usual with `org-store-link' from the
;; corresponding magit buffers. By default the path to the repo is abbreviated
;; with `abbreviate-file-name', just like org-mode does. See
;; `directory-abbrev-alist' for configuring its behavior. Alternately, you can
;; customize `org-magit-filename-transformer' and provide your own
;; transformer function.
;; When exporting those links, the variable `org-magit-known-public-providers'
;; is used to generate meaningful links. This assumes there exists a public
;; http server that is able to expose those objects.
;; Certain settings can be configured directly at the repository level
;; if needed. For example
;; $ git config org-magit.remote upstream
;; In this case, html links will point to the "upstream" webserver, instead of
;; the default "origin". URL templates can also be stored in the
;; repository. For example
;; $ git config org-magit.log http://myserver/plop.git/history
;;; Code:
(require 'cl))
(require 'org)
(require 'magit)
(defvar org-magit-actions
'((status :open current-buffer)
(log :open org-magit-open-log)
(commit :open magit-show-commit)))
(defgroup org-magit nil
"Magit links for org-mode"
:group 'magit
:group 'org-link)
(defcustom org-magit-public-remote "origin"
"Default remote to use when exporting links."
:group 'org-magit
:type 'string)
(defcustom org-magit-config-prefix "org-magit"
"Section to read from in git repository configuration."
:group 'org-magit
:type 'string)
(defun org-magit-gitweb-provider (base)
`(status ,(concat base "/?p=\\1;a=summary")
log ,(concat base "/?p=\\1;a=log")
commit ,(concat base "/?p=\\1;a=commit;h=%s")))
(defun org-magit-gitorious-provider (base)
`(status ,(concat base "/\\1")
log ,(concat base "/\\1/commits")
commit ,(concat base "/\\1/commit/%s")))
(defcustom org-magit-known-public-providers
`(;; GitHub
(,(rx bol (or ""
(and (or "git" "ssh" "http" "https") "://"
(* nonl) (? "@") ""))
(group (* nonl)) ".git")
status "\\1/"
log "\\1/commits"
commit "\\1/commit/%s")
;; Gitorious
(,(rx bol (or ""
(and (or "git://"
(or "http" "https")
(group (* nonl)) ".git")
,@(org-magit-gitorious-provider ""))
;; Bitbucket
(,(rx bol (or ""
(and (or "ssh" "http" "https") "://"
(* nonl) (? "@") "")) (group (* nonl)))
status "\\1"
log "\\1/changesets"
commit "\\1/changeset/%s")
;; org-mode
(,(rx bol "git://" (group (* nonl) ".git"))
,@(org-magit-gitweb-provider ""))
(,(rx bol (or "git" "http" "https") "://"
(group (* nonl) ".git"))
,@(org-magit-gitweb-provider "")))
"List of git providers, and how to generate links for each
object category."
:group 'org-magit
:type '(repeat (list :tag "Provider identifier" regexp
(set :tag "URL templates" :inline t
(list :inline t
(const :tag "Status" status)
(string :tag "Status URL"))
(list :inline t
(const :tag "Log" log)
(string :tag "Log URL"))
(list :inline t
(const :tag "Commit" commit)
(string :tag "Commit URL"))))))
(defcustom org-magit-filename-transformer
"Function to call to produce canonical repository name. This
must take a path as input, and provide an equivalent
representation of this path as output."
:group 'org-magit
:type 'function)
(defun org-magit-split-string (str)
(let* ((strlist (split-string str "::"))
(repo (first strlist))
(view (second strlist))
(view-sym nil)
(args nil))
(cond ((string-match "^status" view)
(setq view-sym 'status))
((string-match "^log" view)
(setq view-sym 'log))
((string-match "^commit@\\(.*\\)" view)
(setq view-sym 'commit
args (list (match-string 1 view)))))
(list view-sym repo args)))
(defun org-magit-open-log ()
(let ((buffer (current-buffer)))
(funcall (if (fboundp 'magit-log) 'magit-log 'magit-display-log))
(bury-buffer buffer)
(defun org-magit-open (str)
(let* ((split (org-magit-split-string str))
(view (first split))
(repo (second split))
(func (plist-get (cdr (assoc view org-magit-actions)) :open))
(args (third split)))
(let ((default-directory repo))
(when func
(magit-status repo))
(apply func args)))))
(defun org-magit-get (repo &rest keys)
(let ((default-directory repo))
(apply 'magit-get keys)))
(defun org-magit-guess-public-url (view url)
(let ((res nil))
(when url
(dolist (provider org-magit-known-public-providers)
(let ((regexp (car provider)))
(when (string-match regexp url)
(setq res (replace-match (plist-get (cdr provider) view)
nil nil url))))))
(defun org-magit-generate-public-url (path)
(let* ((split (org-magit-split-string path))
(view (first split))
(repo (second split))
(remote (or (org-magit-get
repo (format "%s.remote" org-magit-config-prefix))
(tpl (or (org-magit-get
repo (format "%s.%s" org-magit-config-prefix view))
view (setq remote-repo
repo (format "remote.%s.url" remote)))))))
(or (and tpl
(apply 'format tpl (third split)))
(and remote-repo
(concat remote-repo (when (third split)
(concat "@"
(mapconcat 'identity
(third split) "@")))))
(and (fboundp 'magit-svn-get-ref-info)
(let* ((default-directory repo)
(info (magit-svn-get-ref-info)))
(and info
(cdr (assoc 'url info)))))
(defun org-magit-export (path desc format)
(let ((url (or (org-magit-generate-public-url path) path)))
(set-text-properties 0 (length url) nil url)
(set-text-properties 0 (length desc) nil desc)
((eq format 'html) (format "<a href=\"%s\">%s</a>" url (or desc url)))
((eq format 'latex) (format "\\href{%s}{%s}" url (or desc url)))
(t (or desc url)))))
(defun org-magit-make-link (repo &rest components)
(apply 'concat "magit:" repo components))
(defun org-magit-clean-repository (repo)
(let ((name (file-name-nondirectory (directory-file-name repo))))
(when (not (string-match "\\.git" name))
(setq name (concat name ".git")))
;; magit used to have only one major-mode: magit-mode, and minor-modes for
;; status, log and commit. Now they are all major-modes deriving from
;; magit-mode. Let's have a backward-compatible check for the
;; current magit mode.
(defmacro org-magit-check-mode (mode)
`(or (and (boundp ',mode) ,mode)
(derived-mode-p ',mode)))
(defun org-magit-store-link ()
(when (derived-mode-p 'magit-mode)
(let* ((repo (or (and org-magit-filename-transformer
(funcall org-magit-filename-transformer
(link nil)
(desc (org-magit-clean-repository repo)))
(cond ((org-magit-check-mode magit-status-mode)
(setq link (org-magit-make-link repo "::status")
desc (format "%s status" desc)))
((org-magit-check-mode magit-log-mode)
(setq link (org-magit-make-link repo "::log")
desc (format "%s log" desc)))
((org-magit-check-mode magit-commit-mode)
(setq link (org-magit-make-link repo "::commit@"
(car magit-refresh-args))
desc (format "%s commit #%s" desc
"--short" (car magit-refresh-args))))))
:type "magit"
:link link
:description desc))))
(eval-after-load "org"
(org-add-link-type "magit" 'org-magit-open 'org-magit-export)
(add-hook 'org-store-link-functions 'org-magit-store-link)))
(provide 'org-magit)
;;; org-magit.el ends here