<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"><id>https://blog.tusooa.xyz</id><title>code - 何事西风不待人</title><link href="https://blog.tusooa.xyz"/><subtitle>迷糊萝莉</subtitle><updated>2024-11-07T19:00:00.000Z</updated><entry><id>https://blog.tusooa.xyz/2024/11/07/From-hexo-to-astro/</id><title>From hexo to astro</title><link rel="alternate" href="https://blog.tusooa.xyz/2024/11/07/From-hexo-to-astro/"/><published>2024-11-07T19:00:00.000Z</published><content type="html">&lt;div&gt; &lt;p&gt;I have been using &lt;a href=&quot;https://astro.build&quot;&gt;astro&lt;/a&gt; for this blog for quite a long time now.
I will write here how I migrated from &lt;a href=&quot;https://hexo.io&quot;&gt;hexo&lt;/a&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我现在在这个 blog 用 &lt;a href=&quot;https://astro.build&quot;&gt;astro&lt;/a&gt; 已经挺长时间了。
来写写我怎么从 &lt;a href=&quot;https://hexo.io&quot;&gt;hexo&lt;/a&gt; 迁移的。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;This will not be an introduction to astro. Instead, it covers some major challenges
I faced when I tried to implement in astro the same thing I have in my original hexo blog,
and how I resolved them.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;本文不是对 astro 的介绍。相反地，它包括了一些我试图在 astro 里实现我原来的 hexo
博客里面有的东西的时候遇到的挑战，跟我是怎么解决的。&lt;/p&gt; &lt;/div&gt;
&lt;a&gt;&lt;/a&gt;
&lt;h2&gt;&lt;a href=&quot;#The-start起始&quot;&gt;&lt;span&gt; &lt;span&gt; The start &lt;/span&gt;&lt;span&gt; 起始 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;I used to have a couple of blogs before this one a long time ago using jekyll and wordpress,
but this hexo blog was started in 2019, from the commit log, and I started using &lt;a href=&quot;https://github.com/next-theme/hexo-theme-next&quot;&gt;NexT theme&lt;/a&gt;
&lt;sup&gt;&lt;a href=&quot;#user-content-fn-next&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; in 2020. I added &lt;a href=&quot;/2020/10/10/Some-multilingual-hacks-on-Hexo/&quot;&gt;multi-language support&lt;/a&gt; two months later, and it ran for a couple
of years, till the end of 2023, when I read Beiyan Yunyi&apos;s &lt;a href=&quot;https://blog.yunyi.beiyan.us/posts/removeHexo/&quot;&gt;blog post about astro&lt;/a&gt;.
She describes why hexo is frowned upon, and why astro is preferred. Indeed, astro solves some problems
I was facing at that time, for example, I did &lt;a href=&quot;https://github.com/next-theme/hexo-theme-next/pull/243/files#r662300271&quot;&gt;break the theme&lt;/a&gt; by accidentally
removing a closing tag while dealing with conflicts. After researching on it
for quite a while, I finally decided to switch to astro.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我很久以前有过一些用 jekyll 跟 wordpress 的博客，但是根据提交记录看，
这个 hexo 的博客是 2019年开始的。我在 2020年开始用 &lt;a href=&quot;https://github.com/next-theme/hexo-theme-next&quot;&gt;NexT主题&lt;/a&gt; &lt;sup&gt;&lt;a href=&quot;#user-content-fn-next&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;。
两个月之后，我添加了&lt;a href=&quot;/2020/10/10/Some-multilingual-hacks-on-Hexo/&quot;&gt;多语言支持&lt;/a&gt;，运行了好几年，
直到 2023年底，我读到北雁云依的&lt;a href=&quot;https://blog.yunyi.beiyan.us/posts/removeHexo/&quot;&gt;关于astro的博客文章&lt;/a&gt;。
她描述了为什么 hexo 让人皱眉，为什么 astro 更好。确实，astro 解决了一些我当时面临的问题，
比如，我之前&lt;a href=&quot;https://github.com/next-theme/hexo-theme-next/pull/243/files#r662300271&quot;&gt;搞坏了主题&lt;/a&gt;，因为我在解决冲突的时候不小心删掉了一个结束标签。
研究了好一会儿之后，我终于决定换到 astro 了。&lt;/p&gt; &lt;/div&gt;
&lt;h2&gt;&lt;a href=&quot;#Goals-for-the-migration迁移的目标&quot;&gt;&lt;span&gt; &lt;span&gt; Goals for the migration &lt;/span&gt;&lt;span&gt; 迁移的目标 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;I made several goals for the migration, including:&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我指定了几个迁移的目标，包括：&lt;/p&gt; &lt;/div&gt;
&lt;ul&gt;&lt;li&gt;&lt;span&gt; &lt;span&gt; Multi-language support is retained. &lt;/span&gt;&lt;span&gt; 多语言支持要保留。 &lt;/span&gt; &lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt; &lt;span&gt; RSS should work, including tag-specific RSS. &lt;/span&gt;&lt;span&gt; RSS 得工作，包括标签的 RSS。 &lt;/span&gt; &lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt; &lt;span&gt; Link-to-link compatibility, including hash routing. &lt;/span&gt;&lt;span&gt; 链接到链接的兼容性，包括 # 后面的东西。 &lt;/span&gt; &lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt; &lt;span&gt; Able to work without JavaScript. &lt;/span&gt;&lt;span&gt; 没得 JavaScript 也要能工作。 &lt;/span&gt; &lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span&gt; &lt;span&gt; Responsive and convergent. &lt;/span&gt;&lt;span&gt; 要响应式，也得各平台同一。 &lt;/span&gt; &lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;&lt;a href=&quot;#Multi-language-support多语言支持&quot;&gt;&lt;span&gt; &lt;span&gt; Multi-language support &lt;/span&gt;&lt;span&gt; 多语言支持 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;This is actually easier than hexo. Just define components. Add proper CSS to it.
No more remembering the template language. Just use MDX/JSX, and it will work.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;这个其实比 hexo 更简单。定义组件就行了。添加对的 CSS。
不用再去记模板语言了。就用 MDX 跟 JSX，就能运作了。&lt;/p&gt; &lt;/div&gt;
&lt;h2&gt;&lt;a href=&quot;#Link-compatibility链接兼容性&quot;&gt;&lt;span&gt; &lt;span&gt; Link compatibility &lt;/span&gt;&lt;span&gt; 链接兼容性 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;I want to explain why even I want this. Because the website has been there for quite some time,
and there are already links in the wild leading to existing pages of the blog, and I from time
to time link to my &lt;a href=&quot;/poems/&quot;&gt;poems page&lt;/a&gt;. That page is very long, and each heading links to one poem.
It would be very frustrated if one clicks on the link, but does not get which poem I refer to.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我想解释一下为什么我要这个。因为这个网站在那块好久了，
已经有好多野生的链接指向博客现有的页面了，而且我时不时链接到我的&lt;a href=&quot;/poems/&quot;&gt;诗的页面&lt;/a&gt;。
那个页面非常长，每一个标题指向一首诗。
要是有人点开链接，但又不晓得我指的是哪首诗，那会非常抓狂。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;To achieve page link compatibility, I structure &lt;code&gt;/src/pages&lt;/code&gt; the way exactly same as the original
blog will do (either hexo or NexT theme), so &lt;code&gt;/&lt;/code&gt; for article listing with partial content,
&lt;code&gt;/archives/&lt;/code&gt; for article listing with only titles, appending &lt;code&gt;pages/X/&lt;/code&gt; for pages after the first,
&lt;code&gt;/Y/M/D/filename&lt;/code&gt; for individual posts, and three individual pages: &lt;code&gt;/about/&lt;/code&gt;, &lt;code&gt;/poems/&lt;/code&gt; and &lt;code&gt;/tags/&lt;/code&gt;.
List of tag-associated posts is under &lt;code&gt;/tags/X/&lt;/code&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;为了达成页面链接的兼容性，我把 &lt;code&gt;/src/pages&lt;/code&gt; 构建成了跟原来的博客一模一样的方式（要么是 hexo 的，要么是
NexT 主题的），即：&lt;code&gt;/&lt;/code&gt; 是带片段的文章列表，&lt;code&gt;/archives/&lt;/code&gt; 是只有标题的文章列表，第一页之后加上 &lt;code&gt;pages/X/&lt;/code&gt;，
&lt;code&gt;/Y/M/D/filename&lt;/code&gt; 是一个一个的文章，还有三个单独的页面：&lt;code&gt;/about/&lt;/code&gt;，&lt;code&gt;/poems/&lt;/code&gt; 跟 &lt;code&gt;/tags/&lt;/code&gt;。
有某个标签的文章的列表在 &lt;code&gt;/tags/X/&lt;/code&gt; 底下。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;The fact that the first page has no &lt;code&gt;pages/X/&lt;/code&gt; suffix but all others do means that I cannot directly
use astro&apos;s &lt;a href=&quot;https://docs.astro.build/en/guides/routing/#pagination&quot;&gt;built-in pagination support&lt;/a&gt;, because apparently it will generate
&lt;code&gt;pages/1/&lt;/code&gt; for the first page, which is not what I want.
I ended up writing my &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/pages/helpers/pagination.js&quot;&gt;own pagination helpers&lt;/a&gt;.
The main point is to use &lt;code&gt;[...page].astro&lt;/code&gt; as the file name (not &lt;code&gt;[page].astro&lt;/code&gt;),
and use &lt;code&gt;undefined&lt;/code&gt; for &lt;code&gt;params.page&lt;/code&gt; of the first page and &lt;code&gt;&quot;pages/X/&quot;&lt;/code&gt; for the others.
The &lt;code&gt;...&lt;/code&gt; allows you to have &lt;code&gt;/&lt;/code&gt; in the params.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;由于 &lt;code&gt;pages/X/&lt;/code&gt; 在第一页没有，而后面的页都有，我没得办法直接用 astro &lt;a href=&quot;https://docs.astro.build/en/guides/routing/#pagination&quot;&gt;内建的分页支持&lt;/a&gt;，
因为显然它就给第一页生成 &lt;code&gt;pages/1/&lt;/code&gt;，这就不是我想要的了。
我最后写了自己的&lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/pages/helpers/pagination.js&quot;&gt;分页助手&lt;/a&gt;。主要是用 &lt;code&gt;[...page].astro&lt;/code&gt; 作为文件名（不是 &lt;code&gt;[page].astro&lt;/code&gt;），
然后把第一页的 &lt;code&gt;params.page&lt;/code&gt; 设成 &lt;code&gt;undefined&lt;/code&gt;，别的页的设成 &lt;code&gt;&quot;pages/X/&quot;&lt;/code&gt;。
&lt;code&gt;...&lt;/code&gt; 允许你在 params 里头有 &lt;code&gt;/&lt;/code&gt;。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;For anchor compatibiliy, I directly used the slugize function by hexo.
Plugging it as a &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/plugins/rehypeSlugHexoish.js&quot;&gt;rehype plugin&lt;/a&gt; I made by modifying the official rehype-slug, it just works.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;为了锚点的兼容性，我直接用了 hexo 的 slugize 函数。
把它作为一个 &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/plugins/rehypeSlugHexoish.js&quot;&gt;rehype 插件&lt;/a&gt;加入进去（我自己根据官方的 rehype-slug 改的），它就能用了。&lt;/p&gt; &lt;/div&gt;
&lt;h2&gt;&lt;a href=&quot;#Index-with-partial-content有部分内容的索引&quot;&gt;&lt;span&gt; &lt;span&gt; Index with partial content &lt;/span&gt;&lt;span&gt; 有部分内容的索引 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;In hexo, the index page has partial content with a &quot;read more&quot; link, and this is achieved
through a comment &lt;code&gt;&amp;lt;!-- more --&amp;gt;&lt;/code&gt; in the post markdown. Two things are needed: first,
obtain the content before the &lt;code&gt;&amp;lt;!-- more --&amp;gt;&lt;/code&gt; comment, and render it on the page;
second, have an element in the rendered post html with the id &lt;code&gt;more&lt;/code&gt; at the place of the comment,
and make a link to &lt;code&gt;#more&lt;/code&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;在 hexo 里头，索引页有一部分内容，还有「阅读更多」的链接，这是通过文章的 markdown 里头的
&lt;code&gt;&amp;lt;!-- more --&amp;gt;&lt;/code&gt; 注释达成的。需要两件事：首先，要获取 &lt;code&gt;&amp;lt;!-- more --&amp;gt;&lt;/code&gt; 注释前头的内容，
并且渲染到页面高头；其次，在文章的 html 里头要有一个 id 是 &lt;code&gt;more&lt;/code&gt; 的项目，正正好好就在那个注释的地方，
然后做一个到 &lt;code&gt;#more&lt;/code&gt; 的链接。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;The first one is rather tricky, as it involves one level of indirectness.
astro&apos;s &lt;code&gt;getCollection&lt;/code&gt; gives you an array that you can call the &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/#rendering-content-to-html&quot;&gt;&lt;code&gt;render&lt;/code&gt; function&lt;/a&gt;
of the entries and get a astro component called &lt;code&gt;Content&lt;/code&gt;. Normally, we will use it
directly in the JSX as &lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;第一个挺难的，因为涉及一层间接的东西。
astro 的 &lt;code&gt;getCollection&lt;/code&gt; 给你一个数组，可以用元素的 &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/#rendering-content-to-html&quot;&gt;&lt;code&gt;render&lt;/code&gt; 函数&lt;/a&gt;来获得一个叫 &lt;code&gt;Content&lt;/code&gt;
的 astro 组件。一般来讲，我们会把它直接用在 JSX 里头，用 &lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt;。&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;Content&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; post&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Content&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;However, this will render the whole post, not allowing us to render only a &lt;em&gt;part&lt;/em&gt; of the post.
On the bright side, astro provides a function that can &lt;a href=&quot;https://docs.astro.build/en/reference/api-reference/#astroslotsrender&quot;&gt;render a &lt;em&gt;slot&lt;/em&gt; to html string&lt;/a&gt;.
This means we can first pass &lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt; as a slot of another astro component, and then
get the html string, and manipulate the string directly:&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;然而，这会渲染整个文章，不让我们只渲染文章的&lt;em&gt;一部分&lt;/em&gt;。
但好的地方是，astro 给了一个函数，可以&lt;a href=&quot;https://docs.astro.build/en/reference/api-reference/#astroslotsrender&quot;&gt;把一个 &lt;em&gt;slot&lt;/em&gt; 渲染成 html 字符串&lt;/a&gt;。
这就意味到可以先把 &lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt; 作为 slot 传给另一个 astro 组件，然后获取 html 字符串，
再直接操纵字符串：&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// PostExcerpt.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; PostExcerptImpl&lt;/span&gt;&lt;span&gt; from&lt;/span&gt;&lt;span&gt; &apos;./PostExcerptImpl.astro&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Astro&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;Content&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; post&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;PostExcerptImpl&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &amp;lt;&lt;/span&gt;&lt;span&gt;Content&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;PostExcerptImpl&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// PostExcerptImpl.astro&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; postHtml&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; Astro&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;slots&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;default&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; excerpt&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; getExcerpt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;postHtml&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Fragment&lt;/span&gt;&lt;span&gt; set:html&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;excerpt&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;(Read more | 阅读全文)&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;So, how to implement &lt;code&gt;getExcerpt&lt;/code&gt; here? We add a &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/plugins/rehypeAddMoreAnchor.js&quot;&gt;rehype plugin&lt;/a&gt;
that finds the &lt;code&gt;&amp;lt;!-- more --&amp;gt;&lt;/code&gt; comment (or &lt;code&gt;{/* more */}&lt;/code&gt; in MDX),
and converts it into &lt;code&gt;&amp;lt;a id=&quot;more&quot;&amp;gt;&amp;lt;/a&amp;gt;&lt;/code&gt; in html, and then find this exact string in html.
Everything before it is the excerpt we are looking for.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;那么，怎么实现 &lt;code&gt;getExcerpt&lt;/code&gt; 呢？添加一个 &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/plugins/rehypeAddMoreAnchor.js&quot;&gt;rehype 插件&lt;/a&gt;，寻找 &lt;code&gt;&amp;lt;!-- more --&amp;gt;&lt;/code&gt;
注释（或者，MDX 里头的 &lt;code&gt;{/* more */}&lt;/code&gt;），并且把它转换成 html 里的 &lt;code&gt;&amp;lt;a id=&quot;more&quot;&amp;gt;&amp;lt;/a&amp;gt;&lt;/code&gt;。再在 html
里头找这个字符串就好了。它前面的东西，就是我们要找的片段。&lt;/p&gt; &lt;/div&gt;
&lt;h2&gt;&lt;a href=&quot;#RSS&quot;&gt;RSS&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;astro has official support for RSS, but it is very limited. It does not by default
include post content, and it does not support injecting the post content directly
at all. Instead, it &lt;a href=&quot;https://docs.astro.build/en/guides/rss/#including-full-post-content&quot;&gt;recommends users to use &lt;em&gt;another&lt;/em&gt; markdown renderer&lt;/a&gt;
in the RSS. This means you cannot render MDX, JSX, or astro components into the RSS.
This is not acceptable for me, because what I want is to allow people read my blog
directly in the RSS reader.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;astro 官方支持 RSS，但非常有限。它默认不包括文章内容，而且也完全不支持直接插入文章内容。
相反地，它在 RSS 里&lt;a href=&quot;https://docs.astro.build/en/guides/rss/#including-full-post-content&quot;&gt;推荐用户用&lt;em&gt;另一个&lt;/em&gt; markdown 渲染器&lt;/a&gt;。
这就意味到你在 RSS 里头没得办法渲染 MDX，JSX，或者 astro 组件了。
这是不能接受的，因为我想要允许我博客的读者直接在 RSS 阅读器里看它。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;An apparent trick is to use what I have already used for the post excerpt: &lt;code&gt;post.render()&lt;/code&gt; and
&lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt;. However, this is only for astro components. Moreover, astro components can currently
be used to generate html outputs -- it adds the doctype header, making it unsuitable for xml outputs.
There is an &lt;a href=&quot;https://github.com/withastro/astro/issues/9489&quot;&gt;issue in astro&lt;/a&gt;, which can be eventually traced to this &lt;a href=&quot;https://github.com/withastro/roadmap/pull/916&quot;&gt;merge request
on Container API&lt;/a&gt;. At the time of writing this post, it is not yet available in astro.
There is also another person&apos;s work on &lt;a href=&quot;https://scottwillsey.com/rss-pt2/&quot;&gt;how to generate RSS for astro&lt;/a&gt;. I think I probably
saw it when I was adding RSS support for this blog, but I eventually took a slightly different approach.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;一个显而易见的技巧是，用我之前用来处理文章节选的 &lt;code&gt;post.render()&lt;/code&gt; 和 &lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt;。但是，这只能在
astro 组件里用。更何况，astro 组件只能生成 html 输出——它会添加 doctype 头部，所以是不适用于 xml 输出的。
&lt;a href=&quot;https://github.com/withastro/astro/issues/9489&quot;&gt;astro 里有一个 issue&lt;/a&gt; 相关，最终可以追溯到这个&lt;a href=&quot;https://github.com/withastro/roadmap/pull/916&quot;&gt;关于 Container API 的合并请求&lt;/a&gt;。
在写这篇文章的时候，在 astro 里面还并不可用。又有另外一个人做了一些关于&lt;a href=&quot;https://scottwillsey.com/rss-pt2/&quot;&gt;怎么给 astro 生成 RSS&lt;/a&gt; 的工作。
我觉得我当年给这个博客添加 RSS 支持的时候可能看过它，但是我最终采取了一个稍微不一样的方法。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;First, to obtain the post content as html, I added a page in the &lt;code&gt;src/pages/atomRender&lt;/code&gt; directory.
All it does is to get all posts, and &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/pages/atomRender/posts/%5B...slug%5D.astro&quot;&gt;render just the content&lt;/a&gt; of those posts as html. In this way,
the content is available as local files.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;首先，要以 html 的的形式获得文章内容，我在 &lt;code&gt;src/pages/atomRender&lt;/code&gt; 目录底下添加了一个页面。
它做的所有事情就是获得所有的文章，然后&lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/pages/atomRender/posts/%5B...slug%5D.astro&quot;&gt;只把这些文章内容渲染&lt;/a&gt;成 html。
这样，内容就以本地文件的形式可用了。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;Second, we generate some metadata for the RSS builder. The metadata should at least include
which posts should be rendered, and the title, date, and anything that you want to include in the
RSS. This is achieved by creating a file called &lt;code&gt;X.js&lt;/code&gt;, where &lt;code&gt;X&lt;/code&gt; is the output file name,
and exporting a function called &lt;code&gt;GET&lt;/code&gt; from the js file. This, of course, does not include
the actual rendered post content, because it is not an astro component, and the &lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt;
is only available in astro components.
I created two of them, one &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/pages/atomRender/metadata.json.js&quot;&gt;for the whole blog&lt;/a&gt; and one &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/pages/atomRender/tags/%5Btag%5D/metadata.json.js&quot;&gt;for tags&lt;/a&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;其次，给 RSS 构建器生成一些元数据。元数据要至少包括有哪些文章需要被渲染，跟它的标题，日期，
还有你想要在 RSS 里放的任何东西。可以创建一个叫 &lt;code&gt;X.js&lt;/code&gt; 的文件，其中 &lt;code&gt;X&lt;/code&gt; 是输出的文件名，
然后从这个 js 文件里导出一个叫 &lt;code&gt;GET&lt;/code&gt; 的函数。当然了，这个文件当然不会包括文章的实际内容，
因为它不是 astro 组件，而只有 astro 组件里才能用 &lt;code&gt;&amp;lt;Content /&amp;gt;&lt;/code&gt;。
我创建了两个这样的文件，一个&lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/pages/atomRender/metadata.json.js&quot;&gt;给整个博客&lt;/a&gt;，另一个[给标签][matadatajsonTag]。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;The final step is to combine the metadata and the actual post content after they are all
generated. My approach is to add an astro &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/plugins/buildFeed.js&quot;&gt;plugin that builds the RSS&lt;/a&gt; as a hook.
An apparent disadvantage is that it is not built when we are running the local dev server,
and it is not suitable if your site is not statically generated. But for my purpose, it is sufficient.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;最后一步就是在把元数据跟实际文章内容都生成了之后，把它们给组合起来了。
我的方法是添加一个 astro &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/plugins/buildFeed.js&quot;&gt;插件来用钩子构建 RSS&lt;/a&gt;。
一个显然的劣势是，在运行本地开发服务器的时候，RSS 是没得被构建的，
而且如果你的网站不是静态生成的，那它就没得办法用。但是对我来讲，就够了。&lt;/p&gt; &lt;/div&gt;
&lt;h2&gt;&lt;a href=&quot;#Responsive-design-without-JavaScript-requirement不强制要-JavaScript-的响应式设计&quot;&gt;&lt;span&gt; &lt;span&gt; Responsive design without JavaScript requirement &lt;/span&gt;&lt;span&gt; 不强制要 JavaScript 的响应式设计 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;The NexT theme of hexo does not actually meet this requirement, because on mobile,
the sidebar is either completely hidden (not convergent) or is only activable via JavaScript.
I want my site to function even without JavaScript (it can still contain JavaScript, but
all functionalities should still be available when it is not available).&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;hexo 的 NexT 主题实际上不满足这个要求，因为在移动端，侧边栏要么是完全隐藏的（各平台不同一），
要么就只能经由 JavaScript 启用。我希望我的站点就算没有 JavaScript 也能工作（它还是可以包括
JavaScript，但所有功能在 JavaScript 不可用的时候仍然应该可用）。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;There are three responsive parts on the site: the navigation bar, the site info panel,
and the table of contents for each page. The &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/styles/global.css#L160&quot;&gt;site info panel&lt;/a&gt; is shown as a sidebar when
the page is wide enough, and at the bottom otherwise. &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/components/Header.astro&quot;&gt;The navigation bar&lt;/a&gt; is
shown horizontally when the page is wide enough, and vertically as a drop-down menu when it is not wide enough.
When JavaScript is available, the drop-down menu is hidden at page load, and shown when the activating
button is clicked. When JavaScript is not available, it is always displayed. The &lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/layouts/BlogPost.astro#L22&quot;&gt;table of contents&lt;/a&gt;
is similar, displaying in the sidebar when the page is wide enough. Otherwise, it is displayed at the top of the
post, collapsed by default if JavaScript is available, and always expanded if JavaScript is not available.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;站点上有三个响应式的部分：导航栏，站点信息面板，和每页的目录。
&lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/styles/global.css#L160&quot;&gt;站点信息面板&lt;/a&gt;在页面够宽的时候显示为侧边栏，否则就显示在最底部。
&lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/components/Header.astro&quot;&gt;导航栏&lt;/a&gt;要是页面够宽，就显示为横向的列表，不然就显示成竖的下拉菜单。
当 JavaScript 可用的时候，下拉菜单在页面加载的时候是隐藏的，按下激活按钮就会显示。
当 JavaScript 不可用的时候，它就一直都显示了。
&lt;a href=&quot;https://lily-is.land/tusooa/tusooa.pg.kazv.moe/-/blob/81076f111e8edd94ecea6dd90e2a8e1abfa7ac5c/astro/src/layouts/BlogPost.astro#L22&quot;&gt;目录&lt;/a&gt;是类似的，在页面够宽的时候显示在侧边栏里。不然，就显示在文章的最上面，
要是 JavaScript 可用就默认折叠，JavaScript 不可用就一直展开。&lt;/p&gt; &lt;/div&gt;
&lt;figure&gt;&lt;p&gt;&lt;/p&gt;&lt;figcaption&gt;&lt;span&gt; &lt;span&gt; Navigation bar on a wide screen, showing the entries horizontally &lt;/span&gt;&lt;span&gt; 宽屏上的导航栏，横向显示项目 &lt;/span&gt; &lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;p&gt;&lt;/p&gt;&lt;figcaption&gt;&lt;span&gt; &lt;span&gt; Navigation bar on a narrow screen, with a button to toggle the vertical list &lt;/span&gt;&lt;span&gt; 窄屏上的导航栏，有一个按钮来开关纵向列表 &lt;/span&gt; &lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;p&gt;&lt;/p&gt;&lt;figcaption&gt;&lt;span&gt; &lt;span&gt; Navigation bar on a narrow screen, with an always-displayed vertical list when JavaScript is not available &lt;/span&gt;&lt;span&gt; 窄屏上的导航栏，当 JavaScript 不可用时，有一个始终显示的纵向列表 &lt;/span&gt; &lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;
&lt;section&gt;&lt;h2&gt;&lt;a href=&quot;#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;span&gt; &lt;div&gt; This theme has gone through many forks and has various versions. The linked one is the one I use. &lt;/div&gt;&lt;span&gt; 这个主题经历了很多复刻，有多个版本。链接了的是我当年用的版本。 &lt;/span&gt; &lt;/span&gt;
&lt;a href=&quot;#user-content-fnref-next&quot;&gt;↩&lt;/a&gt;
 
&lt;a href=&quot;#user-content-fnref-next-2&quot;&gt;↩&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content></entry><entry><id>https://blog.tusooa.xyz/2024/06/26/Use-phorge-for-code-review/</id><title>Use phorge for code review</title><link rel="alternate" href="https://blog.tusooa.xyz/2024/06/26/Use-phorge-for-code-review/"/><published>2024-06-26T12:00:00.000Z</published><content type="html">&lt;div&gt; &lt;p&gt;I have always wanted to self-host a &lt;a href=&quot;https://phabricator.com/&quot;&gt;Phabricator&lt;/a&gt; for task management, as
I found KDE&apos;s Phabricator pretty useful for this. But one day
&lt;a href=&quot;https://tofuball.moe/&quot;&gt;Tofu&lt;/a&gt; told me that she likes Phabricator for
a different reason: stacked diffs. After some research, I found out that
Phabricator has been discontinued while there is an active fork called
&lt;a href=&quot;https://phorge.it/&quot;&gt;Phorge&lt;/a&gt;. And I began to use it.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt;  &lt;/div&gt;
&lt;a&gt;&lt;/a&gt;
&lt;h2&gt;&lt;a href=&quot;#Why-is-Phorge-intriguing-为什么-Phorge-吸引人呢？&quot;&gt;&lt;span&gt; &lt;span&gt; Why is Phorge intriguing? &lt;/span&gt;&lt;span&gt; 为什么 Phorge 吸引人呢？ &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;The main reason I wanted Phorge was indeed the task management system.
GitLab issues has almost nonexistent task management features, at least
in the free version. (Paid versions may have more, like mutually-exclusive
tags, but I frown upon the idea of using non-libre software for hosting
my code, or why wouldn&apos;t I use GitHub.)&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;Stacked diffs, while taking quite some time for me to understand at first,
do solve an important problem for my workflow when I write code for the
Kazv Project. In the original workflow, I needed to branch out from trunk&lt;sup&gt;&lt;a href=&quot;#user-content-fn-trunk&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;,
write code, add a changelog entry in the &lt;code&gt;changelogs&lt;/code&gt; folder, commit, then
open a merge request and wait for our other developer to review it.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;In the mean time, if I wanted to work on other things, I needed to branch
out from trunk again, and follow the same procedure, all while waiting for
reviews from the other developer. If the two branches touched the same file,
conflicts will arise, and the problems are:&lt;/p&gt; &lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;span&gt; &lt;span&gt; I usually want to use the new features by myself immediately. This means I need to maintain two states of the same set of changes (one on my local branch, one rebased upon trunk). &lt;/span&gt;&lt;span&gt;  &lt;/span&gt; &lt;/span&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt; &lt;span&gt; The two states may well have conflicts against each other. &lt;/span&gt;&lt;span&gt;  &lt;/span&gt; &lt;/span&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;span&gt; &lt;span&gt; I need to do this for &lt;em&gt;each&lt;/em&gt; of my merge requests. &lt;/span&gt;&lt;span&gt;  &lt;/span&gt; &lt;/span&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;p&gt;Stacked diffs solves this problem, because the unit of code reviews is not
restricted to the difference between &lt;em&gt;trunk and&lt;/em&gt; one of the other branches.
Instead, it can be any diff. So, if I write feature A upon trunk and then
go on to write feature B, I can start with A, and then submit the diff
&lt;em&gt;between A and B&lt;/em&gt; for review. The state only needs to be maintained once.
And then I can &lt;code&gt;git format-patch&lt;/code&gt; and copy them into my &lt;code&gt;/etc/portage/patches&lt;/code&gt;
to have my package manager install the patched version onto my system
so that I can use every new feature I just wrote.&lt;/p&gt; &lt;/div&gt;
&lt;h2&gt;&lt;a href=&quot;#Setting-up-Phorge-to-mirror-a-repository-from-GitLab让-Phorge-从-GitLab-镜像一个仓库&quot;&gt;&lt;span&gt; &lt;span&gt; Setting up Phorge to mirror a repository from GitLab &lt;/span&gt;&lt;span&gt; 让 Phorge 从 GitLab 镜像一个仓库 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;Installing &lt;strong&gt;Phorge&lt;/strong&gt; and &lt;strong&gt;Arcanist&lt;/strong&gt;&lt;sup&gt;&lt;a href=&quot;#user-content-fn-arcanist&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; is covered by their
&lt;a href=&quot;https://we.phorge.it/book/phorge/article/installation_guide/&quot;&gt;official docs&lt;/a&gt;.
I will omit the steps here (my set up is using Docker, so there are some
slight differences, maybe one day I will write another post about this).
One thing to note is that if you are also
on Gentoo, you will need the &lt;code&gt;curl&lt;/code&gt; USE flag on &lt;code&gt;php&lt;/code&gt; to use Arcanist:&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;] euse -E curl -p dev-lang/php&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;To use Phorge for code reviews, we first need to host the repository on
Phorge. Phorge can be set up to pull from another git repository, or to
push to another one, or both. But first, we need to create a repository
in &lt;strong&gt;Diffusion&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;First, we go to the main page of Diffusion, and click on &lt;strong&gt;Create Repository&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;It will prompt us to choose a version control system. We choose Git, of course.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;It will then give us a form. Give the repository a name you like. The
&lt;strong&gt;callsign&lt;/strong&gt; is a string composed of only the ASCII capital letters A-Z,
and must be unique across the Phorge installation. It is recommended
to give the repository a callsign as we might need this for Arcanist
to find it properly.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;After submitting the form, we will be directed to the overview page of
the repository. Click on &lt;strong&gt;URIs&lt;/strong&gt; on the left sidebar.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;We can see that there already are default URIs in the list, but we cannot
use them yet (because the repository has not yet been &lt;strong&gt;activated&lt;/strong&gt;).
As we want to mirror them from GitLab, we will add another URI pointing
to GitLab. Click on &lt;strong&gt;Add New URI&lt;/strong&gt; on the right sidebar.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;Copy the clone link from your GitLab repository and paste it into
the &lt;strong&gt;URI&lt;/strong&gt; entry. In &lt;strong&gt;I/O Type&lt;/strong&gt;, select &lt;strong&gt;Observe: Copy from a remote&lt;/strong&gt;.
If the GitLab repository is public, using the &lt;strong&gt;https&lt;/strong&gt; clone link would
suffice. If not, use the &lt;strong&gt;ssh&lt;/strong&gt; clone link, and add a credential by
generating a new ssh key, pasting the private key into Phorge, and
the public key into your GitLab account.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;Finally, we can go back to the &lt;strong&gt;Basics&lt;/strong&gt; page and click &lt;strong&gt;Activate Repository&lt;/strong&gt;
on the right sidebar. You will soon see your repository being imported.&lt;/p&gt; &lt;/div&gt;
&lt;h3&gt;&lt;a href=&quot;#arcconfig&quot;&gt;&lt;code&gt;.arcconfig&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt; &lt;p&gt;We need to make a &lt;code&gt;.arcconfig&lt;/code&gt; file in the repository root so that &lt;strong&gt;Arcanist&lt;/strong&gt;
will know where to find the repository on our Phorge server. Replace the URI
and callsign to the one you have.&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;phabricator.uri&quot; : &quot;https://iron.lily-is.land/&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  &quot;repository.callsign&quot;: &quot;TESTTWO&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;It is recommended to commit &lt;code&gt;.arcconfig&lt;/code&gt; into the repository. Why not create
a diff out of it to test that it works?&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;] git add .arcconfig&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;] git commit&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;] arc diff HEAD^&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;If you have not used Arcanist before, it will ask you to log in. Otherwise,
your favourite editor (hopefully) will open a file asking you to fill out
a &lt;strong&gt;Summary&lt;/strong&gt; and a &lt;strong&gt;Test Plan&lt;/strong&gt;. After you save and close the file, it will
give you the corresponding Revision URI. You can open it to review it.
&lt;a href=&quot;https://iron.lily-is.land/D74&quot;&gt;Here&lt;/a&gt; is a sample Revision page.&lt;/p&gt; &lt;/div&gt;
&lt;h2&gt;&lt;a href=&quot;#But-what-about-CI-CD-那-CI-CD-呢？&quot;&gt;&lt;span&gt; &lt;span&gt; But what about CI/CD? &lt;/span&gt;&lt;span&gt; 那 CI/CD 呢？ &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;GitLab has great CI/CD tooling. How to make use of it when submitting diffs
through Differential Revisions? There is one good news and one bad news.
The bright side is that it has a built-in tool called Harbormaster, which
can be set up to do certain things when you submit or change a Revision.
The dark side? Well... the only thing it can do right now is making an HTTP
request.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;The first key is to use a Phorge feature called &lt;strong&gt;Staging Area&lt;/strong&gt;. A staging
area is an url that Arcanist will push your changes to. There is no need that
the Phorge server has write access to that url. Only the user who is using
Arcanist need to have access. To use GitLab CI, we want the staging area to
be on GitLab. For this, we create another GitLab repository. Then, from the
Phorge repository, click &lt;strong&gt;Staging Area&lt;/strong&gt; on the left sidebar. Click on
&lt;strong&gt;Edit Staging&lt;/strong&gt; on the right, and paste the URI of the newly created
GitLab repository into it. This URI will be used to push when you submit
diffs using &lt;code&gt;arc diff&lt;/code&gt;, so use a protocol (https or ssh+git) that you will
be comfortable with.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;&lt;code&gt;arc diff&lt;/code&gt; will push two tags to the staging area: &lt;code&gt;phabricator/diff/X&lt;/code&gt;
and &lt;code&gt;phabricator/base/X&lt;/code&gt;. The former corresponds to the state &lt;em&gt;after&lt;/em&gt;
the diff, and the latter &lt;em&gt;before&lt;/em&gt; it. Of course, we want to run our
pipelines on the &lt;code&gt;phabricator/diff/X&lt;/code&gt; tags. The first thing you
might think of is to have the pipelines run when the tag being pushed
starts with &lt;code&gt;phabricator/diff/&lt;/code&gt;. That is a good idea, but then you
do not have a way to report the status of the build directly to Phorge,
and you will need to find the corresponding pipeline manually.
Instead, we will have Phorge &lt;strong&gt;trigger&lt;/strong&gt; a build on GitLab. Add the
following to your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; in your &lt;strong&gt;main repository&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;workflow:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rules:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: &apos;$CI_PIPELINE_SOURCE == &quot;trigger&quot;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: &apos;$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      when: never&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: $CI_COMMIT_BRANCH&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: &apos;$CI_COMMIT_TAG !~ /^phabricator\//&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;#Triggering-the-build-from-Phorge从-Phorge-触发构建&quot;&gt;&lt;span&gt; &lt;span&gt; Triggering the build from Phorge &lt;/span&gt;&lt;span&gt; 从 Phorge 触发构建 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt; &lt;p&gt;we will need to create a &lt;strong&gt;trigger token&lt;/strong&gt; for the staging area repository.
Go to &lt;strong&gt;Settings -&amp;gt; CI/CD&lt;/strong&gt;, then in &lt;strong&gt;Pipeline trigger tokens&lt;/strong&gt;,
click on &lt;strong&gt;Add new token&lt;/strong&gt;. Follow the instructions there.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;Go to &lt;strong&gt;Harbormaster&lt;/strong&gt; on Phorge, click on &lt;strong&gt;Manage Build Plans&lt;/strong&gt; on the
left sidebar, and then &lt;strong&gt;Create Build Plan&lt;/strong&gt; on the top-right (by default,
you will need to be an administrator of the Phorge instance to do this).
After you created the build plan, click &lt;strong&gt;Add Build Step&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;Choose &lt;strong&gt;Make HTTP Request&lt;/strong&gt; from the list.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;This is where the limitations of Harbormaster comes in our way, as
you can see on this page. First, it
does not support a request body, even though it can send POST requests.
Second, it can only send Basic Auth credentials. Even though GitLab supports
trigger tokens provided by a query parameter, this is not a recommended way
because your token will then be exposed to everyone who can see the diff
you submitted.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#Proxying-GitLab-requests代理-GitLab-的请求&quot;&gt;&lt;span&gt; &lt;span&gt; Proxying GitLab requests &lt;/span&gt;&lt;span&gt; 代理 GitLab 的请求 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt; &lt;p&gt;We will use nginx to reverse-proxy our requests and rewrite a Basic
Auth header into a query parameter. This will take in the user part
of a Basic Auth header and add it to the query parameter called &lt;code&gt;token&lt;/code&gt;.
Replace &lt;code&gt;&amp;lt;gitlab&amp;gt;&lt;/code&gt; in the following
file with the address and port your GitLab is listening to. For example,
you might write &lt;code&gt;proxy_pass http://localhost:8964;&lt;/code&gt;. Remember &lt;strong&gt;not&lt;/strong&gt; to
have a trailing slash after it.&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;server {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; listen 0.0.0.0:443 ssl;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; listen [::]:443 ssl;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; server_name gt.lily-is.land;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; server_tokens off;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; ssl_certificate /etc/letsencrypt/live/gt.lily-is.land/fullchain.pem;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; ssl_certificate_key /etc/letsencrypt/live/gt.lily-is.land/privkey.pem;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; location /api {&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rewrite ^(.+)$ $1?token=$remote_user break;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  proxy_pass http://&amp;lt;gitlab&amp;gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  proxy_redirect off;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  proxy_set_header Host $http_host;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  proxy_set_header X-Real-IP $remote_addr;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  proxy_set_header X-Forwarded-Proto $scheme;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  proxy_set_header X-Forwarded-Protocol $scheme;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  proxy_set_header X-Url-Scheme $scheme;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; access_log /var/log/nginx/gitlab_access.log;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt; error_log /var/log/nginx/gitlab_error.log;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;a href=&quot;#Two-step-trigger-for-GitLabGitLab-的两步触发器&quot;&gt;&lt;span&gt; &lt;span&gt; Two-step trigger for GitLab &lt;/span&gt;&lt;span&gt; GitLab 的两步触发器 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt; &lt;p&gt;Although GitLab offers a way to trigger a pipeline without using the request
body, it does not work in our case, because our tag name &lt;code&gt;phabricator/diff/X&lt;/code&gt;
has slashes in it. (GitLab docs say it works if you urlencode the slash, but
I tried, and it does not, at least when called from Phorge.) Instead, we use
a simple branch name, &lt;code&gt;servant&lt;/code&gt;. Add the following to the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;
file in the &lt;code&gt;servant&lt;/code&gt; branch of your &lt;strong&gt;staging repository&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;workflow:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rules:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: &apos;$CI_PIPELINE_SOURCE == &quot;trigger&quot;&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;stages:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  - trigger&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;trigger-build:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stage: trigger&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  image: curlimages/curl&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  script:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &apos;curl --request POST --form token=&quot;$TRIGGER_TOKEN&quot; --form ref=phabricator/diff/&quot;$DIFFID&quot; --form &quot;variables[TARGET_PHID]=$TARGET_PHID&quot; &quot;https://lily-is.land/api/v4/projects/$CI_PROJECT_ID/trigger/pipeline&quot;&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;And in your &lt;strong&gt;staging repository&lt;/strong&gt;,
add a CI variable &lt;code&gt;TRIGGER_TOKEN&lt;/code&gt;, masked and protected, with the
value set to the trigger token you just generated.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;Now go back to Harbormaster&apos;s Add Build Step page, we have collected everything
we need to trigger a pipeline.
Put &lt;code&gt;https://&amp;lt;gitlab&amp;gt;/api/v4/projects/&amp;lt;project-id&amp;gt;/ref/servant/trigger/pipeline?variables[TARGET_PHID]=${target.phid}&amp;amp;variables[DIFFID]=${buildable.diff}&lt;/code&gt;
in the &lt;strong&gt;URI&lt;/strong&gt; field. Replace &lt;code&gt;&amp;lt;gitlab&amp;gt;&lt;/code&gt; with the publicly accessible URI
of your GitLab instance, and &lt;code&gt;&amp;lt;project-id&amp;gt;&lt;/code&gt; with the project id of your
&lt;strong&gt;staging repository&lt;/strong&gt;. Choose &lt;strong&gt;POST&lt;/strong&gt; as the &lt;strong&gt;HTTP Method&lt;/strong&gt;.
In &lt;strong&gt;Credentials&lt;/strong&gt;, &lt;strong&gt;Add New Credential&lt;/strong&gt;, and put the trigger token into
&lt;strong&gt;Login/Username&lt;/strong&gt; field, leaving &lt;strong&gt;Password&lt;/strong&gt; empty. Choose &lt;strong&gt;Wait For Message&lt;/strong&gt;
for &lt;strong&gt;When Complete&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;#Trigger-a-build-automatically自动触发构建&quot;&gt;&lt;span&gt; &lt;span&gt; Trigger a build automatically &lt;/span&gt;&lt;span&gt; 自动触发构建 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt; &lt;p&gt;You will need to create a &lt;strong&gt;Herald rule&lt;/strong&gt; for this. As an administrator,
go to &lt;strong&gt;Herald&lt;/strong&gt; in Phorge, and click on &lt;strong&gt;Create Herald Rule&lt;/strong&gt;. Choose
&lt;strong&gt;Differential Revisions&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;Then click on &lt;strong&gt;Global Rule&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;In &lt;strong&gt;Conditions&lt;/strong&gt;, put &lt;strong&gt;Repository&lt;/strong&gt; &lt;strong&gt;is any of&lt;/strong&gt;, then enter the
Phorge repository just created. In &lt;strong&gt;Action&lt;/strong&gt;, choose &lt;strong&gt;every time
this rule matches&lt;/strong&gt;, and select &lt;strong&gt;Run build plans&lt;/strong&gt;, then choose
the build plan we just created.&lt;/p&gt; &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;p&gt;Now change something else, use &lt;code&gt;arc diff HEAD^&lt;/code&gt; to create another diff.
It should push to the staging area (maybe it will prompt you for
credentials), and run the build plan automatically.&lt;/p&gt; &lt;/div&gt;
&lt;h3&gt;&lt;a href=&quot;#Reporting-the-results-back-to-Phorge把结果报回给-Phorge&quot;&gt;&lt;span&gt; &lt;span&gt; Reporting the results back to Phorge &lt;/span&gt;&lt;span&gt; 把结果报回给 Phorge &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;div&gt; &lt;p&gt;We need to first generate a &lt;strong&gt;Conduit API Token&lt;/strong&gt; for this. To do so,
it is advised to use a separate Phorge account for it. Make sure
that the Phorge account has access to the build plan and the diff.
In your user settings page, go to &lt;strong&gt;Conduit API Tokens&lt;/strong&gt; and click on
&lt;strong&gt;Generate Token&lt;/strong&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;In the &lt;strong&gt;staging repository&lt;/strong&gt;&apos;s CI/CD settings, add a variable, masked,
called &lt;code&gt;CONDUIT_TOKEN&lt;/code&gt;, and paste the generated into it.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;Add the following to your &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; of your &lt;strong&gt;main repository&lt;/strong&gt;.
&lt;strong&gt;Also, add a stage &lt;code&gt;prepare&lt;/code&gt; to the very beginning of the &lt;code&gt;stages&lt;/code&gt;
and &lt;code&gt;report&lt;/code&gt; to the end.&lt;/strong&gt;&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;.report:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  image:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    name: &apos;reg.lily.kazv.moe/infra/phorge-ci-tools:servant&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rules:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: $TARGET_PHID&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      when: always&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - when: never&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  before_script:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - pipelineUrl=&quot;$CI_PROJECT_URL&quot;/-/pipelines/&quot;$CI_PIPELINE_ID&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;report-start:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  extends: .report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stage: prepare&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  script:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &apos;echo &quot;{\&quot;receiver\&quot;: \&quot;$TARGET_PHID\&quot;, \&quot;type\&quot;: \&quot;work\&quot;, \&quot;unit\&quot;: [{\&quot;name\&quot;: \&quot;GitLab CI (information only)\&quot;, \&quot;result\&quot;: \&quot;skip\&quot;, \&quot;details\&quot;: \&quot;$pipelineUrl\&quot;, \&quot;format\&quot;: \&quot;remarkup\&quot;}]}&quot; | /tools/arcanist/bin/arc call-conduit --conduit-uri https://iron.lily-is.land/ --conduit-token &quot;$CONDUIT_TOKEN&quot; -- harbormaster.sendmessage&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;report-success:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  extends: .report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rules:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: $TARGET_PHID&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      when: on_success&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - when: never&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stage: report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  script:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &apos;echo &quot;{\&quot;receiver\&quot;: \&quot;$TARGET_PHID\&quot;, \&quot;type\&quot;: \&quot;pass\&quot;}&quot; | /tools/arcanist/bin/arc call-conduit --conduit-uri https://iron.lily-is.land/ --conduit-token &quot;$CONDUIT_TOKEN&quot; -- harbormaster.sendmessage&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;report-failure:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  extends: .report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rules:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: $TARGET_PHID&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      when: on_failure&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - when: never&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stage: report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  script:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &apos;echo &quot;{\&quot;receiver\&quot;: \&quot;$TARGET_PHID\&quot;, \&quot;type\&quot;: \&quot;fail\&quot;}&quot; | /tools/arcanist/bin/arc call-conduit --conduit-uri https://iron.lily-is.land/ --conduit-token &quot;$CONDUIT_TOKEN&quot; -- harbormaster.sendmessage&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;Update the diff to include the changes. Then you will be able to see the
pipeline run, and the link to the pipeline can be found on the &lt;strong&gt;Buildable&lt;/strong&gt;
page. Here is &lt;a href=&quot;https://iron.lily-is.land/B100&quot;&gt;an example&lt;/a&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;] git add .gitlab-ci.yml&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;] git commit --amend&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;] arc diff HEAD^&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;#And-code-coverage-还有代码覆盖呢？&quot;&gt;&lt;span&gt; &lt;span&gt; And code coverage? &lt;/span&gt;&lt;span&gt; 还有代码覆盖呢？ &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;div&gt; &lt;p&gt;Another good thing from GitLab is code coverage visualization. On
the merge request page, you can see which lines are covered and which
are not. GitLab takes an XML file in cobertura format. Phorge, on the
other hand, uses a different format, which is a mapping from file names
to a &lt;a href=&quot;https://we.phorge.it/book/phorge/article/arcanist_coverage/#building-coverage-support&quot;&gt;string indicating the lines covered&lt;/a&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;Luckily, there is a tool called [pycobertura][pycob] that can help us
do the conversion. I created a &lt;a href=&quot;https://lily-is.land/infra/phorge-ci-tools/-/blob/eaae8d89c1332defa650d69bbd82ff57de62e6c7/cobertura-to-phorge&quot;&gt;script&lt;/a&gt; to convert cobertura XML files to
a JSON in Phorge&apos;s coverage format.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;We can add the following to the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; in the &lt;strong&gt;main repository&lt;/strong&gt;.
Assuming your previous jobs give you a cobertura XML in &lt;code&gt;build/coverage.xml&lt;/code&gt;
and you collect it as an artifact, the &lt;code&gt;coverage-report&lt;/code&gt; job generates a
HTML file visualizing the code coverage in addition to the JSON.
The &lt;code&gt;upload-coverage&lt;/code&gt; job sends the coverage information to Harbormaster,
so you can view it in the Differential Revision. Here is an &lt;a href=&quot;https://iron.lily-is.land/D74&quot;&gt;example&lt;/a&gt;.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;The &lt;code&gt;coverage-vis&lt;/code&gt; stage should be after the stage where your coverage job
is run, and the &lt;code&gt;coverage-upload&lt;/code&gt; stage should be after &lt;code&gt;coverage-vis&lt;/code&gt;, but
before &lt;code&gt;report&lt;/code&gt; (this is important, because the &lt;code&gt;report&lt;/code&gt; stage marks the
build as final, and you cannot change the status any more).&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;&apos;coverage-report&apos;:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rules: *build-rules&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stage: coverage-vis&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  image: &apos;reg.lily.kazv.moe/infra/phorge-ci-tools/pycobertura:servant&apos;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  script:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - pycobertura show ./build/coverage.xml --format html --output ./build/coverage.html --source .&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - /tools/cobertura-to-phorge ./build/coverage.xml . &amp;gt; ./build/coverage.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  artifacts:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    paths:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - build/coverage.html&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      - build/coverage.json&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;upload-coverage:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  extends: .report&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  stage: coverage-upload&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  rules:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - if: $TARGET_PHID&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      changes: *src-chg&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;      when: always&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - when: never&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;  script:&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    - &apos;{ echo &quot;{\&quot;receiver\&quot;: \&quot;$TARGET_PHID\&quot;, \&quot;type\&quot;: \&quot;work\&quot;, \&quot;unit\&quot;: [{\&quot;name\&quot;: \&quot;Test coverage\&quot;, \&quot;result\&quot;: \&quot;pass\&quot;, \&quot;details\&quot;: \&quot;$pipelineUrl\&quot;, \&quot;format\&quot;: \&quot;remarkup\&quot;, \&quot;coverage\&quot;:&quot;; cat build/coverage.json; echo &quot;}]}&quot;; } | /tools/arcanist/bin/arc call-conduit --conduit-uri https://iron.lily-is.land/ --conduit-token &quot;$CONDUIT_TOKEN&quot; -- harbormaster.sendmessage&apos;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;section&gt;&lt;h2&gt;&lt;a href=&quot;#footnote-label&quot;&gt;Footnotes&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It&apos;s called &lt;code&gt;servant&lt;/code&gt; in the repositories of the Kazv Project.
This name comes from Fate/Zero. &lt;a href=&quot;#user-content-fnref-trunk&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Arcanist is the command line tool to interact with Phorge. &lt;a href=&quot;#user-content-fnref-arcanist&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</content></entry><entry><id>https://blog.tusooa.xyz/2023/07/13/Fediverse-the-two-years/</id><title>Fediverse: the two years</title><link rel="alternate" href="https://blog.tusooa.xyz/2023/07/13/Fediverse-the-two-years/"/><published>2023-07-13T14:19:25.000Z</published><content type="html">&lt;div&gt; &lt;p&gt;It&apos;s more than two years I have been here, maybe almost three.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我呆这块儿两年有余了，可能几乎有三年了。&lt;/p&gt; &lt;/div&gt;
&lt;a&gt;&lt;/a&gt;
&lt;h1&gt;&lt;a href=&quot;#1-y-hitorino&quot;&gt;-1+y: hitorino&lt;/a&gt;&lt;/h1&gt;
&lt;div&gt; &lt;p&gt;This is the actual beginning of me in the fediverse though I did not realize it then.
Someone invited me to &lt;a href=&quot;https://m.hitorino.moe&quot;&gt;hitorino&lt;/a&gt;, a then-functioning Mastodon server. And they suggested
an app to me, maybe it&apos;s Pawoo or something, but I cannot remember. No one told me anything
about the fediverse, not even Mastodon. They just gave me a link, and that&apos;s it. So I just
registered, and that&apos;s it.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;这是我在联邦宇宙里的实际开始，虽然我那时没得意识到。
有人邀请我去 &lt;a href=&quot;https://m.hitorino.moe&quot;&gt;hitorino&lt;/a&gt;，当时还在运作的一个 Mastodon 服务器。
对面还推荐了一个 app 给我，可能是 Pawoo 或者什么玩意儿吧，但是我记不得了。
没得人告诉我关于联邦宇宙的任何东西，甚至没得讲 Mastodon。伊就给了我一个链接，
然后就没得了。所以我也就注册了，然后就没得了。&lt;/p&gt; &lt;/div&gt;
&lt;h1&gt;&lt;a href=&quot;#0-t-tusooa-xyz&quot;&gt;0: t.tusooa.xyz&lt;/a&gt;&lt;/h1&gt;
&lt;div&gt; &lt;p&gt;It was the end of 2020 I remember. I was browsing through some old archives on #archlinux-cn.
There were many interesting people, including those I&apos;ve been known for a decade or so.
Some got a blog, and they put their link to their Mastodon
account on it. I followed that link, and got (re-)introduced to that little simple interface.
&quot;Oh, it&apos;s a microblog.&quot; I was like. I was already using Matrix for a long time, and my feeling
was, &quot;So I can self-host this as well?&quot; So I did, and that was &lt;a href=&quot;https://t.tusooa.xyz&quot;&gt;https://t.tusooa.xyz&lt;/a&gt; .
I began posting poems, posting some daily life, and it turns out that I have already been interacting
with those who later became my lovers.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我记得那是 2020 的末尾。我在浏览 #archlinux-cn 高头的一些老档案。
那块有好多有意思的人，包括那些我晓得了有十年多了的人。
有人有个博客，而把伊的 Mastodon 账号链接放在上面了。
我追随了那个链接，被（重新）带到了那个小小的简单的界面高头。
「哦，这是个微博啊。」我这么想到。我已经用了 Matrix 蛮长时间的了，我的感受就是，
「所以我也能自己 host 这个玩意儿？」那我就干了，就成了 &lt;a href=&quot;https://t.tusooa.xyz&quot;&gt;https://t.tusooa.xyz&lt;/a&gt; 。
我开始发诗，发点儿日常生活，我还发觉在那个时候我就开始跟那些后来成为我的恋人的人们互动了。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;Good life was short. I got burned out by my internship. Covid was there. I wasn&apos;t able
to see my girlfriend. I tried to participate in discussions peacefully, there were always
people who spread hatred, discrimination, and especially transphobia. Since the beginning,
I tried to give constructive feedback, but those just won&apos;t listen and will just violently
push you back against a rock. And the more I use Mastodon, the more I feel it was really
uncustomizable. The text limit can&apos;t be changed without changing the code. The requirement
for email upon registration is constantly bothering me because it is inherently unnecessary
information collected against users&apos; privacy, and Eugen refused to make it optional.
I never actually opened up registration on the server.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;美好生活是短暂的。我被实习搞得精疲力竭。Covid 在那块。我见不到女朋友。我试到和平地参与讨论，
总有人散播仇恨，歧视，而尤其是恐跨。从最开始来讲，我试到给出建设性的反馈，但是伊们就不听，
还粗暴地把你推到石头高头。而且啊，我越是用 Mastodon，就越觉得它真的不可定制。文字上限不改代码就改不了。
要求注册时填邮箱真的是让我很苦恼，因为是原本不必要的信息被收集起来，损害用户的隐私，而 Eugen
拒绝让它变成可选的。我从来没得在那个服务器高头开过注册。&lt;/p&gt; &lt;/div&gt;
&lt;h1&gt;&lt;a href=&quot;#Watching-for-Pleroma注视到-Pleroma&quot;&gt;&lt;span&gt; &lt;span&gt; Watching for Pleroma &lt;/span&gt;&lt;span&gt; 注视到 Pleroma &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;div&gt; &lt;p&gt;It was no surprise I began searching for alternative microblogging software. And somehow I found out about Pleroma.
It looks so nice, so customizable, and is so friendly towards those who do not register (Mastodon requires you
to register to perform most read-only actions). Immediately I was amazed about that. Only one thing though: Mastodon
allows you to take all (&quot;conditions apply&quot;!) your followers with you when you jump ships. Pleroma had no such feature.
The good side was, I found out about &lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/2773&quot;&gt;one merge request&lt;/a&gt; on their GitLab, which allows you to move from
Mastodon to Pleroma, by adding an alias referring the old account to the new account.
I was never thinking of going back, so that was enough for me. I looked at it and that gave me hope
of using some other software than Mastodon. But after a month or so, there was literally no progress on that MR, so
I decided to shout out in the dev channel and ask what was going on. One of them, maybe rinpatch, told me that if no one
cared about it then it was probably going to be ignored, and suggested that I comment on the MR. I ended up testing it and
reported my results -- initially it did not work. It went the wrong way as the original method added it to the WebFinger
profile, but Mastodon expects the alias in the Actor object. After the problem was fixed, I was able to open up a Pleroma
server for my own use.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我开始搜索替代的微博软件就不值得惊讶了。然后我不知怎么的就找到了 Pleroma。它看上去很棒，
很可定制，而且对不注册的人非常友好（Mastodon要你注册才能完成大部分的只读操作）。
我立刻就为之惊叹了。但有一事：Mastodon允许你跳船的时候把所有的关注者带走（「要符合条件的才行」！）。
Pleroma 还没得这个功能。好的一面就是，我在伊们的 GitLab 高头找到了&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/2773&quot;&gt;一个合并请求&lt;/a&gt;，
能让你从 Mastodon 移动到 Pleroma。原理是在新账号上加一个指向旧账号的别名。
我从来没得想过回去，所以那对我来讲就足够了。我看到它，就觉有希望能用 Mastodon 意外的什么软件了。
但过了一个月吧，那个 MR 高头完全没得任何进度，所以我决定去开发者频道高头吼一嗓子问问到底怎么样了。
其中一个，可能是 rinpatch 吧，告诉我如果没得人关心，那可能就会被忽略了，然后建议我去那个 MR 高头评论。
我最终还是自己测试了，汇报了我的结果——一开始是不能用的。它走错了道路，之前的方法是把它加到 WebFinger
profile 高头，但是 Mastodon 想要别名在 Actor 对象里头。这个问题修好了之后，我就能开一个自己用的 Pleroma 服务器了。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;And that was the beginning of &lt;a href=&quot;https://kazv.moe&quot;&gt;kazv.moe&lt;/a&gt;, somewhere April in 2021.
It started out as a tiny little server.
I invited my girlfriend and some friends there. Some people who I have known when I was on Mastodon
registered alt accounts on it. Even though registration was completely open without any verification,
there was not a lot of people.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;而那便是 &lt;a href=&quot;https://kazv.moe&quot;&gt;kazv.moe&lt;/a&gt; 的开端了，2021年4月的某日。
它开始是一个微小的服务器。我把我的女朋友和一些朋友邀请了过来。
一些我在 Mastodon 高头认识的人在上面注册了小号。
尽管完全开放注册，没得任何验证机制，它也还是没得多少人。&lt;/p&gt; &lt;/div&gt;
&lt;h1&gt;&lt;a href=&quot;#Hacking-Pleroma&quot;&gt;Hacking Pleroma&lt;/a&gt;&lt;/h1&gt;
&lt;div&gt; &lt;p&gt;As time goes, I feel that I want more features for Pleroma. First of all, I want account migration
to fully work. A lot of people are posting really long images and I could not zoom in conveniently.
I really liked Misskey&apos;s tree-style thread display and nice mention links. The emoji picker was a
disaster to use because it was really slow to load, and everything was mixed together, there being only
two groups: &quot;custom&quot; and &quot;unicode,&quot; effectively making emoji packs useless.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;时光流逝，我感觉我想要 Pleroma 有更多功能。首先，我想要账号迁移能完全工作。
好多人在发巨长的图，我没得办法方便地放大。
我真的很喜欢 Misskey 的树状线索显示和好看的艾特链接。
表情选择器用起来简直是灾难，因为它加载起来真的真的慢，所有的东西都糊在一起，只有两个分组：
「自定义」和「unicode」，让表情包完全无用。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;So I started to write code, for the features I want, and deployed them to kazv.moe .
At the beginning, I just touched the frontend. It was relatively easy. It&apos;s JavaScript,
and I knew JavaScript since 2017, when I joined &lt;a href=&quot;https://rikumi.dev&quot;&gt;Rikumi&lt;/a&gt;&apos;s project and started
writing server-side JavaScript. It took me some time to figure out how Vue works, but
since I knew React, it wasn&apos;t that much of a problem.
Then, &lt;a href=&quot;https://suicablog.cobaltkiss.blue&quot;&gt;suica&lt;/a&gt; asked me why search
on Pleroma was broken. And debula found out the cause of it. To fix that took me a couple
of hours -- that was &lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3519&quot;&gt;the first time I touched Pleroma&apos;s backend code&lt;/a&gt;. I was never exposed
to any Elixir or Erlang before, and all I read was Elixir&apos;s official tutorial. I did not get
to read the OTP part -- it could have saved a huge part of my time if I did, because I did not
know how to run individual test cases and wasted a lot of time on running the whole &lt;code&gt;coveralls&lt;/code&gt;
process.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;所以我开始给我想要的特性写代码，然后把它部署到 kazv.moe 去。一开始，我只是动动前端。
还是比较简单的。它是 JavaScript 的，而我从 2017年加入 &lt;a href=&quot;https://rikumi.dev&quot;&gt;Rikumi&lt;/a&gt; 的项目开始写服务端
JavaScript 的时候就懂得 JavaScript 了。弄清楚 Vue 怎么工作花了点时间，但是我懂 React，
所以问题不大。
然后，&lt;a href=&quot;https://suicablog.cobaltkiss.blue&quot;&gt;suica&lt;/a&gt; 问我为什么 Pleroma 的搜索是坏的。debula 找到了问题的原因。修它花了我几个小时——
那是&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3519&quot;&gt;我第一次去动 Pleroma 的后端代码&lt;/a&gt;。我之前从来没得接触过任何 Elixir 或者 Erlang，
我当时就读了 Elixir 的官方教程。没得读 OTP 的那部分——要是看了的话，那能节省我一大半的时间了，
因为我不晓得怎么运行单个的测试案例，在运行整个 &lt;code&gt;coveralls&lt;/code&gt; 过程里浪费了好多时间。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;And so I continued adding new features: &lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1408&quot;&gt;grouped emoji picker&lt;/a&gt;, &lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1419&quot;&gt;remote interaction&lt;/a&gt;,
&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1424&quot;&gt;misskeyish mention links&lt;/a&gt;, &lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1431&quot;&gt;confirmation dialogs&lt;/a&gt;, &lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3524&quot;&gt;account migration&lt;/a&gt;,
&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3634&quot;&gt;translations for backend-rendered pages&lt;/a&gt;, &lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3643&quot;&gt;server announcements&lt;/a&gt;, &lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3678&quot;&gt;editing&lt;/a&gt;.
Those are all things wanted by people, and they really love to see it. However, Pleroma at that time was on the
verge of collapsing: AG was using his power to push all his changes into Pleroma in any way he could, while
caring little if nothing about other people&apos;s work and especially Pleroma-FE, as Soapbox was the frontend of
&lt;em&gt;his&lt;/em&gt; legacy. There were not a lot of people who can review my code, so it got stuck in the process for
a very long time. My very first MR took half a year to get merged.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;然后我就继续加新功能：&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1408&quot;&gt;分组的表情选择器&lt;/a&gt;，&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1419&quot;&gt;远端互动&lt;/a&gt;，
&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1424&quot;&gt;misskey 式的艾特链接&lt;/a&gt;，&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1431&quot;&gt;确认对话框&lt;/a&gt;，&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3524&quot;&gt;账号迁移&lt;/a&gt;，
&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3634&quot;&gt;后端渲染的页面的翻译&lt;/a&gt;，&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3643&quot;&gt;服务器公告&lt;/a&gt;，&lt;a href=&quot;https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3678&quot;&gt;编辑&lt;/a&gt;。
这些都是人们想要的功能，伊们真的很想看到的。但是，Pleroma 在那个时候处在崩溃的边缘：
AG 在用他的权力以任何可以的方法把他的修改推进 Pleroma 里头，但是对别人的工作，尤其是 Pleroma-FE
（毕竟 Soapbox 是&lt;em&gt;他&lt;/em&gt;的遗迹前端嘛）的关注微乎其微，如果不是完全没得的话。没得什么人能审阅我的代码，
所以它卡在过程里就卡了好久。我第一个 MR 用了半年才被合并。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;In the meantime, hj organized a meeting for Pleroma developers and users to reunionize the
community and rethink about the next steps. AG was stripped off the maintainer title, and
later got kicked out. He then became really abusive on the fediverse, calling names towards
me and Hélène, the other girl at that time on our team. I wonder whether it is due to this
that she ended up quitting developing Pleroma. I ended up banned both of his instance from
kazv.moe . Reflecting on that, I would say, &quot;&lt;em&gt;&lt;strong&gt;A self-claimed feminist, and all he does is...
harassing women? What a shame?&lt;/strong&gt;&lt;/em&gt;&quot; By the end of the year of 2022, I was named
a new maintainer through the consensus of the current devs on the IRC channel. And we released
2.5.0, the one with many of the long-longed features I wrote. The Akkoma folks forked away
from us and advertised their software with the features I wrote, claiming credits just because
they released earlier than us. I really can&apos;t say I like how this works.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;在同时，hj 给 Pleroma 的开发者和用户组织了一个会议，为了重组社群，再思考下一步怎么办。
AG 被削除了维护者的头衔，之后被开除出去了。他之后在联邦宇宙上面变得非常粗暴，咒骂我跟
Hélène，当时我们团队里头的另一个女孩。我不晓得是不是因为这个，她才从 Pleroma 的开发里退出的。
我最后在 kazv.moe 高头把他的两个实例都给封得了。
现在回想起来，我就要讲了：「&lt;em&gt;&lt;strong&gt;一个自称的女权主义者，而他做的事情都是在...伤害女性？多么可耻啊？&lt;/strong&gt;&lt;/em&gt;」
2022年底，IRC 频道高头的现任开发者们一致同意授予我新的维护者的职位。然后我们释出了 2.5.0，
这个有很多我写的，大家都期盼已久的特性的版本。Akkoma 那一帮人从我们这边 fork 走了，
拿着我写的那些特性来宣传伊们的软件，只因为伊们释出的早，便抢了名头。我真的没得办法讲我喜欢这事的。&lt;/p&gt; &lt;/div&gt;
&lt;h1&gt;&lt;a href=&quot;#My-personal-life我的个人生活&quot;&gt;&lt;span&gt; &lt;span&gt; My personal life &lt;/span&gt;&lt;span&gt; 我的个人生活 &lt;/span&gt; &lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;div&gt; &lt;p&gt;&lt;strong&gt;From here on there might be depiction of sex. You have been warned.&lt;/strong&gt;&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;&lt;strong&gt;从这里开始可能有性描写。你已经被警告过了。&lt;/strong&gt;&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;That&apos;s not as much dramatic as the Pleroma drama, no? I still posted my poems (2021 was the year when I wrote the
most poems till then). I occasionally posted the food I cooked, and I posted my political viewpoints.
Definitely a lot of people hate me for my views, but I do not care any more. I created an &lt;a href=&quot;https://kazv.moe/users/SWwind&quot;&gt;alt account&lt;/a&gt; for
more intimate relationships, and I started to flirt with &lt;a href=&quot;https://kazv.moe/users/Murasaki&quot;&gt;Reiki&lt;/a&gt;. We used paired avatars, expressed our feelings
to each other, and maybe after half a year, I asked them to be my girlfriend. They agreed.
Since that time I realized I was not a &quot;pure lesbian&quot; but rather can be attracted to non-binary people as well, which
is a type of Bi+. I use the term yuri to represent my sexual orientation now.
And later I got to be girlfriends with &lt;a href=&quot;https://m.cmx.im/@StarKiller&quot;&gt;two&lt;/a&gt; &lt;a href=&quot;https://nya.one/@Orca&quot;&gt;other&lt;/a&gt; cute people.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;那远不如 Pleroma 的戏剧那么戏剧化，不是吗？我仍然发诗（2021是我在以往年份里写过最多诗的一年）。
我偶尔发点自己做的食物，我还发我的政治观点。肯定有很多人因为我的观点而恨我的，但我不再在意了。
我创了一个&lt;a href=&quot;https://kazv.moe/users/SWwind&quot;&gt;小号&lt;/a&gt;给更加亲密的关系，并且开始和&lt;a href=&quot;https://kazv.moe/users/Murasaki&quot;&gt;蕾姬&lt;/a&gt;调情。我们用一对的头像，
向对方表达自己的感情，可能有半年之后吧，我让伊做我的女朋友。伊答应了。
从那个时候起，我意识到我不是一个「纯女同」，我其实也可以对非二元的人产生吸引的，这是一种 Bi+。
我现在用百合这个词来表达我的性取向。再之后我又和&lt;a href=&quot;https://m.cmx.im/@StarKiller&quot;&gt;另外&lt;/a&gt;&lt;a href=&quot;https://nya.one/@Orca&quot;&gt;两个&lt;/a&gt;可爱的人成为了女朋友。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;After covid I have been able to see Saya more often, but only during my school breaks.
She masturbated me and I think that counts as sex.
I hate it that I have to cross the border. At school it&apos;s still busy, and I have encountered
a couple of small problems, but luckily they were resolved promptly.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;covid 过后我能更频繁地见 Saya 了，但是也只有学校放假的时候。
她给我自慰了，我觉得这算是性交。
我讨厌必须穿越边境这事。
在学校还是很忙，碰到了好多小问题，但还好能妥善解决。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;Revealing my political views isn&apos;t necessarily a bad thing right? &lt;a href=&quot;https://nanakumo.github.io&quot;&gt;Someone&lt;/a&gt; came to kazv.moe
because she love my views, and I ended up having a crush on her. Unfortunately she doesn&apos;t
love me back so we are just being friends and sisters. I guess that&apos;s the bitterness of life, huh.
(Oh have I told you that she now has a Pleroma instance with the exact same setup as mine?)
I finally actually used my Funkwhale, and please, look forward to our new song, ok?&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;揭示我的政治观点不一定是坏事，对吧？&lt;a href=&quot;https://nanakumo.github.io&quot;&gt;有人&lt;/a&gt;就是因为喜欢我的观点才来到 kazv.moe，再后来我爱上了她。
但不凑巧，她并不也爱我，所以我们只是在做朋友和姐妹啦。我想这就是人生的酸涩吧，嘛。
（哦，我啊讲过啊，她现在有一个 Pleroma 实例了，用的是跟我一模一样的配置诶？）
我终于把我的 Funkwhale 真的用起来了，请期待一下我们的新歌吧？&lt;/p&gt; &lt;/div&gt;</content></entry><entry><id>https://blog.tusooa.xyz/2019/05/30/Assistants-copy-share-assignment/</id><title>Assistants -- copy, share, assignment</title><link rel="alternate" href="https://blog.tusooa.xyz/2019/05/30/Assistants-copy-share-assignment/"/><published>2019-05-30T14:20:53.000Z</published><content type="html">&lt;p&gt;Over the last week I have been investigating into &lt;a href=&quot;https://bugs.kde.org/show_bug.cgi?id=361012&quot;&gt;Bug 361012&lt;/a&gt;,
on the undo history of the modification of guides. But from the very beginning I mixed up the two terms
&quot;guides&quot; and &quot;assistants,&quot; so I decided to &lt;a href=&quot;https://invent.kde.org/kde/krita/merge_requests/32&quot;&gt;work on both&lt;/a&gt;.
The work with guides is a lot simpler and will not be covered here, though.&lt;/p&gt;
&lt;p&gt;As I write this post, the &lt;a href=&quot;https://invent.kde.org/kde/krita/tree/master&quot;&gt;master branch of Krita&lt;/a&gt;
does not create any undo commands for the document. I first &lt;a href=&quot;https://invent.kde.org/kde/krita/merge_requests/32/diffs?commit_id=12bc1d0730f04321c5ec304ce09f6670ef14c576&quot;&gt;added undo commands&lt;/a&gt; for adding and removing
assistants, which seems the easiest. The editing of them is a bit more difficult, as the dragging
operations involve the movement of many &quot;handles,&quot; the movable round buttons that define the position
of one or more assistants. The source code on master for implementing such actions is quite complicated
and involves a great number of cases. It would be another great endeavour to put all these bunches of
code into a &lt;code&gt;KUndo2Command&lt;/code&gt;. But, another thing I have experimented with and I will be working on
will immediately clear the clouds.&lt;/p&gt;
&lt;p&gt;So I just thought of the copy-on-write mechanism, and yes, why not? Though COW itself is not
actually implemented for the guides, it does seem inspiring. I mean, we can just save a copy
of all assistants and, when needed, restore that.&lt;/p&gt;
&lt;p&gt;The main problem here is the handles. They are represented as shared pointers in individual
assistants and may be shared between different ones (e.g. two perspectives share two corner
handles and one side handles). When we take a clone of the list of assistants it will be
necessary to keep this kind of relationship. My solution is to use a &lt;code&gt;QMap&lt;/code&gt; of pointers,
which seems to coincide with the logic of exporting to xml, but I had yet to read that part
of the code when writing mine so I did not know about that. The logic is to check, for
every handle, whether there is a mapping relationship in the map. If there is, we reuse that
handle, and if not, we create a new one with the same position and record that relationship
in our &lt;code&gt;QMap&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But some display properties are not to be recorded into the undo history. Such properties
include the changing of color, visibility, etc. To resolve this problem, I put these data into
a shared pointer and, when we are cloning an assistant for undo/redo, we will reuse that pointer.
When we replace the assistant list with the one recorded, all the display properties will remain
since the data are shared.&lt;/p&gt;
&lt;p&gt;And for the next several weeks I will move onto the &lt;a href=&quot;https://phabricator.kde.org/T10991&quot;&gt;Snapshot Docker&lt;/a&gt;.&lt;/p&gt;</content></entry></feed>