reddit / emacs

Link to a git commit from Org mode using Magit | THIS IS EMACS

tonyaldon · 2022-04-29 14:47
- this post in Org format: <https://github.com/tonyaldon/posts> - email me: aldon.tony@gmail.com - message me: <https://twitter.com/tonyaldon> ─────────────────────────────────────────── Hey Org mode lovers, In the article [Hyperlink (wikipedia)](https://en.wikipedia.org/wiki/Hyperlink) we can read that a hyperlink "is a reference to data that the user can follow by clicking". Do you believe me if I tell you that with Org mode the data we refer to in a link can be a buffer in `magit-revision-mode` (from [magit](https://github.com/magit/magit) package) showing us a specific commit of some git repository? For a non-Emacs user, it's not that you can or can't believe it, it's just that this sentence doesn't make sense. But for me (a regular Emacs user like you), when I read that sentence I think: 1. Really! Emacs, Org, Magit, Buffer, Link! Integration. Non-context switching. Maybe a bit of lisp. Extensible. Yes it makes sense. 2. AFTER ALL THIS IS EMACS! 3. Show me how to do it! The key elements to doing so lie in: 1. Org mode provides the built-in `elisp` type link that allows to evaluate any Elisp s-exps or to call any commands when we open this type of link with `org-open-at-point` (bound to `C-c C-o` by default), 2. and that the command `magit-show-commit` used to visit commits in magit buffers when we are on a commit line can also be used non interactively as a regular function where we have to provide it the commit hash and the repository ("module"). First we look at the syntax of `elisp` type links. A bracket link is a valid `elisp` type link if it starts with the identifier (the type) `elisp`, then is followed by a colon `:` and then followed by either an Elisp form in parentheses or an Elisp command. For instance this link: [[elisp:(+ 1 1)]] when followed with `org-open-at-point` prints the following in the echo area: (+ 1 1) => 2 And this link: [[elisp:tetris]] when followed with `org-open-at-point` starts the built-in Tetris. By default, the variable `org-link-elisp-confirm-function` is set to the function `yes-or-no-p` which means that when we open an `elisp` link we are asked for confirmation before executing the `elisp` link. We can set this variable to `nil` if we never want to be asked for confirmation. But it might be dangerous (see the docstring of this variable for an example of a dangerous `elisp` link that can remove all our user home directory). Besides setting `org-link-elisp-confirm-function` to `nil` in order not to be asked confirmation everytime we open an `elisp` link we can use the variable `org-link-elisp-skip-confirm-regexp`. Indeed, any `elisp` link that matches the regexp `org-link-elisp-skip-confirm-regexp` is executed without asking confirmation. For instance, setting up the variable `org-link-elisp-skip-confirm-regexp` to `"tetris"` lets us open the link `[[elisp:tetris]]` without asking us confirmation. (setq org-link-elisp-skip-confirm-regexp "tetris") Now things are getting spicy :) We add the Magit layer to our `elisp` link. The scenario is the following. We are "working" on the Org repository that we assume is cloned under the directory `/tmp/org-mode/`. At some point we've looked at the commit `baffebbc3` that fixes a bug in `org-get-heading` and we want to keep in our note a link to that commit. To do so we can use the following link: [[elisp:(magit-show-commit "baffebbc3" nil nil "/tmp/org-mode/")]] Now, if we open that link with `org-open-at-point`, we jump to the following buffer in `magit-revision-mode`: baffebbc33e600ec7abfe5e54b60ea6753d4f272 Author: XXX <XXX@XXX.com> AuthorDate: Fri Apr 30 14:09:05 2021 +0200 Commit: YYY <YYY@YYY.com> CommitDate: Mon Apr 25 19:40:05 2022 +0800 Parent: 240a14988 Fix typo: delete-duplicates → delete-dups Contained: main Follows: release_9.5.3 (433) Fix bug in org-get-heading Fixes #26, where fontification could make the matching and extraction of heading components fail. modified lisp/org.el @@ -6167,8 +6167,9 @@ Return nil before first heading." (let ((case-fold-search nil)) (looking-at org-complex-heading-regexp) ;; When using `org-fold-core--optimise-for-huge-buffers', - ;; returned text may be invisible. Clear it up. - (org-fold-core-remove-optimisation (match-beginning 0) (match-end 0)) + ;; returned text will be invisible. Clear it up. + (save-match-data + (org-fold-core-remove-optimisation (match-beginning 0) (match-end 0))) (let ((todo (and (not no-todo) (match-string 2))) (priority (and (not no-priority) (match-string 3))) (headline (pcase (match-string 4) TAKING NOTE WITH EMACS/ORG-MODE IS F\*\*\*ING CRAZY. DO YOU AGREE??? Now that we've seen how to use `elisp` type links with practical examples, let's see how they are implemented. Except for the reserved link types `coderef`, `custom-id`, `fuzzy` and `radio` we can defined new link types or modify an existing link types using the function `org-link-set-parameters`. For instance the link type `elisp` is defined like this in the file `ol.el`: (org-link-set-parameters "elisp" :follow #'org-link--open-elisp) Fine, but what does it mean to define a link type? It means that we add an entry to the alist `org-link-parameters` where we specify for a link type how we want the Org mode features related to links to behave regarding that type. With the default configuration the variable `org-link-parameters` looks like this (with some link types skipped): (("eww" :follow org-eww-open :store org-eww-store-link) ... ("info" :follow org-info-open :export org-info-export :store org-info-store-link) ... ("id" :follow org-id-open) ... ("shell" :follow org-link--open-shell) ... ("help" :follow org-link--open-help :store org-link--store-help) ("file" :complete org-link-complete-file) ("elisp" :follow org-link--open-elisp)) For instance, the variable `org-link-parameters` tells org-mode that when we open an `elisp` type link with `org-open-at-point` (bound to `C-c C-o` by default) the function that finally opens the link is `org-link--open-elisp` (the one after the keyword `:follow` in the variable `org-link-parameters`). And for the `info` type links (links to info node) the variable `org-link-parameters` tells org-mode: 1. to use the function `org-info-open` when we open `info` type links, 2. to use the function `org-info-export` when we export open `info` type links, 3. to use the function `org-info-store-link` when we store `info` type links. If we want to write our own type link we can look at the docstring of `org-link-parameters` to know what are the supported keys (`:follow`, `:export`, `:store`, `:activate-func`, `:complete`, `:display`, `:face`, `:help-echo`, `:htmlize-link`, `:keymap`, `:mouse-face`) and what are their accepted values. Back to `elisp` type links. When we call `org-open-at-point` (bound to `C-c C-o` by default) without universal argument on the following `elisp` type link: [[elisp:(+ 1 1)]] in its body, `org-open-at-point` parses the link at point and "delegates" the work to the function `org-link-open` passing it as first argument the parsed link like this: (org-link-open (link (:type "elisp" :path "(+ 1 1)" :format bracket :raw-link "elisp:(+ 1 1)" ... :parent (paragraph ... :parent (section ... :parent (org-data ...))))) nil ;; called without universal argument ) Then the function `org-link-open` locally binds the variables `type` and `path` respectively to `"elisp"` and `"(+ 1 1)"` getting those values out of the link `(link (:type "elisp" :path "(+ 1 1)" ...))` using the function `org-element-property`. Then as `type` is not equal to any of the following types `"file"`, `"coderef"`, `"custom-id"`, `"fuzzy"` and `"radio"` but equal to `"elisp"` the function `org-link-open` decides to use the dedicated function `org-link--open-elisp` to open `elisp` type links. This is done by locally binding `f` to `org-link--open-elisp` retrieving this value using the function `org-link-get-parameter` which is a thin wrapper around the variable `org-link-parameters` as we can see in the following code snippet (defun org-link-get-parameter (type key) "..." (plist-get (cdr (assoc type org-link-parameters)) key)) and as `org-link--open-elisp` is a function that accepts two arguments, `org-link-open` finally returns with the following call: (funcall 'org-link--open-elisp "(+ 1 1)" nil) Here are the parts of `org-link-open` we've just discussed: (defun org-link-open (link &optional arg) "..." (let ((type (org-element-property :type link)) (path (org-element-property :path link))) (pcase type ("file" ...) ((or "coderef" "custom-id" "fuzzy" "radio") ...) (_ ;; Look for a dedicated "follow" function in custom links. (let ((f (org-link-get-parameter type :follow))) (when (functionp f) ;; Function defined in `:follow' parameter may use a single ;; argument, as it was mandatory before Org 9.4. This is ;; deprecated, but support it for now. (condition-case nil (funcall (org-link-get-parameter type :follow) path arg) (wrong-number-of-arguments (funcall (org-link-get-parameter type :follow) path))))))))) Now we are left with the function `org-link--open-elisp`. Assuming we answer `yes` to the question raised by calling `org-link-elisp-confirm-function` in the condition of the `if` form, the THEN part of the `if` is evaluated. As the `path` equal to `"(+ 1 1)"` starts by a left parentheses `(`, the string `"(+ 1 1)"` is "transformed" into a lisp object by the function `read`, then is evaluted by the function `eval`, and its result is substituted in the second placeholder `%s` in the string of the `message` function. Here are the parts of `org-link--open-elisp` we've just discussed: (defun org-link--open-elisp (path _) "..." (if (...) (message "%s => %s" path (if (eq ?\( (string-to-char path)) (eval (read path)) (call-interactively (read path)))) (user-error "Abort"))) And this is how the message `(+ 1 1) => 2` is printed in the echo area when we call `org-open-at-point` on top of the `elisp` type link `[[elisp:(+ 1 1)]]`. WE ARE DONE!!! If this is the first time you have seen the `read` and `eval` functions, you may be interested in these examples: (read "(+ 1 2)") ; (+ 1 2) (type-of (read "(+ 1 2)")) ; cons (functionp (read "(+ 1 2)")) ; nil (type-of (eval (read "(+ 1 2)"))) ; integer (read "(lambda () (+ 1 2))") ; (lambda nil (+ 1 2)) (type-of (read "(lambda () (+ 1 2))")) ; cons (functionp (read "(lambda () (+ 1 2))")) ; t (type-of (eval (read "(lambda () (+ 1 2))"))) ; cons (read "foo") ; foo (type-of (read "foo")) ; symbol (type-of (eval (read "foo"))) ; error: (void-variable foo) (read "\"bar\"") ; "bar" (type-of (read "\"bar\"")) ; string (type-of (eval (read "\"bar\""))) ; string
Deep-Fox6860 · 2022-04-29 15:38
Long post, but have you tried [orgit](https://github.com/magit/orgit)? same author as Magit, doing exactly what you mentioned. It will link branches, commits, pull request with customizable link description. I have the following format: (setq orgit-rev-description-format "%%N (%%R): %s (%ai)") which produces clickable links like these (a commit in the orgmode project): org-mode (bebf0bd42): Merge branch 'bugfix' (2022-04-18 22:58:10 +0800)
mattaccount · 2022-04-29 15:47
I've really enjoyed reading your posts lately. Thanks for making them, they're exceptionally useful and I find myself exploring emacs functionality that I never would have found on my own.
paretoOptimalDev · 2022-04-29 15:54
I find it useful to automatically store orgit links when I make a commit, so if I want to talk about a recent commit it's there when I hit C-c C-l.
MohKohn · 2022-04-29 17:02
Can it handle issues? I've been meaning to find a way to add them to my todo list in some automatic way
Deep-Fox6860 · 2022-04-29 18:18
Yes, just add the tiny [orgit-forge](https://github.com/magit/orgit-forge/), also with customizable description.
ftrx · 2022-04-29 20:36
Personally I use `elisp:` link type for various other stuff: - org-ql queries in some notes, like a note for a book, and org-ql query on my notes for other books of the same author(s), similar books, book series; a contact note a query for similar contacts kinds, like "project x", "insurances" etc - running scripts/launching external apps with some arguments - change Emacs frame (I'm on EXWM) layout while using org-mode for slides purposes, like splitting windows, moving to the new one, do something there etc Other link types, of course notmuch: for individual mail messages and notmuch-search: for queries on my maildir The org-mode "glue" ability and the Emacs integration form the Emacs OE (Operating Environment), the essence of a desktop environment in the classical sense. Text is at the center because almost anything is or can be reduced to text and text is "live", rendered and interpreted continuously/in response to events. There are no barriers between "apps" because in our head there are no barrier between different information kinds. Org-mode is the "metadata-rich storage" and the surrounding are the means to profit from that storage. I also suggest not being shy and try org-attach, with a global dir and org-roam or linkmarks to access notes, so headings, so attachments. Long story short: instead of traversing a curated taxonomy do a search&narrow style access to anything. The personal Google for our desktops.
tonyaldon · 2022-04-29 21:13
I'm really glad to hear that :) Thank you for taking the time to read them. > I find myself exploring emacs functionality that I never would have found on my own. REALLY REALLY COOL, this gives me a lot of motivation to continue.
tonyaldon · 2022-04-29 21:21
I haven't thought about using `org-ql` in elisp type links. Really good idea. I often use elisp type links with `rg.el` to keep the searches I've done in repositories. > Org-mode is the "metadata-rich storage" and the surrounding are the means to profit from that storage. Well said! Agreed! The flexibility of Emacs and org-mode is impressive. It seems that the limits are just dictated by our creativity and our needs. Nothing else.
tonyaldon · 2022-04-29 21:24
No I have never tried it. Thank you for sharing the link.
rswgnu · 2022-04-29 23:14
Or you could use Hyperbole and type: git#bebf0bd42 in any kind of file and have your link, not just Org mode. So you can have Org and Hyperbole’s easier to use links in the same buffer if you like as well.
tonyaldon · 2022-04-30 07:02
I didn't about Hyperbole. Thank you for sharing.
ftrx · 2022-04-30 07:18
> I haven't thought about using org-ql in elisp type links. Really good idea. Thanks, I do just because interactive org-ql is not that quick to interact for me and org-ql-views are too rigid to add to my config, so that is a middle ground. I add a new book for an author I already have some books, I just grab the query/link from another note, a new one a generic yasnippet gives me the base to create it quickly. My most important pain is consistency using org-ql mostly on drawers key/value (meta)data and create such drawers and queries with yasnippet to ensure a bit of consistency. Since it's still long I do that only on some notes (contracts, books, contacts, ...) that I add "rarely enough" to accept the small extra bit of time for the outcome... > The flexibility of Emacs and org-mode is impressive. It seems that the limits are just dictated by our creativity and our needs. Nothing else. Org-mode is a text-centric tool, like the rest of Emacs in the end, in text we type short messages in chats, letters, articles, books, thesis, laws, usage instructions, ... probably that's the limit: what we do in text, just automated through org-mode. If you like going a bit through the history I suggest Cataloging the World, Paul Otlet and the Birth of the Information Age by Alex Wright (or just search for "Mundaneum" and its "electronic telescopes") well, I think the web Paul Otlet and Henry La Fontaine have imagined in ~1930 is an org-mode web :-) What we, humans using computers these days, have completely lost is the classic concept of desktop, witch is exactly what Emacs and org-mode do.
tonyaldon · 2022-04-30 15:47
Thank you for the references. > I think the web Paul Otlet and Henry La Fontaine have imagined in ~1930 is an org-mode web :-) There is no doubt about it :)