This commit is contained in:
SqrtMinusOne 2023-01-02 17:39:12 +00:00
parent 47facf13a8
commit a4a277e015
11 changed files with 897 additions and 27 deletions

BIN
images/gource/gource.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

252
index.xml
View file

@ -6,7 +6,257 @@
<description>Recent content in Index on SqrtMinusOne</description>
<generator>Hugo -- gohugo.io</generator>
<language>en-us</language>
<lastBuildDate>Fri, 16 Sep 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/index.xml" rel="self" type="application/rss+xml" />
<lastBuildDate>Mon, 02 Jan 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Running Gource with Emacs</title>
<link>https://sqrtminusone.xyz/posts/2023-01-02-gource/</link>
<pubDate>Mon, 02 Jan 2023 00:00:00 +0000</pubDate>
<guid>https://sqrtminusone.xyz/posts/2023-01-02-gource/</guid>
<content type="html">
&lt;figure&gt;&lt;img src=&#34;https://sqrtminusone.xyz/images/gource/gource.png&#34;/&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&#34;https://gource.io/&#34;&gt;Gource&lt;/a&gt; is a program that draws an animated graph of users changing the repository over time.&lt;/p&gt;
&lt;p&gt;Although it can work without extra effort (just run &lt;code&gt;gource&lt;/code&gt; in a &lt;a href=&#34;https://git-scm.com/&#34;&gt;git&lt;/a&gt; repo), there are some tweaks that can be done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gource supports using custom pictures for users. &lt;a href=&#34;https://en.gravatar.com/&#34;&gt;Gravatar&lt;/a&gt; is an obvious place to get these.&lt;/li&gt;
&lt;li&gt;Occasionally, the same people have different names and/or emails in history.&lt;br /&gt;
It may happen when people use forges like &lt;a href=&#34;https://gitlab.com/&#34;&gt;GitLab&lt;/a&gt; or just have different settings on different machines. It would be nice to merge these names.&lt;/li&gt;
&lt;li&gt;Visualizing the history of multiple repositories (e.g. frontend and backend) requires combining multiple gource logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, why not try doing that with Emacs?&lt;/p&gt;
&lt;h2 id=&#34;gravatars&#34;&gt;Gravatars&lt;/h2&gt;
&lt;p&gt;Much to my surprise, Emacs turned out to have a built-in package called &lt;a href=&#34;https://github.com/emacs-mirror/emacs/blob/master/lisp/image/gravatar.el&#34;&gt;gravatar.el&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, let&amp;rsquo;s make a function to retrieve a gravatar and save it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-retrieve-sync&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Get gravatar for EMAIL and save it to FILE-NAME.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;gravatar-default-image&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;identicon&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;gravatar-size&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;coding-system-for-write&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;binary&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;write-region-annotate-functions&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;write-region-post-annotation-function&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;write-region&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;image-property&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;gravatar-retrieve-synchronously&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;) &lt;span style=&#34;color:#008000&#34;&gt;:data&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:silent&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To use these images, we need to save them to some folder and use usernames as file names. The folder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;setq&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-folder&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;/home/pavel/.cache/gravatars/&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the function that downloads a gravatar if necessary:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-save&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Download gravatar for EMAIL.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;AUTHOR is the username.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-folder&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;.png&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;mkdir&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-folder&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;t&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;unless&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;file-exists-p&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Fetching gravatar for %s (%s)&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-retrieve-sync&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;merging-authors&#34;&gt;Merging authors&lt;/h2&gt;
&lt;p&gt;Now to merging authors.&lt;/p&gt;
&lt;p&gt;Gource itself uses only usernames (without emails), but we can use &lt;code&gt;git log&lt;/code&gt; to get both. The required information can be extracted like that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git log --pretty&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;format:&lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;%ae|%an&amp;#34;&lt;/span&gt; | sort | uniq -c | sed &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;s/^[ \t]*//;s/ /|/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output is a list of pipe-separated strings, where the values are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of occurrences for this combination of username and email&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Username&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, that part would have to be changed appropriately for other version control systems if you happen to use one.&lt;/p&gt;
&lt;p&gt;So, below is one hell of a function that wraps this command and tries to merge emails and usernames belonging to one author:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/git-get-authors&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#008000&#34;&gt;&amp;amp;optional&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors-init&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Extract and merge all combinations of authors &amp;amp; emails from REPO.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;REPO is the path to a git repository.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;AUTHORS-INIT is the previous output of &lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;`my/git-get-authors&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;. It can
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;be used to extract that information from multiple repositories.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;The output is a list of alists with following keys:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;- emails: list of (&amp;lt;email&amp;gt; . &amp;lt;count&amp;gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;- authors: list of (&amp;lt;username&amp;gt; . &amp;lt;count&amp;gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;- email: the most popular email
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;- author: the most popular username
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;I.e. one alist is all emails and usernames of one author.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let*&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;default-directory&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;data&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;shell-command-to-string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;git log --pretty=format:\&amp;#34;%ae|%an\&amp;#34; | sort | uniq -c | sed \&amp;#34;s/^[ \t]*//;s/ /|/\&amp;#34;&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-loop&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;string&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;length&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;string&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)) &lt;span style=&#34;color:#666&#34;&gt;3&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;collect&lt;/span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;string&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#666&#34;&gt;`&lt;/span&gt;((&lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;string-to-number&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;downcase&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)))))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;mapcar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setf&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cl-reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;cdr&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;) (&lt;span style=&#34;color:#00f&#34;&gt;cdr&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:initial-value&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;#39;&lt;/span&gt;(&lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;-1&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setf&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cl-reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;cdr&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;) (&lt;span style=&#34;color:#00f&#34;&gt;cdr&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:initial-value&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;#39;&lt;/span&gt;(&lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;-1&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cl-reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;val&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let*&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;val&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;val&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;count&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;val&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;seq-find&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;or&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;progn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-incf&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;push&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;cons&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;) (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-incf&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;push&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;cons&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;) (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setq&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;push&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;`&lt;/span&gt;((&lt;span style=&#34;color:#19177c&#34;&gt;emails&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; ((&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; ((&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:initial-value&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors-init&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Despite the probable we-enjoy-typing-ness of the implementation, it&amp;rsquo;s actually pretty simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The output of &lt;code&gt;git log&lt;/code&gt; is parsed into a list of alists with &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;author&lt;/code&gt; as keys.&lt;/li&gt;
&lt;li&gt;This list is reduced by &lt;code&gt;cl-reduce&lt;/code&gt; into a list of alists with &lt;code&gt;emails&lt;/code&gt; and &lt;code&gt;authors&lt;/code&gt; as keys and the respective counts as values, e.g. &lt;code&gt;((&amp;lt;email-1&amp;gt; . 1) (&amp;lt;email-2&amp;gt; . 3))&lt;/code&gt;.&lt;br /&gt;
I&amp;rsquo;ve seen a couple of cases where people would swap their username and email (lol), so &lt;code&gt;seq-find&lt;/code&gt; also looks for an email in the list of authors and vice versa.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;mapcar&lt;/code&gt; call determines the most popular email and username for each authors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output is another list of alists, now with the following keys:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;emails&lt;/code&gt; - list of elements like &lt;code&gt;(&amp;lt;email&amp;gt; . &amp;lt;count&amp;gt;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;authors&lt;/code&gt; - list of elements like &lt;code&gt;(&amp;lt;author-name&amp;gt; . &amp;lt;count&amp;gt;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt; - the most popular email&lt;/li&gt;
&lt;li&gt;&lt;code&gt;author&lt;/code&gt; - the most popular username.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;running-for-multiple-repos&#34;&gt;Running for multiple repos&lt;/h2&gt;
&lt;p&gt;This section was mostly informed by &lt;a href=&#34;https://github.com/acaudwell/Gource/wiki/Visualizing-Multiple-Repositories&#34;&gt;this page&lt;/a&gt; in the &lt;a href=&#34;https://github.com/acaudwell/Gource/wiki&#34;&gt;gource wiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I said above, by default &lt;code&gt;gource&lt;/code&gt; just creates a visualization for the current repo. To change something in it, we need to invoke the program like that: &lt;code&gt;gource --output-custom-log PATH&lt;/code&gt;, where &lt;code&gt;PATH&lt;/code&gt; is either the path to the log file or &lt;code&gt;-&lt;/code&gt; for stdout.&lt;/p&gt;
&lt;p&gt;The log consists of lines of pipe-separated strings, e.g.:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1600769568|dsofronov|A|/studentor/.dockerignore
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1600769568|dsofronov|A|/studentor/.editorconfig
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1600769568|dsofronov|A|/studentor/.flake8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1600769568|dsofronov|A|/studentor/.gitignore
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;where the values of one line are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UNIX timestamp&lt;/li&gt;
&lt;li&gt;Author name&lt;/li&gt;
&lt;li&gt;&lt;code&gt;A&lt;/code&gt; for add, &lt;code&gt;M&lt;/code&gt; for modify, and &lt;code&gt;D&lt;/code&gt; for delete&lt;/li&gt;
&lt;li&gt;Path to file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The file has to be sorted by the timestamp in ascending order.&lt;/p&gt;
&lt;p&gt;So, the function that prepares the log for one repository:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gource-prepare-log&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Create gource log string for REPO.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;AUTHORS is the output of &lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;`my/git-get-authors&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#00f&#34;&gt;log&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;shell-command-to-string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;gource --output-custom-log - &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;authors-mapping&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;make-hash-table&lt;/span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:test&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;equal&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;prefix&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;file-name-base&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-loop&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author-datum&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author-datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;do&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-save&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author-datum&lt;/span&gt;) &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;do&lt;/span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-loop&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;other-author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author-datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;unless&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;string-equal&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;other-author&lt;/span&gt;) &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;do&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;puthash&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;other-author&lt;/span&gt;) &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors-mapping&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-loop&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;log&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;when&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;) &lt;span style=&#34;color:#666&#34;&gt;3&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;when-let&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;mapped-author&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;gethash&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors-mapping&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setf&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;) &lt;span style=&#34;color:#19177c&#34;&gt;mapped-author&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setf&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;prefix&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;string-join&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downloads a gravatar for each author&lt;/li&gt;
&lt;li&gt;Replaces all usernames of one author with the most frequent one&lt;/li&gt;
&lt;li&gt;Prepends the file path with the repository name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output is a string in the gource log format as described above.&lt;/p&gt;
&lt;p&gt;Finally, as we need to invoke all of this for multiple repositories, why not do that with &lt;a href=&#34;https://www.gnu.org/software/emacs/manual/html_node/emacs/Dired.html&#34;&gt;dired&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gource-dired-create-logs&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;repos&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;log-name&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Create combined gource log for REPOS.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;REPOS is a list of strings, where a string is a path to a git repo.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;LOG-NAME is the path to the resulting log file.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;This function is meant to be invoked from &lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;`dired&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;, where the required
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;repositories are marked.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;interactive&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;list&lt;/span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;or&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;dired-get-marked-files&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;file-directory-p&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#d2413a;font-weight:bold&#34;&gt;user-error&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Select at least one directory&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;read-file-name&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Log file name: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;combined.log&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cl-reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;my/git-get-authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repos&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:initial-value&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;with-temp-file&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;log-name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;insert&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;string-join&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;seq-filter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;not&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;string-empty-p&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;seq-sort-by&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;if-let&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;time&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;string-to-number&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;time&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#666&#34;&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;&amp;lt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;mapconcat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;my/gource-prepare-log&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repos&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function extracts authors from each repository and merges the logs as required by gource, that is sorting the result by time in ascending order.&lt;/p&gt;
&lt;h2 id=&#34;using-the-function&#34;&gt;Using the function&lt;/h2&gt;
&lt;p&gt;To use the function above, mark the required repos in a dired buffer and run &lt;code&gt;M-x my/gource-dired-create-logs&lt;/code&gt;. This also works nicely with &lt;a href=&#34;https://github.com/Fuco1/dired-hacks&#34;&gt;dired-subtree&lt;/a&gt;, in case your repos are located in different folders.&lt;/p&gt;
&lt;p&gt;The function will create a combined log file (by default &lt;code&gt;combined.log&lt;/code&gt;). To visualize the log, run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gource &amp;lt;log-file&amp;gt; --user-image-dir &amp;lt;path-to-gravatars&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check the &lt;a href=&#34;https://github.com/acaudwell/Gource&#34;&gt;README&lt;/a&gt; for possible parameters, such as the speed of visualization, different elements, etc. That&amp;rsquo;s it!&lt;/p&gt;
&lt;p&gt;I thought about making something like a &lt;a href=&#34;https://github.com/magit/transient&#34;&gt;transient.el&lt;/a&gt; wrapper around the &lt;code&gt;gource&lt;/code&gt; command but figured it wasn&amp;rsquo;t worth the effort for something that I run just a handful of times in a year.&lt;/p&gt;
</content>
</item>
<item>
<title>Podcast transcripts with elfeed &amp; speech recognition engine</title>
<link>https://sqrtminusone.xyz/posts/2022-09-16-vosk/</link>

View file

@ -0,0 +1,351 @@
<!DOCTYPE html>
<html lang=""><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Running Gource with Emacs</title>
<meta name="description" content="Freedom is a state of mind">
<meta name="author" content='SqrtMinusOne'>
<link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w==" crossorigin="anonymous">
<link rel="stylesheet" href="/sass/researcher.min.css">
<link rel="icon" type="image/ico" href="https://sqrtminusone.xyz/favicon.ico">
<script defer data-domain="sqrtminusone.xyz" src="https://plausible.sqrtminusone.xyz/js/plausible.js"></script>
</head>
<body><div class="container mt-5">
<nav class="navbar navbar-expand-sm flex-column flex-sm-row text-nowrap p-0">
<a class="navbar-brand mx-0 mr-sm-auto" href="https://sqrtminusone.xyz/" title="SqrtMinusOne">
SqrtMinusOne
</a>
<div class="navbar-nav flex-row flex-wrap justify-content-center">
<a class="nav-item nav-link" href="/" title="Index">
Index
</a>
<span class="nav-item navbar-text mx-1">/</span>
<a class="nav-item nav-link" href="/posts/" title="Posts">
Posts
</a>
<span class="nav-item navbar-text mx-1">/</span>
<a class="nav-item nav-link" href="/configs/readme" title="Configs">
Configs
</a>
</div>
</nav>
</div>
<hr>
<div id="content">
<script defer language="javascript" type="text/javascript" src="/js/dynamic-toc.js"></script>
<div class="root">
<h1 id="title-small-screen">Running Gource with Emacs</h1>
<div class="container" id="actual-content">
<h1 id="title-large-screen">Running Gource with Emacs</h1>
<figure><img src="/images/gource/gource.png"/>
</figure>
<p><a href="https://gource.io/">Gource</a> is a program that draws an animated graph of users changing the repository over time.</p>
<p>Although it can work without extra effort (just run <code>gource</code> in a <a href="https://git-scm.com/">git</a> repo), there are some tweaks that can be done:</p>
<ul>
<li>Gource supports using custom pictures for users. <a href="https://en.gravatar.com/">Gravatar</a> is an obvious place to get these.</li>
<li>Occasionally, the same people have different names and/or emails in history.<br />
It may happen when people use forges like <a href="https://gitlab.com/">GitLab</a> or just have different settings on different machines. It would be nice to merge these names.</li>
<li>Visualizing the history of multiple repositories (e.g. frontend and backend) requires combining multiple gource logs.</li>
</ul>
<p>So, why not try doing that with Emacs?</p>
<h2 id="gravatars">Gravatars</h2>
<p>Much to my surprise, Emacs turned out to have a built-in package called <a href="https://github.com/emacs-mirror/emacs/blob/master/lisp/image/gravatar.el">gravatar.el</a>.</p>
<p>So, let&rsquo;s make a function to retrieve a gravatar and save it:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/gravatar-retrieve-sync</span> (<span style="color:#19177c">email</span> <span style="color:#19177c">file-name</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Get gravatar for EMAIL and save it to FILE-NAME.&#34;</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">gravatar-default-image</span> <span style="color:#ba2121">&#34;identicon&#34;</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">gravatar-size</span> <span style="color:#800">nil</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">coding-system-for-write</span> <span style="color:#19177c">&#39;binary</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">write-region-annotate-functions</span> <span style="color:#800">nil</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">write-region-post-annotation-function</span> <span style="color:#800">nil</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#00f">write-region</span>
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">image-property</span> (<span style="color:#19177c">gravatar-retrieve-synchronously</span> <span style="color:#19177c">email</span>) <span style="color:#008000">:data</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#19177c">file-name</span> <span style="color:#800">nil</span> <span style="color:#008000">:silent</span>)))
</span></span></code></pre></div><p>To use these images, we need to save them to some folder and use usernames as file names. The folder:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">setq</span> <span style="color:#19177c">my/gravatar-folder</span> <span style="color:#ba2121">&#34;/home/pavel/.cache/gravatars/&#34;</span>)
</span></span></code></pre></div><p>And the function that downloads a gravatar if necessary:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/gravatar-save</span> (<span style="color:#19177c">email</span> <span style="color:#19177c">author</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Download gravatar for EMAIL.
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">AUTHOR is the username.&#34;</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">file-name</span> (<span style="color:#00f">concat</span> <span style="color:#19177c">my/gravatar-folder</span> <span style="color:#19177c">author</span> <span style="color:#ba2121">&#34;.png&#34;</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">mkdir</span> <span style="color:#19177c">my/gravatar-folder</span> <span style="color:#800">t</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">unless</span> (<span style="color:#00f">file-exists-p</span> <span style="color:#19177c">file-name</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#00f">message</span> <span style="color:#ba2121">&#34;Fetching gravatar for %s (%s)&#34;</span> <span style="color:#19177c">author</span> <span style="color:#19177c">email</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/gravatar-retrieve-sync</span> <span style="color:#19177c">email</span> <span style="color:#19177c">file-name</span>))))
</span></span></code></pre></div><h2 id="merging-authors">Merging authors</h2>
<p>Now to merging authors.</p>
<p>Gource itself uses only usernames (without emails), but we can use <code>git log</code> to get both. The required information can be extracted like that:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git log --pretty<span style="color:#666">=</span>format:<span style="color:#ba2121">&#34;%ae|%an&#34;</span> | sort | uniq -c | sed <span style="color:#ba2121">&#34;s/^[ \t]*//;s/ /|/&#34;</span>
</span></span></code></pre></div><p>The output is a list of pipe-separated strings, where the values are:</p>
<ul>
<li>Number of occurrences for this combination of username and email</li>
<li>Email</li>
<li>Username</li>
</ul>
<p>Of course, that part would have to be changed appropriately for other version control systems if you happen to use one.</p>
<p>So, below is one hell of a function that wraps this command and tries to merge emails and usernames belonging to one author:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/git-get-authors</span> (<span style="color:#19177c">repo</span> <span style="color:#008000">&amp;optional</span> <span style="color:#19177c">authors-init</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Extract and merge all combinations of authors &amp; emails from REPO.
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">REPO is the path to a git repository.
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">AUTHORS-INIT is the previous output of </span><span style="color:#19177c">`my/git-get-authors&#39;</span><span style="color:#ba2121">. It can
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">be used to extract that information from multiple repositories.
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">The output is a list of alists with following keys:
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">- emails: list of (&lt;email&gt; . &lt;count&gt;)
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">- authors: list of (&lt;username&gt; . &lt;count&gt;)
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">- email: the most popular email
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">- author: the most popular username
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">I.e. one alist is all emails and usernames of one author.&#34;</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let*</span> ((<span style="color:#19177c">default-directory</span> <span style="color:#19177c">repo</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">data</span> (<span style="color:#19177c">shell-command-to-string</span>
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;git log --pretty=format:\&#34;%ae|%an\&#34; | sort | uniq -c | sed \&#34;s/^[ \t]*//;s/ /|/\&#34;&#34;</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">authors</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">for</span> <span style="color:#00f">string</span> <span style="color:#19177c">in</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">data</span> <span style="color:#ba2121">&#34;\n&#34;</span>)
</span></span><span style="display:flex;"><span> <span style="color:#008000">if</span> (<span style="color:#00f">=</span> (<span style="color:#00f">length</span> (<span style="color:#19177c">split-string</span> <span style="color:#00f">string</span> <span style="color:#ba2121">&#34;|&#34;</span>)) <span style="color:#666">3</span>)
</span></span><span style="display:flex;"><span> <span style="color:#19177c">collect</span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">datum</span> (<span style="color:#19177c">split-string</span> <span style="color:#00f">string</span> <span style="color:#ba2121">&#34;|&#34;</span>)))
</span></span><span style="display:flex;"><span> <span style="color:#666">`</span>((<span style="color:#19177c">count</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#00f">string-to-number</span> (<span style="color:#00f">nth</span> <span style="color:#666">0</span> <span style="color:#19177c">datum</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">email</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#00f">downcase</span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">datum</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">author</span> <span style="color:#666">.</span> <span style="color:#666">,</span>(<span style="color:#00f">nth</span> <span style="color:#666">2</span> <span style="color:#19177c">datum</span>)))))))
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapcar</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">datum</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setf</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;author</span> <span style="color:#19177c">datum</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#00f">car</span> (<span style="color:#19177c">cl-reduce</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">acc</span> <span style="color:#19177c">author</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#00f">&gt;</span> (<span style="color:#00f">cdr</span> <span style="color:#19177c">author</span>) (<span style="color:#00f">cdr</span> <span style="color:#19177c">acc</span>))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">author</span>
</span></span><span style="display:flex;"><span> <span style="color:#19177c">acc</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;authors</span> <span style="color:#19177c">datum</span>)
</span></span><span style="display:flex;"><span> <span style="color:#008000">:initial-value</span> <span style="color:#666">&#39;</span>(<span style="color:#800">nil</span> <span style="color:#666">.</span> <span style="color:#666">-1</span>))))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setf</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;email</span> <span style="color:#19177c">datum</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#00f">car</span> (<span style="color:#19177c">cl-reduce</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">acc</span> <span style="color:#19177c">email</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#00f">&gt;</span> (<span style="color:#00f">cdr</span> <span style="color:#19177c">email</span>) (<span style="color:#00f">cdr</span> <span style="color:#19177c">acc</span>))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">email</span>
</span></span><span style="display:flex;"><span> <span style="color:#19177c">acc</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;emails</span> <span style="color:#19177c">datum</span>)
</span></span><span style="display:flex;"><span> <span style="color:#008000">:initial-value</span> <span style="color:#666">&#39;</span>(<span style="color:#800">nil</span> <span style="color:#666">.</span> <span style="color:#666">-1</span>))))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">datum</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">cl-reduce</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">acc</span> <span style="color:#19177c">val</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let*</span> ((<span style="color:#19177c">author</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;author</span> <span style="color:#19177c">val</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">email</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;email</span> <span style="color:#19177c">val</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">count</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;count</span> <span style="color:#19177c">val</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">saved-value</span>
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-find</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">cand</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">or</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">email</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;emails</span> <span style="color:#19177c">cand</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;string-equal</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">author</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;authors</span> <span style="color:#19177c">cand</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;string-equal</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">email</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;authors</span> <span style="color:#19177c">cand</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;string-equal</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">author</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;emails</span> <span style="color:#19177c">cand</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;string-equal</span>)))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">acc</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> <span style="color:#19177c">saved-value</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">progn</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">email</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;emails</span> <span style="color:#19177c">saved-value</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;string-equal</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-incf</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">email</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;emails</span> <span style="color:#19177c">saved-value</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;string-equal</span>)
</span></span><span style="display:flex;"><span> <span style="color:#19177c">count</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">push</span> (<span style="color:#00f">cons</span> <span style="color:#19177c">email</span> <span style="color:#19177c">count</span>) (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;emails</span> <span style="color:#19177c">saved-value</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">if</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">author</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;authors</span> <span style="color:#19177c">saved-value</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;string-equal</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-incf</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">author</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;authors</span> <span style="color:#19177c">saved-value</span>)
</span></span><span style="display:flex;"><span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;string-equal</span>)
</span></span><span style="display:flex;"><span> <span style="color:#19177c">count</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#008000">push</span> (<span style="color:#00f">cons</span> <span style="color:#19177c">author</span> <span style="color:#19177c">count</span>) (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;authors</span> <span style="color:#19177c">saved-value</span>))))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setq</span> <span style="color:#19177c">saved-value</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">push</span> <span style="color:#666">`</span>((<span style="color:#19177c">emails</span> <span style="color:#666">.</span> ((<span style="color:#666">,</span><span style="color:#19177c">email</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">count</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">authors</span> <span style="color:#666">.</span> ((<span style="color:#666">,</span><span style="color:#19177c">author</span> <span style="color:#666">.</span> <span style="color:#666">,</span><span style="color:#19177c">count</span>))))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">acc</span>)))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">acc</span>))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">authors</span>
</span></span><span style="display:flex;"><span> <span style="color:#008000">:initial-value</span> <span style="color:#19177c">authors-init</span>))))
</span></span></code></pre></div><p>Despite the probable we-enjoy-typing-ness of the implementation, it&rsquo;s actually pretty simple:</p>
<ul>
<li>The output of <code>git log</code> is parsed into a list of alists with <code>count</code>, <code>email</code> and <code>author</code> as keys.</li>
<li>This list is reduced by <code>cl-reduce</code> into a list of alists with <code>emails</code> and <code>authors</code> as keys and the respective counts as values, e.g. <code>((&lt;email-1&gt; . 1) (&lt;email-2&gt; . 3))</code>.<br />
I&rsquo;ve seen a couple of cases where people would swap their username and email (lol), so <code>seq-find</code> also looks for an email in the list of authors and vice versa.</li>
<li>The <code>mapcar</code> call determines the most popular email and username for each authors.</li>
</ul>
<p>The output is another list of alists, now with the following keys:</p>
<ul>
<li><code>emails</code> - list of elements like <code>(&lt;email&gt; . &lt;count&gt;)</code></li>
<li><code>authors</code> - list of elements like <code>(&lt;author-name&gt; . &lt;count&gt;)</code></li>
<li><code>email</code> - the most popular email</li>
<li><code>author</code> - the most popular username.</li>
</ul>
<h2 id="running-for-multiple-repos">Running for multiple repos</h2>
<p>This section was mostly informed by <a href="https://github.com/acaudwell/Gource/wiki/Visualizing-Multiple-Repositories">this page</a> in the <a href="https://github.com/acaudwell/Gource/wiki">gource wiki</a>.</p>
<p>As I said above, by default <code>gource</code> just creates a visualization for the current repo. To change something in it, we need to invoke the program like that: <code>gource --output-custom-log PATH</code>, where <code>PATH</code> is either the path to the log file or <code>-</code> for stdout.</p>
<p>The log consists of lines of pipe-separated strings, e.g.:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>1600769568|dsofronov|A|/studentor/.dockerignore
</span></span><span style="display:flex;"><span>1600769568|dsofronov|A|/studentor/.editorconfig
</span></span><span style="display:flex;"><span>1600769568|dsofronov|A|/studentor/.flake8
</span></span><span style="display:flex;"><span>1600769568|dsofronov|A|/studentor/.gitignore
</span></span></code></pre></div><p>where the values of one line are:</p>
<ul>
<li>UNIX timestamp</li>
<li>Author name</li>
<li><code>A</code> for add, <code>M</code> for modify, and <code>D</code> for delete</li>
<li>Path to file</li>
</ul>
<p>The file has to be sorted by the timestamp in ascending order.</p>
<p>So, the function that prepares the log for one repository:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/gource-prepare-log</span> (<span style="color:#19177c">repo</span> <span style="color:#19177c">authors</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Create gource log string for REPO.
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">AUTHORS is the output of </span><span style="color:#19177c">`my/git-get-authors&#39;</span><span style="color:#ba2121">.&#34;</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#00f">log</span> (<span style="color:#19177c">shell-command-to-string</span>
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span>
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;gource --output-custom-log - &#34;</span>
</span></span><span style="display:flex;"><span> <span style="color:#19177c">repo</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">authors-mapping</span> (<span style="color:#00f">make-hash-table</span> <span style="color:#008000">:test</span> <span style="color:#00f">#&#39;equal</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">prefix</span> (<span style="color:#19177c">file-name-base</span> <span style="color:#19177c">repo</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">for</span> <span style="color:#19177c">author-datum</span> <span style="color:#19177c">in</span> <span style="color:#19177c">authors</span>
</span></span><span style="display:flex;"><span> <span style="color:#19177c">for</span> <span style="color:#19177c">author</span> <span style="color:#00f">=</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;author</span> <span style="color:#19177c">author-datum</span>)
</span></span><span style="display:flex;"><span> <span style="color:#008000">do</span> (<span style="color:#19177c">my/gravatar-save</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;email</span> <span style="color:#19177c">author-datum</span>) <span style="color:#19177c">author</span>)
</span></span><span style="display:flex;"><span> <span style="color:#008000">do</span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">for</span> <span style="color:#19177c">other-author</span> <span style="color:#19177c">in</span> (<span style="color:#19177c">alist-get</span> <span style="color:#19177c">&#39;authors</span> <span style="color:#19177c">author-datum</span>)
</span></span><span style="display:flex;"><span> <span style="color:#008000">unless</span> (<span style="color:#00f">string-equal</span> (<span style="color:#00f">car</span> <span style="color:#19177c">other-author</span>) <span style="color:#19177c">author</span>)
</span></span><span style="display:flex;"><span> <span style="color:#008000">do</span> (<span style="color:#00f">puthash</span> (<span style="color:#00f">car</span> <span style="color:#19177c">other-author</span>) <span style="color:#19177c">author</span>
</span></span><span style="display:flex;"><span> <span style="color:#19177c">authors-mapping</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">cl-loop</span> <span style="color:#19177c">for</span> <span style="color:#19177c">line</span> <span style="color:#19177c">in</span> (<span style="color:#19177c">split-string</span> <span style="color:#00f">log</span> <span style="color:#ba2121">&#34;\n&#34;</span>)
</span></span><span style="display:flex;"><span> <span style="color:#00f">concat</span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">fragments</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">line</span> <span style="color:#ba2121">&#34;|&#34;</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">when</span> (<span style="color:#00f">&gt;</span> (<span style="color:#00f">length</span> <span style="color:#19177c">fragments</span>) <span style="color:#666">3</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">when-let</span> (<span style="color:#19177c">mapped-author</span> (<span style="color:#00f">gethash</span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">fragments</span>)
</span></span><span style="display:flex;"><span> <span style="color:#19177c">authors-mapping</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setf</span> (<span style="color:#00f">nth</span> <span style="color:#666">1</span> <span style="color:#19177c">fragments</span>) <span style="color:#19177c">mapped-author</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">setf</span> (<span style="color:#00f">nth</span> <span style="color:#666">3</span> <span style="color:#19177c">fragments</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#00f">concat</span> <span style="color:#ba2121">&#34;/&#34;</span> <span style="color:#19177c">prefix</span> (<span style="color:#00f">nth</span> <span style="color:#666">3</span> <span style="color:#19177c">fragments</span>))))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">string-join</span> <span style="color:#19177c">fragments</span> <span style="color:#ba2121">&#34;|&#34;</span>))
</span></span><span style="display:flex;"><span> <span style="color:#00f">concat</span> <span style="color:#ba2121">&#34;\n&#34;</span>)))
</span></span></code></pre></div><p>This function:</p>
<ul>
<li>Downloads a gravatar for each author</li>
<li>Replaces all usernames of one author with the most frequent one</li>
<li>Prepends the file path with the repository name.</li>
</ul>
<p>The output is a string in the gource log format as described above.</p>
<p>Finally, as we need to invoke all of this for multiple repositories, why not do that with <a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Dired.html">dired</a>:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span style="display:flex;"><span>(<span style="color:#008000">defun</span> <span style="color:#19177c">my/gource-dired-create-logs</span> (<span style="color:#19177c">repos</span> <span style="color:#19177c">log-name</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;Create combined gource log for REPOS.
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">REPOS is a list of strings, where a string is a path to a git repo.
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">LOG-NAME is the path to the resulting log file.
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">This function is meant to be invoked from </span><span style="color:#19177c">`dired&#39;</span><span style="color:#ba2121">, where the required
</span></span></span><span style="display:flex;"><span><span style="color:#ba2121">repositories are marked.&#34;</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">interactive</span> (<span style="color:#00f">list</span> (<span style="color:#008000">or</span> (<span style="color:#19177c">dired-get-marked-files</span> <span style="color:#800">nil</span> <span style="color:#800">nil</span> <span style="color:#00f">#&#39;file-directory-p</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#d2413a;font-weight:bold">user-error</span> <span style="color:#ba2121">&#34;Select at least one directory&#34;</span>))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">read-file-name</span> <span style="color:#ba2121">&#34;Log file name: &#34;</span> <span style="color:#800">nil</span> <span style="color:#ba2121">&#34;combined.log&#34;</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">let</span> ((<span style="color:#19177c">authors</span>
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">cl-reduce</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">acc</span> <span style="color:#19177c">repo</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/git-get-authors</span> <span style="color:#19177c">repo</span> <span style="color:#19177c">acc</span>))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">repos</span>
</span></span><span style="display:flex;"><span> <span style="color:#008000">:initial-value</span> <span style="color:#800">nil</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#008000">with-temp-file</span> <span style="color:#19177c">log-name</span>
</span></span><span style="display:flex;"><span> (<span style="color:#00f">insert</span>
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">string-join</span>
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-filter</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">line</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">not</span> (<span style="color:#19177c">string-empty-p</span> <span style="color:#19177c">line</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">seq-sort-by</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">line</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">if-let</span> (<span style="color:#19177c">time</span> (<span style="color:#00f">car</span> (<span style="color:#19177c">split-string</span> <span style="color:#19177c">line</span> <span style="color:#ba2121">&#34;|&#34;</span>)))
</span></span><span style="display:flex;"><span> (<span style="color:#00f">string-to-number</span> <span style="color:#19177c">time</span>)
</span></span><span style="display:flex;"><span> <span style="color:#666">0</span>))
</span></span><span style="display:flex;"><span> <span style="color:#00f">#&#39;&lt;</span>
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">split-string</span>
</span></span><span style="display:flex;"><span> (<span style="color:#00f">mapconcat</span>
</span></span><span style="display:flex;"><span> (<span style="color:#008000">lambda</span> (<span style="color:#19177c">repo</span>)
</span></span><span style="display:flex;"><span> (<span style="color:#19177c">my/gource-prepare-log</span> <span style="color:#19177c">repo</span> <span style="color:#19177c">authors</span>))
</span></span><span style="display:flex;"><span> <span style="color:#19177c">repos</span> <span style="color:#ba2121">&#34;\n&#34;</span>)
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;\n&#34;</span>)))
</span></span><span style="display:flex;"><span> <span style="color:#ba2121">&#34;\n&#34;</span>)))))
</span></span></code></pre></div><p>This function extracts authors from each repository and merges the logs as required by gource, that is sorting the result by time in ascending order.</p>
<h2 id="using-the-function">Using the function</h2>
<p>To use the function above, mark the required repos in a dired buffer and run <code>M-x my/gource-dired-create-logs</code>. This also works nicely with <a href="https://github.com/Fuco1/dired-hacks">dired-subtree</a>, in case your repos are located in different folders.</p>
<p>The function will create a combined log file (by default <code>combined.log</code>). To visualize the log, run:</p>
<div class="highlight"><pre tabindex="0" style=";-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>gource &lt;log-file&gt; --user-image-dir &lt;path-to-gravatars&gt;
</span></span></code></pre></div><p>Check the <a href="https://github.com/acaudwell/Gource">README</a> for possible parameters, such as the speed of visualization, different elements, etc. That&rsquo;s it!</p>
<p>I thought about making something like a <a href="https://github.com/magit/transient">transient.el</a> wrapper around the <code>gource</code> command but figured it wasn&rsquo;t worth the effort for something that I run just a handful of times in a year.</p>
</div>
<div class="table-of-contents">
<div class="table-of-contents-text">
<b><a href="#">Table of Contents</a></b>
<nav id="TableOfContents">
<ul>
<li><a href="#gravatars">Gravatars</a></li>
<li><a href="#merging-authors">Merging authors</a></li>
<li><a href="#running-for-multiple-repos">Running for multiple repos</a></li>
<li><a href="#using-the-function">Using the function</a></li>
</ul>
</nav>
</div>
<a id="unhide-all-button" class="hidden">&lt;Expand&gt;</a>
<a id="hide-all-button" class="hidden">&lt;Collapse&gt;</a>
</div>
</div>
</div><div id="footer" class="mb-5">
<hr>
<div class="container text-center">
</div>
<div class="container text-center">
<a href="https://creativecommons.org/licenses/by/4.0/legalcode" title="Licensed under CC-BY 4.0"><small>Licensed under CC-BY 4.0</small></a>
|
<a href="https://plausible.io/" title="Uses Plausible Analytics"><small>Uses Plausible Analytics</small></a>
<br>
<a href="https://sqrtminusone.xyz/" title="Pavel Korytov, 2023"><small>Pavel Korytov, 2023</small></a>
</div>
</div>
</body>
</html>

View file

@ -66,6 +66,8 @@
<h1>Posts</h1>
<ul>
<li><a href="https://sqrtminusone.xyz/posts/2023-01-02-gource/">2023-01-02 | Running Gource with Emacs</a></li>
<li><a href="https://sqrtminusone.xyz/posts/2022-09-16-vosk/">2022-09-16 | Podcast transcripts with elfeed &amp; speech recognition engine</a></li>
<li><a href="https://sqrtminusone.xyz/posts/2022-05-09-pdf/">2022-05-10 | Extending elfeed with PDF viewer and subtitles fetcher</a></li>

View file

@ -6,7 +6,257 @@
<description>Recent content in Posts on SqrtMinusOne</description>
<generator>Hugo -- gohugo.io</generator>
<language>en-us</language>
<lastBuildDate>Fri, 16 Sep 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/posts/index.xml" rel="self" type="application/rss+xml" />
<lastBuildDate>Mon, 02 Jan 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/posts/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Running Gource with Emacs</title>
<link>https://sqrtminusone.xyz/posts/2023-01-02-gource/</link>
<pubDate>Mon, 02 Jan 2023 00:00:00 +0000</pubDate>
<guid>https://sqrtminusone.xyz/posts/2023-01-02-gource/</guid>
<content type="html">
&lt;figure&gt;&lt;img src=&#34;https://sqrtminusone.xyz/images/gource/gource.png&#34;/&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;a href=&#34;https://gource.io/&#34;&gt;Gource&lt;/a&gt; is a program that draws an animated graph of users changing the repository over time.&lt;/p&gt;
&lt;p&gt;Although it can work without extra effort (just run &lt;code&gt;gource&lt;/code&gt; in a &lt;a href=&#34;https://git-scm.com/&#34;&gt;git&lt;/a&gt; repo), there are some tweaks that can be done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gource supports using custom pictures for users. &lt;a href=&#34;https://en.gravatar.com/&#34;&gt;Gravatar&lt;/a&gt; is an obvious place to get these.&lt;/li&gt;
&lt;li&gt;Occasionally, the same people have different names and/or emails in history.&lt;br /&gt;
It may happen when people use forges like &lt;a href=&#34;https://gitlab.com/&#34;&gt;GitLab&lt;/a&gt; or just have different settings on different machines. It would be nice to merge these names.&lt;/li&gt;
&lt;li&gt;Visualizing the history of multiple repositories (e.g. frontend and backend) requires combining multiple gource logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, why not try doing that with Emacs?&lt;/p&gt;
&lt;h2 id=&#34;gravatars&#34;&gt;Gravatars&lt;/h2&gt;
&lt;p&gt;Much to my surprise, Emacs turned out to have a built-in package called &lt;a href=&#34;https://github.com/emacs-mirror/emacs/blob/master/lisp/image/gravatar.el&#34;&gt;gravatar.el&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, let&amp;rsquo;s make a function to retrieve a gravatar and save it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-retrieve-sync&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Get gravatar for EMAIL and save it to FILE-NAME.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;gravatar-default-image&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;identicon&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;gravatar-size&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;coding-system-for-write&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;binary&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;write-region-annotate-functions&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;write-region-post-annotation-function&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;write-region&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;image-property&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;gravatar-retrieve-synchronously&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;) &lt;span style=&#34;color:#008000&#34;&gt;:data&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:silent&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To use these images, we need to save them to some folder and use usernames as file names. The folder:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;setq&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-folder&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;/home/pavel/.cache/gravatars/&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the function that downloads a gravatar if necessary:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-save&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Download gravatar for EMAIL.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;AUTHOR is the username.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-folder&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;.png&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;mkdir&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-folder&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;t&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;unless&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;file-exists-p&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Fetching gravatar for %s (%s)&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-retrieve-sync&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;file-name&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;merging-authors&#34;&gt;Merging authors&lt;/h2&gt;
&lt;p&gt;Now to merging authors.&lt;/p&gt;
&lt;p&gt;Gource itself uses only usernames (without emails), but we can use &lt;code&gt;git log&lt;/code&gt; to get both. The required information can be extracted like that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git log --pretty&lt;span style=&#34;color:#666&#34;&gt;=&lt;/span&gt;format:&lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;%ae|%an&amp;#34;&lt;/span&gt; | sort | uniq -c | sed &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;s/^[ \t]*//;s/ /|/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output is a list of pipe-separated strings, where the values are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Number of occurrences for this combination of username and email&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Username&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, that part would have to be changed appropriately for other version control systems if you happen to use one.&lt;/p&gt;
&lt;p&gt;So, below is one hell of a function that wraps this command and tries to merge emails and usernames belonging to one author:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/git-get-authors&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#008000&#34;&gt;&amp;amp;optional&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors-init&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Extract and merge all combinations of authors &amp;amp; emails from REPO.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;REPO is the path to a git repository.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;AUTHORS-INIT is the previous output of &lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;`my/git-get-authors&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;. It can
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;be used to extract that information from multiple repositories.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;The output is a list of alists with following keys:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;- emails: list of (&amp;lt;email&amp;gt; . &amp;lt;count&amp;gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;- authors: list of (&amp;lt;username&amp;gt; . &amp;lt;count&amp;gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;- email: the most popular email
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;- author: the most popular username
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;I.e. one alist is all emails and usernames of one author.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let*&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;default-directory&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;data&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;shell-command-to-string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;git log --pretty=format:\&amp;#34;%ae|%an\&amp;#34; | sort | uniq -c | sed \&amp;#34;s/^[ \t]*//;s/ /|/\&amp;#34;&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-loop&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;string&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;length&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;string&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)) &lt;span style=&#34;color:#666&#34;&gt;3&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;collect&lt;/span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;string&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#666&#34;&gt;`&lt;/span&gt;((&lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;string-to-number&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;downcase&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;(&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)))))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;mapcar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setf&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cl-reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;cdr&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;) (&lt;span style=&#34;color:#00f&#34;&gt;cdr&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:initial-value&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;#39;&lt;/span&gt;(&lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;-1&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setf&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cl-reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;cdr&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;) (&lt;span style=&#34;color:#00f&#34;&gt;cdr&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:initial-value&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;&amp;#39;&lt;/span&gt;(&lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;-1&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cl-reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;val&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let*&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;val&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;val&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;count&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;val&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;seq-find&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;or&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;cand&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;progn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-incf&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;push&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;cons&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;) (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;emails&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-incf&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;string-equal&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;push&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;cons&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;) (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setq&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;saved-value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;push&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;`&lt;/span&gt;((&lt;span style=&#34;color:#19177c&#34;&gt;emails&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; ((&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;email&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; ((&lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;,&lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;count&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:initial-value&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors-init&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Despite the probable we-enjoy-typing-ness of the implementation, it&amp;rsquo;s actually pretty simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The output of &lt;code&gt;git log&lt;/code&gt; is parsed into a list of alists with &lt;code&gt;count&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt; and &lt;code&gt;author&lt;/code&gt; as keys.&lt;/li&gt;
&lt;li&gt;This list is reduced by &lt;code&gt;cl-reduce&lt;/code&gt; into a list of alists with &lt;code&gt;emails&lt;/code&gt; and &lt;code&gt;authors&lt;/code&gt; as keys and the respective counts as values, e.g. &lt;code&gt;((&amp;lt;email-1&amp;gt; . 1) (&amp;lt;email-2&amp;gt; . 3))&lt;/code&gt;.&lt;br /&gt;
I&amp;rsquo;ve seen a couple of cases where people would swap their username and email (lol), so &lt;code&gt;seq-find&lt;/code&gt; also looks for an email in the list of authors and vice versa.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;mapcar&lt;/code&gt; call determines the most popular email and username for each authors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output is another list of alists, now with the following keys:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;emails&lt;/code&gt; - list of elements like &lt;code&gt;(&amp;lt;email&amp;gt; . &amp;lt;count&amp;gt;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;authors&lt;/code&gt; - list of elements like &lt;code&gt;(&amp;lt;author-name&amp;gt; . &amp;lt;count&amp;gt;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;email&lt;/code&gt; - the most popular email&lt;/li&gt;
&lt;li&gt;&lt;code&gt;author&lt;/code&gt; - the most popular username.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;running-for-multiple-repos&#34;&gt;Running for multiple repos&lt;/h2&gt;
&lt;p&gt;This section was mostly informed by &lt;a href=&#34;https://github.com/acaudwell/Gource/wiki/Visualizing-Multiple-Repositories&#34;&gt;this page&lt;/a&gt; in the &lt;a href=&#34;https://github.com/acaudwell/Gource/wiki&#34;&gt;gource wiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I said above, by default &lt;code&gt;gource&lt;/code&gt; just creates a visualization for the current repo. To change something in it, we need to invoke the program like that: &lt;code&gt;gource --output-custom-log PATH&lt;/code&gt;, where &lt;code&gt;PATH&lt;/code&gt; is either the path to the log file or &lt;code&gt;-&lt;/code&gt; for stdout.&lt;/p&gt;
&lt;p&gt;The log consists of lines of pipe-separated strings, e.g.:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1600769568|dsofronov|A|/studentor/.dockerignore
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1600769568|dsofronov|A|/studentor/.editorconfig
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1600769568|dsofronov|A|/studentor/.flake8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1600769568|dsofronov|A|/studentor/.gitignore
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;where the values of one line are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UNIX timestamp&lt;/li&gt;
&lt;li&gt;Author name&lt;/li&gt;
&lt;li&gt;&lt;code&gt;A&lt;/code&gt; for add, &lt;code&gt;M&lt;/code&gt; for modify, and &lt;code&gt;D&lt;/code&gt; for delete&lt;/li&gt;
&lt;li&gt;Path to file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The file has to be sorted by the timestamp in ascending order.&lt;/p&gt;
&lt;p&gt;So, the function that prepares the log for one repository:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gource-prepare-log&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Create gource log string for REPO.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;AUTHORS is the output of &lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;`my/git-get-authors&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#00f&#34;&gt;log&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;shell-command-to-string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;gource --output-custom-log - &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;authors-mapping&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;make-hash-table&lt;/span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:test&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;equal&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;prefix&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;file-name-base&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-loop&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author-datum&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author-datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;do&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;my/gravatar-save&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;email&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author-datum&lt;/span&gt;) &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;do&lt;/span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-loop&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;other-author&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;alist-get&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;&amp;#39;authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;author-datum&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;unless&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;string-equal&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;other-author&lt;/span&gt;) &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;do&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;puthash&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;other-author&lt;/span&gt;) &lt;span style=&#34;color:#19177c&#34;&gt;author&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors-mapping&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;cl-loop&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;log&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;when&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;&amp;gt;&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;) &lt;span style=&#34;color:#666&#34;&gt;3&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;when-let&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;mapped-author&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;gethash&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors-mapping&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setf&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;) &lt;span style=&#34;color:#19177c&#34;&gt;mapped-author&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;setf&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;/&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;prefix&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;nth&lt;/span&gt; &lt;span style=&#34;color:#666&#34;&gt;3&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt;))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;string-join&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;fragments&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#00f&#34;&gt;concat&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downloads a gravatar for each author&lt;/li&gt;
&lt;li&gt;Replaces all usernames of one author with the most frequent one&lt;/li&gt;
&lt;li&gt;Prepends the file path with the repository name.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output is a string in the gource log format as described above.&lt;/p&gt;
&lt;p&gt;Finally, as we need to invoke all of this for multiple repositories, why not do that with &lt;a href=&#34;https://www.gnu.org/software/emacs/manual/html_node/emacs/Dired.html&#34;&gt;dired&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-emacs-lisp&#34; data-lang=&#34;emacs-lisp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(&lt;span style=&#34;color:#008000&#34;&gt;defun&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;my/gource-dired-create-logs&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;repos&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;log-name&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Create combined gource log for REPOS.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;REPOS is a list of strings, where a string is a path to a git repo.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;LOG-NAME is the path to the resulting log file.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;This function is meant to be invoked from &lt;/span&gt;&lt;span style=&#34;color:#19177c&#34;&gt;`dired&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;, where the required
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ba2121&#34;&gt;repositories are marked.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;interactive&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;list&lt;/span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;or&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;dired-get-marked-files&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;file-directory-p&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#d2413a;font-weight:bold&#34;&gt;user-error&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Select at least one directory&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;read-file-name&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;Log file name: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;combined.log&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;let&lt;/span&gt; ((&lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;cl-reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;my/git-get-authors&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;acc&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repos&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#008000&#34;&gt;:initial-value&lt;/span&gt; &lt;span style=&#34;color:#800&#34;&gt;nil&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;with-temp-file&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;log-name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;insert&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;string-join&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;seq-filter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;not&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;string-empty-p&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;seq-sort-by&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;if-let&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;time&lt;/span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;car&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;|&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;string-to-number&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;time&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#666&#34;&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#00f&#34;&gt;#&amp;#39;&amp;lt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;split-string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#00f&#34;&gt;mapconcat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#008000&#34;&gt;lambda&lt;/span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (&lt;span style=&#34;color:#19177c&#34;&gt;my/gource-prepare-log&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repo&lt;/span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;authors&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#19177c&#34;&gt;repos&lt;/span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; &lt;span style=&#34;color:#ba2121&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;)))))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function extracts authors from each repository and merges the logs as required by gource, that is sorting the result by time in ascending order.&lt;/p&gt;
&lt;h2 id=&#34;using-the-function&#34;&gt;Using the function&lt;/h2&gt;
&lt;p&gt;To use the function above, mark the required repos in a dired buffer and run &lt;code&gt;M-x my/gource-dired-create-logs&lt;/code&gt;. This also works nicely with &lt;a href=&#34;https://github.com/Fuco1/dired-hacks&#34;&gt;dired-subtree&lt;/a&gt;, in case your repos are located in different folders.&lt;/p&gt;
&lt;p&gt;The function will create a combined log file (by default &lt;code&gt;combined.log&lt;/code&gt;). To visualize the log, run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gource &amp;lt;log-file&amp;gt; --user-image-dir &amp;lt;path-to-gravatars&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check the &lt;a href=&#34;https://github.com/acaudwell/Gource&#34;&gt;README&lt;/a&gt; for possible parameters, such as the speed of visualization, different elements, etc. That&amp;rsquo;s it!&lt;/p&gt;
&lt;p&gt;I thought about making something like a &lt;a href=&#34;https://github.com/magit/transient&#34;&gt;transient.el&lt;/a&gt; wrapper around the &lt;code&gt;gource&lt;/code&gt; command but figured it wasn&amp;rsquo;t worth the effort for something that I run just a handful of times in a year.&lt;/p&gt;
</content>
</item>
<item>
<title>Podcast transcripts with elfeed &amp; speech recognition engine</title>
<link>https://sqrtminusone.xyz/posts/2022-09-16-vosk/</link>

View file

@ -2,23 +2,26 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://sqrtminusone.xyz/tags/emacs/</loc>
<lastmod>2023-01-02T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/</loc>
<lastmod>2023-01-02T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/posts/</loc>
<lastmod>2023-01-02T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/posts/2023-01-02-gource/</loc>
<lastmod>2023-01-02T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/tags/</loc>
<lastmod>2023-01-02T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/tags/elfeed/</loc>
<lastmod>2022-09-16T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/tags/emacs/</loc>
<lastmod>2022-09-16T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/</loc>
<lastmod>2022-09-16T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/posts/2022-09-16-vosk/</loc>
<lastmod>2022-09-16T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/posts/</loc>
<lastmod>2022-09-16T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/tags/</loc>
<lastmod>2022-09-16T00:00:00+00:00</lastmod>
</url><url>
<loc>https://sqrtminusone.xyz/posts/2022-05-09-pdf/</loc>
<lastmod>2022-05-10T00:00:00+00:00</lastmod>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

View file

@ -66,6 +66,8 @@
<h1>emacs</h1>
<ul>
<li><a href="https://sqrtminusone.xyz/posts/2023-01-02-gource/">2023-01-02 | Running Gource with Emacs</a></li>
<li><a href="https://sqrtminusone.xyz/posts/2022-09-16-vosk/">2022-09-16 | Podcast transcripts with elfeed &amp; speech recognition engine</a></li>
<li><a href="https://sqrtminusone.xyz/posts/2022-05-09-pdf/">2022-05-10 | Extending elfeed with PDF viewer and subtitles fetcher</a></li>

View file

@ -6,7 +6,19 @@
<description>Recent content in emacs on SqrtMinusOne</description>
<generator>Hugo -- gohugo.io</generator>
<language>en-us</language>
<lastBuildDate>Fri, 16 Sep 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/tags/emacs/index.xml" rel="self" type="application/rss+xml" />
<lastBuildDate>Mon, 02 Jan 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/tags/emacs/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>Running Gource with Emacs</title>
<link>https://sqrtminusone.xyz/posts/2023-01-02-gource/</link>
<pubDate>Mon, 02 Jan 2023 00:00:00 +0000</pubDate>
<guid>https://sqrtminusone.xyz/posts/2023-01-02-gource/</guid>
<description>Gource is a program that draws an animated graph of users changing the repository over time.
Although it can work without extra effort (just run gource in a git repo), there are some tweaks that can be done:
Gource supports using custom pictures for users. Gravatar is an obvious place to get these. Occasionally, the same people have different names and/or emails in history.
It may happen when people use forges like GitLab or just have different settings on different machines.</description>
</item>
<item>
<title>Podcast transcripts with elfeed &amp; speech recognition engine</title>
<link>https://sqrtminusone.xyz/posts/2022-09-16-vosk/</link>

View file

@ -66,9 +66,9 @@
<h1>Tags</h1>
<ul>
<li><a href="https://sqrtminusone.xyz/tags/elfeed/">2022-09-16 | elfeed</a></li>
<li><a href="https://sqrtminusone.xyz/tags/emacs/">2023-01-02 | emacs</a></li>
<li><a href="https://sqrtminusone.xyz/tags/emacs/">2022-09-16 | emacs</a></li>
<li><a href="https://sqrtminusone.xyz/tags/elfeed/">2022-09-16 | elfeed</a></li>
<li><a href="https://sqrtminusone.xyz/tags/org-mode/">2022-05-10 | org-mode</a></li>

View file

@ -6,7 +6,16 @@
<description>Recent content in Tags on SqrtMinusOne</description>
<generator>Hugo -- gohugo.io</generator>
<language>en-us</language>
<lastBuildDate>Fri, 16 Sep 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/tags/index.xml" rel="self" type="application/rss+xml" />
<lastBuildDate>Mon, 02 Jan 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://sqrtminusone.xyz/tags/index.xml" rel="self" type="application/rss+xml" />
<item>
<title>emacs</title>
<link>https://sqrtminusone.xyz/tags/emacs/</link>
<pubDate>Mon, 02 Jan 2023 00:00:00 +0000</pubDate>
<guid>https://sqrtminusone.xyz/tags/emacs/</guid>
<description></description>
</item>
<item>
<title>elfeed</title>
<link>https://sqrtminusone.xyz/tags/elfeed/</link>
@ -16,15 +25,6 @@
<description></description>
</item>
<item>
<title>emacs</title>
<link>https://sqrtminusone.xyz/tags/emacs/</link>
<pubDate>Fri, 16 Sep 2022 00:00:00 +0000</pubDate>
<guid>https://sqrtminusone.xyz/tags/emacs/</guid>
<description></description>
</item>
<item>
<title>org-mode</title>
<link>https://sqrtminusone.xyz/tags/org-mode/</link>