<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://tuobashaoli.github.io/atom.xml" rel="self" type="application/atom+xml" /><link href="https://tuobashaoli.github.io/" rel="alternate" type="text/html" /><updated>2025-12-01T14:54:55+00:00</updated><id>https://tuobashaoli.github.io/atom.xml</id><title type="html">Hei fe</title><subtitle>如果我的理解有误，欢迎指正</subtitle><author><name>Tuo Bashaoli</name></author><entry><title type="html">多仓库管理</title><link href="https://tuobashaoli.github.io/blog/2025/10/26/multirepo/" rel="alternate" type="text/html" title="多仓库管理" /><published>2025-10-26T13:08:18+00:00</published><updated>2025-10-26T13:08:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2025/10/26/multirepo</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2025/10/26/multirepo/"><![CDATA[<h1 id="说点儿啥">说点儿啥</h1>

<p>当项目过于庞大的时候，往往趋向于使用将整个代码分散到多个仓库进行管理，各个仓库对应于项目的哥哥模块，彼此间可以用接口相互关联，但是又可以独立管理，十分地方便</p>

<p>但是怎么管理起来，就一点点说法了</p>

<h1 id="repo工具">repo工具</h1>

<p>这个是google用来管理Android项目的工具，通过一个文件manifest.xml，里面列出相关的仓库和分支以及各个仓库对应的下载位置，来管理全部的仓库</p>

<p>一个典型的manifest.xml内容如下</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;manifest&gt;</span>
<span class="nt">&lt;remote</span> <span class="na">name=</span><span class="s">"origin"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="na">review=</span><span class="s">"review.source.android.com"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;default</span> <span class="na">revision=</span><span class="s">"master"</span> <span class="na">remote=</span><span class="s">"origin"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/build"</span> <span class="na">path=</span><span class="s">"build"</span><span class="nt">&gt;</span>
<span class="nt">&lt;copyfile</span> <span class="na">src=</span><span class="s">"makefile"</span> <span class="na">dest=</span><span class="s">"makefile.mk"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/project&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/test"</span> <span class="na">path=</span><span class="s">"test"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/apps/hello"</span> <span class="na">path=</span><span class="s">"apps/hello"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>这里的project没有指定分支branch或者revision，就表示默认下载默认分支master上最新的节点的代码</p>

<h2 id="场景1">场景1</h2>

<p>获取主线master版本的最新代码，通常用于持续集成和定时构建，如下</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;manifest&gt;</span>
<span class="nt">&lt;remote</span> <span class="na">name=</span><span class="s">"origin"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="na">review=</span><span class="s">"review.source.android.com"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;default</span> <span class="na">revision=</span><span class="s">"master"</span> <span class="na">remote=</span><span class="s">"origin"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/build"</span> <span class="na">path=</span><span class="s">"build"</span><span class="nt">&gt;</span>
<span class="nt">&lt;copyfile</span> <span class="na">src=</span><span class="s">"makefile"</span> <span class="na">dest=</span><span class="s">"makefile.mk"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/project&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/test"</span> <span class="na">path=</span><span class="s">"test"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/apps/hello"</span> <span class="na">path=</span><span class="s">"apps/hello"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>使用各个仓库的主线分支，每次下载都是下载这些仓库的主线分支master上的最新代码</p>

<h2 id="场景2">场景2</h2>

<p>开发中需要适配各种版本，从而需要生成各种manifest.xml，每个manifest.xml中存储这个版本的各个仓库的分支/哈希节点的信息</p>

<p>比如对应release分支，如下，对应的是v1版本</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;manifest&gt;</span>
<span class="nt">&lt;remote</span> <span class="na">name=</span><span class="s">"origin"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="na">review=</span><span class="s">"review.source.android.com"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;default</span> <span class="na">revision=</span><span class="s">"master"</span> <span class="na">remote=</span><span class="s">"origin"</span> <span class="nt">/&gt;</span>
<span class="c">&lt;!-- project/build是一个通用的仓库，默认分支可以兼容各种发布版本 --&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/build"</span> <span class="na">path=</span><span class="s">"build"</span><span class="nt">&gt;</span>
<span class="nt">&lt;copyfile</span> <span class="na">src=</span><span class="s">"makefile"</span> <span class="na">dest=</span><span class="s">"makefile.mk"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/project&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/test"</span> <span class="na">revision=</span><span class="s">"release_v1"</span> <span class="na">path=</span><span class="s">"test"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/apps/hello"</span> <span class="na">revision=</span><span class="s">"release_v1"</span> <span class="na">path=</span><span class="s">"apps/hello"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<h2 id="场景3">场景3</h2>

<p>某个节点的全部代码都已经测试完毕，bug收敛，达到发布质量标准了，并对每个仓库创建了标记号TAG，比如V1.0</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;manifest&gt;</span>
<span class="nt">&lt;remote</span> <span class="na">name=</span><span class="s">"origin"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="na">review=</span><span class="s">"review.source.android.com"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;default</span> <span class="na">revision=</span><span class="s">"master"</span> <span class="na">remote=</span><span class="s">"origin"</span> <span class="nt">/&gt;</span>
<span class="c">&lt;!-- project/build是一个通用的仓库，默认分支可以兼容各种发布版本 --&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/build"</span> <span class="na">revision=</span><span class="s">"V1.0"</span> <span class="na">path=</span><span class="s">"build"</span><span class="nt">&gt;</span>
<span class="nt">&lt;copyfile</span> <span class="na">src=</span><span class="s">"makefile"</span> <span class="na">dest=</span><span class="s">"makefile.mk"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/project&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/test"</span> <span class="na">revision=</span><span class="s">"V1.0"</span> <span class="na">path=</span><span class="s">"test"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/apps/hello"</span> <span class="na">revision=</span><span class="s">"V1.0"</span> <span class="na">path=</span><span class="s">"apps/hello"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>这样在任意时刻，使用这个manifest下载的代码，都是固定的</p>

<h2 id="场景4">场景4</h2>

<p>当一个大项目中，不同团队开发不同的仓库，但是也存在公共仓库供多个团队开发,如下所示，</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;manifest&gt;</span>
<span class="nt">&lt;remote</span> <span class="na">name=</span><span class="s">"origin"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="na">review=</span><span class="s">"review.source.android.com"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;default</span> <span class="na">revision=</span><span class="s">"master"</span> <span class="na">remote=</span><span class="s">"origin"</span> <span class="nt">/&gt;</span>
<span class="c">&lt;!-- project/build是一个通用的仓库，默认分支可以兼容各种发布版本 --&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/build"</span> <span class="na">groups=</span><span class="s">"public"</span> <span class="na">path=</span><span class="s">"build"</span><span class="nt">&gt;</span>
<span class="nt">&lt;copyfile</span> <span class="na">src=</span><span class="s">"makefile"</span> <span class="na">dest=</span><span class="s">"makefile.mk"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/project&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/test"</span> <span class="na">groups=</span><span class="s">"public"</span> <span class="na">path=</span><span class="s">"test"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/apps/hello"</span> <span class="na">groups=</span><span class="s">"hello"</span> <span class="na">path=</span><span class="s">"apps/hello"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/apps/world"</span> <span class="na">groups=</span><span class="s">"world"</span> <span class="na">path=</span><span class="s">"apps/world"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>hello团队和world团队分别负责自己的仓库，但是project/build和project/test是公共仓库</p>

<p>hello团队使用这个manifest下载时使用命令<code class="language-plaintext highlighter-rouge">repo init -u &lt;manifest_url&gt; -b &lt;branch&gt; -g public,hello</code></p>

<p>world团队使用这个manifest下载时使用命令<code class="language-plaintext highlighter-rouge">repo init -u &lt;manifest_url&gt; -b &lt;branch&gt; -g public,world</code></p>

<h2 id="场景5">场景5</h2>

<p>当仓库不全部都是来源于一个代码源，如下所示</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;manifest&gt;</span>
<span class="nt">&lt;remote</span> <span class="na">name=</span><span class="s">"origin"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="na">review=</span><span class="s">"review.source.android.com"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;remote</span> <span class="na">name=</span><span class="s">"origin2"</span> <span class="na">fetch=</span><span class="s">".."</span> <span class="na">review=</span><span class="s">"review2.source.android.com"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;default</span> <span class="na">revision=</span><span class="s">"master"</span> <span class="na">remote=</span><span class="s">"origin"</span> <span class="nt">/&gt;</span>
<span class="c">&lt;!-- project/build是一个通用的仓库，默认分支可以兼容各种发布版本 --&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/build"</span> <span class="na">revision=</span><span class="s">"V1.0"</span> <span class="na">path=</span><span class="s">"build"</span> <span class="na">remote=</span><span class="s">"origin2"</span><span class="nt">&gt;</span>
<span class="nt">&lt;copyfile</span> <span class="na">src=</span><span class="s">"makefile"</span> <span class="na">dest=</span><span class="s">"makefile.mk"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/project&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/test"</span> <span class="na">revision=</span><span class="s">"V1.0"</span> <span class="na">path=</span><span class="s">"test"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;project</span> <span class="na">name=</span><span class="s">"project/apps/hello"</span> <span class="na">revision=</span><span class="s">"V1.0"</span> <span class="na">path=</span><span class="s">"apps/hello"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/manifest&gt;</span>
</code></pre></div></div>

<p>这样，project/build的代码就从review2.source.android.com地址下载，其他仓库就从review.source.android.com下载</p>

<h2 id="总结">总结</h2>

<p>可见，使用manifest可以非常灵活地管理各个仓库，如果某个仓库有更新，直接更改manifest里面的仓库的revision就可以实现了，</p>

<p>缺点就是需要用户额外学习manifest的使用和配置了，并且manifest也是存放在一个git仓库里面的，所以这个manifest的git仓库也需要额外管理</p>

<h1 id="git-submodule">git submodule</h1>

<p>子模块是内嵌入git的，相关配置都是在git仓库的.gitmodules文件里面，如下所示</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[submodule "libfoo"]
  path = src/libfoo
  url = https://github.com/example/libfoo.git
  branch = dev

[submodule "utils"]
  path = src/utils
  url = ../utils-repo.git  # 相对路径
  branch = release
</code></pre></div></div>

<p>子模块存储在git里面的只能是hash，所以每次子模块存在更新换了新的hash时，都要在git中下载子模块然后做个提交</p>

<p>好处是不需要用户额外学习安装repo，只需要记住几个常用的git submodule命令即可</p>

<h2 id="公共模块更新">公共模块更新</h2>

<p>当一个公共的仓库有更新时，就需要让所有的包含这个公共仓库的仓库，去进行更新，比较麻烦</p>

<p>比如，hello仓库和world仓库都需要build仓库，那么在hello仓库和world仓库的.gitmodules文件里面都写上如下内容</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[submodule "build"]
  path = project/build
  url = git@review.source.android.com/project/build
  branch = master
</code></pre></div></div>

<p>当build仓库有更新的时候，就要把hello仓库和world仓库的子模块build都去更新一下hash，</p>

<ol>
  <li>repo manifest只需要修改xml文件里面仓库的revision为目标hash</li>
  <li>git submodule需要下载submodule的代码并且checkout到目标hash然后提交，仓库越大耗时越长</li>
</ol>

<h2 id="模拟repo-manifest">模拟repo manifest</h2>

<p>假设，有个git仓库叫manifest，里面的.gitmodules内容如下</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[submodule "build"]
  path = project/build
  url = git@review.source.android.com/project/build
  branch = master
[submodule "test"]
  path = project/test
  url = git@review.source.android.com/project/test
  branch = master
[submodule "hello"]
  path = project/hello
  url = git@review.source.android.com/project/hello
  branch = master
</code></pre></div></div>

<p>这样这个仓库连带submodule下载完，其实代码格局和repo manifest是十分相似的，但是</p>

<p>1.repo manifest中的revision可以直接看出仓库的hash节点，所以在交付源码时，直接发送一个revision是固定hash的xml即可
2.git submodule中的.gitmodules显示仓库的hash节点，必须把仓库下载到对应hash然后才能查看各个submodule的hash，比较麻烦</p>

<p><strong>个人感觉</strong>这种模拟的效果是不如repo manifest的</p>

<h2 id="总结-1">总结</h2>

<p>对于一些简单的仓库依赖场景，比如各个仓库呈现层数不高的树状依赖，就可以使用git submodule来简化操作,如下所示</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>     build
     /  \
hello    world

    test
    /  \
hello   world
</code></pre></div></div>

<p>如果是大型项目，存在上百个仓库，十几个开发团队协作，并且存在仓库之间的相互依赖，比如Android项目，还是推荐使用repo manifest管理</p>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="devops" /><summary type="html"><![CDATA[一些关于项目中多仓库管理技术点的思考]]></summary></entry><entry><title type="html">世界是对数/指数的</title><link href="https://tuobashaoli.github.io/blog/2025/10/26/logarithm/" rel="alternate" type="text/html" title="世界是对数/指数的" /><published>2025-10-26T08:08:18+00:00</published><updated>2025-10-26T08:08:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2025/10/26/logarithm</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2025/10/26/logarithm/"><![CDATA[<h1 id="说点儿啥">说点儿啥</h1>

<p>昨天刚刚参加高中同学的婚礼，发现18岁前的记忆、以及记忆的情感，总是占大脑中的很重的比重,于是就感叹，即使往后的岁月时长更长，也无法超过18岁之前的记忆</p>

<h1 id="考更高的分数">考更高的分数</h1>

<p>从70分提升10分到80分，和从90分提升10分到100分，这俩者付出的努力是不同的，后者所需要的努力远远大于前者,如下图所示，</p>

<p><img src="/assets/images/ln.png" alt="avatar" /></p>

<p>虽然都是提高1分，但是从1分到2分所需要的努力BC远远大于从0分到1分所需要的努力AB，也是边际效用的一种体现</p>

<p>这就是指数/对数关系</p>

<h1 id="怀念青春">怀念青春</h1>

<p>“我想起那天夕阳下的奔跑，那是我逝去的青春”</p>

<p>“幸福的人用童年治愈一生，不幸的人用一生治愈童年”</p>

<p>“白月光的杀伤力就是本人来了也没有用”</p>

<p>“人生果只如初见”</p>

<p>“第一印象最重要”</p>

<p>“江山易改，本性难移”</p>

<p>“有些人20岁就已经死了，80岁才埋”</p>

<p>为什么在时间上，一开始的那一段时间的影响，是如此的重要，请看下图</p>

<p><img src="/assets/images/lnage.png" alt="avatar" /></p>

<p>在时间轴上，2-4和4-6是一样的时间，但是俩者产生的记忆认识，却是AB大于BC</p>

<p>有另一种说法，当你十岁的时候，十岁那一年是你生命的1/10,当你30岁的时候，30岁那一年是你生命的1/30，那自然是十岁那一年的记忆认知更明显</p>

<p>假设一个人的寿命是70岁，显然，他的生命终点是35岁，但是，根据对数函数，记忆中点明显是小于35岁，大概是18岁，所以，可能高中毕业后，我们的大脑能认识的记忆就已经过半了，虽然我们的生命还远远没有到一半。</p>

<h1 id="马太效应">马太效应</h1>

<p>为什么越有钱的人，越容易有钱，请看下图</p>

<p><img src="/assets/images/ewealth.png" alt="avatar" /></p>

<p>在x轴上，虽然财富2是财富1的俩被，但是财富2获取财富的效率，却是财富1的3倍，这就是马太效应，也是对数/指数关系</p>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="other" /><summary type="html"><![CDATA[生活中的一些符合对数/指数特征的现象]]></summary></entry><entry><title type="html">分支模型</title><link href="https://tuobashaoli.github.io/blog/2025/10/25/branch_model/" rel="alternate" type="text/html" title="分支模型" /><published>2025-10-25T09:08:18+00:00</published><updated>2025-10-25T09:08:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2025/10/25/branch_model</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2025/10/25/branch_model/"><![CDATA[<h1 id="说点儿啥">说点儿啥</h1>

<p>在项目代码开发中，往往是使用具有版本管理的开发工具管理代码，比如git，而版本管理中重要的一个功能，就是分支功能，在开发过程中，往往会产生各种分支，而每出现一个分支，就意味着开发成本的增加，所以怎么管理这些分支，就需要好好考虑了</p>

<h1 id="首先master">首先，master</h1>

<p>假设当前有一个git仓库，有一个分支，叫master，如果为了降低分支开发成本的话，那么最省成本的是只用一个分支，比如叫master，需要开发的功能不断地往这上面提交就可以了,如下所示</p>

<p><img src="/assets/images/branch_model_master.png" alt="avatar" /></p>

<p>当项目需要发布的时候，选择对应节点发布就可以了</p>

<p>这应该是最简单的分支模型了</p>

<h1 id="然后release">然后，release</h1>

<p>实际上，在公司里面，一个项目或者仓库，往往都是多人参与的，这个时候就有一个问题，每个人以及每个功能的开发进度不一致，有的人开发快，可能已经将下一个阶段的代码开发完成，有的人开发慢，可能还在修复上一个功能引入的bug，这个时候，如果只有一个分支，就很难找到一个commit，满足发布的需求，如下所示,</p>

<p><img src="/assets/images/branch_model_master_1.png" alt="avatar" /></p>

<p>红色的提交/内容是当前发布不需要的内容，绿色的提交是当前发布需要的内容，这样就找不到可以发布的代码节点</p>

<p>聪明的人就知道，这个时候需要额外一个分支了，如下图所示</p>

<p><img src="/assets/images/branch_model_master_2.png" alt="avatar" /></p>

<p>在靠前的节点，创建一个release分支，<strong>专门用于本次发布</strong>，然后将剩下的需要发布的内容，移植到当前的release分支上，这样就可以满足发布需求了</p>

<h1 id="继续hotfix">继续，hotfix</h1>

<p>某日，正在master分支上开发新的功能，但是，突然，测试人员过来反馈一个V1.0版本的重大bug，需要你立刻响应，进行修复，这个时候，master分支可能已经演进到很后面的阶段了，只能在release分支上临时创建一个新的hotfix分支，用于修复测试，如下所示</p>

<p><img src="/assets/images/branch_model_master_3.png" alt="avatar" /></p>

<p>在hotfix上提交后，验证测试完成后再移植到release分支上</p>

<p>这个时候就有人会问，为啥不能直接向release分支上提交，“你看，master分支都可以直接提交”，实际上，也是可以这么做的，但是为了保证release分支的质量，即release分支上的节点尽可能都是质量可靠的，所以需要创建hotfix分支，进行预先验证和测试</p>

<p>当然，这些修复的内容，也是需要回归主线master分支上的，避免后续的发布出现问题</p>

<h1 id="番外feature">番外，feature</h1>

<p>当一个人往master分支上提交时，很舒服，因为不会像多人那样产生提交<strong>冲突</strong>，比如工程师A新建了一个文件叫def.py，工程师也新建了一个文件叫def.py，那么master分支上这个def.py到底应该是那个内容呢，这就是冲突，更准确地说，是<strong>文本冲突</strong>，</p>

<blockquote>
  <p>与文本冲突相对比的另一种冲突，是<strong>内容冲突</strong>,即在文本上，不存在冲突，比如工程师A在def.py第一行定义变量<code class="language-plaintext highlighter-rouge">var = 1</code>,而工程师B在def.py第十行也定义变量<code class="language-plaintext highlighter-rouge">var = 2</code>,那么在合并俩者的修改时，是不会报文本冲突的，但是，由于工程师A在开发的时候，不知道工程师B在第十行将变量var改成了2，就会一直按照<code class="language-plaintext highlighter-rouge">var = 1</code>的逻辑继续编写代码，这样在运行过程中，就会出现超出预期的异常</p>
</blockquote>

<p>当每天都有来自不同的工程师，大量向master分支提交的时候，就可能出现很多的<strong>文本冲突</strong>，从而造浪费工程师大量精力在解冲突上，于是乎，就可以基于master分支，让每个工程师创建属于自己的feature分支，如下所示</p>

<p><img src="/assets/images/branch_model_master_4.png" alt="avatar" /></p>

<p>这样，工程师就可以在自己的分支上开发，验证测试完善后，再合入master分支，在合入master分支的时候，<strong>一次性解决所有的冲突</strong>，从而节省了开发成本。</p>

<p>注意，随着时间推移，master分支的内容和工程师自己的feature分支内容，可能差距会越来越大，毕竟，master分支往往每天都会有很多提交合入，等到feature合入的时候，可能会有海量的冲突，为了避免到时候冲突太多，建议feature分支的周期不要维护太长，在一个短的周期内，比如2天以内，就完成修改然后合入master。如果feature分支开发周期较长，比如一周，那么就建议每天晚上定时将master最新的代码同步到feature分支，从而避免合入时的海量冲突。</p>

<h1 id="总结">总结</h1>

<p>在大型项目中，比如Android项目，往往需要几十上百个仓库联合在一起，从而编译出项目产物，而不同的仓库，并不是全部都能按照计划完美无bug地进行，也不可能让每个仓库的所有开发人员在每次发布的时候，全体停下来，全身心投入本次发布，（当然，有些重大发布还是需要的），有些仓库可能超过计划进度完成，有些仓库可能落后计划进度，这都是很常见的情况。</p>

<p>这个时候，就会在项目的计划节点，让相关仓库创建出专门用于本次发布的分支，然后将本次需要的功能都移植到release分支，然后编译测试，等bug收敛后，正式将release的内容发布并创建TAG版本号。</p>

<p>所以<strong>创建release分支，即是方便聚合本次发布的功能代码，也是方便在发布过程中，提交修复代码，发布是需要一定时间的，比如持续一周</strong></p>

<h1 id="另一种分支模型">另一种分支模型</h1>

<p>在小型项目中，比如小的前后端网站，往往开发人员不是很多，并且代码提交量不算很多的情况下，往往都可以按时完成开发任务，这个时候，就可以直接将代码进行发布，如下所示</p>

<p><img src="/assets/images/branch_model_master_5.png" alt="avatar" /></p>

<p>因为代码都是按照计划进行的，所以不存在本次发布不需要的代码，从而可以直接移植到master分支，</p>

<blockquote>
  <p>其实这里的dev分支就是相当于之前的master分支，master分支相当于release分支</p>
</blockquote>

<p>这种分支模型下，可以直观地在master分支上看到过往的各个版本历程，因为都是从master分支上发布的。</p>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="devops" /><summary type="html"><![CDATA[项目开发中的分支模型]]></summary></entry><entry><title type="html">gerrit插件开发</title><link href="https://tuobashaoli.github.io/blog/2025/08/26/gerrit_plugin/" rel="alternate" type="text/html" title="gerrit插件开发" /><published>2025-08-26T21:29:18+00:00</published><updated>2025-08-26T21:29:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2025/08/26/gerrit_plugin</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2025/08/26/gerrit_plugin/"><![CDATA[<h1 id="准备环境">准备环境</h1>

<p>首先是外网连接权限，这个是大前提，如果你使用的是公司的网络，大概率就没这个问题，毕竟，使用公司网络是合法的，🐶</p>

<p>官方的插件都是在网站<code class="language-plaintext highlighter-rouge">https://gerrit-ci.gerritforge.com/</code>上面编译发布的，如果想知道编译的环境，直接从官网上查看日志就可以了</p>

<p><img src="/assets/images/gerrit_checks_build.png" alt="avatar" /></p>

<p>比如上图，看出来需要jdk21 和 bazel 7.2.1,那么就去安装这个环境</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>openjdk-21-jdk-headless
</code></pre></div></div>

<p>安装bazel的方式在官网上<code class="language-plaintext highlighter-rouge">https://bazel.build/install/ubuntu?hl=zh-cn</code></p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://github.com/bazelbuild/bazel/releases/download/7.2.1/bazel-7.2.1-installer-linux-x86_64.sh
<span class="nb">chmod</span> +x bazel-7.2.1-installer-linux-x86_64.sh
./bazel-7.2.1-installer-linux-x86_64.sh <span class="nt">--user</span>
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PATH</span><span class="s2">:</span><span class="nv">$HOME</span><span class="s2">/bin"</span>
</code></pre></div></div>

<h1 id="下载gerrit代码">下载gerrit代码</h1>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># 下载整个gerrit的代码</span>
 git clone <span class="s2">"https://gerrit.googlesource.com/gerrit"</span> gerrit2
 <span class="nb">cd </span>gerrit2
 <span class="c"># 注意一定要下载submodule，否则就会有奇怪的报错</span>
 git submodule update <span class="nt">--init</span> <span class="nt">--recursive</span>
</code></pre></div></div>

<h1 id="下载需要编译的插件的代码">下载需要编译的插件的代码</h1>

<p>这里用官方的checks插件为例，进行编译</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://gerrit.googlesource.com/plugins/checks
<span class="c"># 把插件代码复制到gerrit仓库源码的plugins目录里面</span>
<span class="nb">cp</span> <span class="nt">-r</span> checks gerrit2/plugins/

</code></pre></div></div>

<h1 id="开始编译">开始编译</h1>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>gerrit2
bazel build //plugins/checks:checks
</code></pre></div></div>

<p>我在wsl里面进行编译的，这里报错缺少zip工具，那么我就安装一个zip工具继续编译</p>

<p><img src="/assets/images/gerrit_plugin_build.png" alt="avatar" /></p>

<p>终成正果，阿弥陀佛 ✔</p>

<p><img src="/assets/images/gerrit_plugin_build_pass.png" alt="avatar" /></p>

<h1 id="checks插件">checks插件</h1>

<p>checks插件是官方提供的前后端都一起剩下的插件，理解了这个插件，就基本掌握了gerrit的插件开发</p>

<p>首先看一下官方画的大饼</p>

<p><code class="language-plaintext highlighter-rouge">https://gerrit-review.googlesource.com/Documentation/pg-plugin-checks-api.html</code></p>

<p><img src="/assets/images/user-checks-overview.png" alt="avatar" /></p>

<p>这里的功能包括</p>

<ol>
  <li>和标签比如Code-Style联动，检查项失败标签自动-1</li>
  <li>展示失败详情和链接，这个链接可以是自定义的具体CI上的检查详情</li>
  <li>红色表示失败，黄色表示告警，绿色表示成功，灰色表示等待或运行，蓝色表示有信息，展示各个检查项的状态</li>
  <li>可以点击重试</li>
  <li>可以给各类检查结果贴上标签</li>
  <li>可以展示大文本，用于描述检查项更具体的信息</li>
</ol>

<p>但是，上面的这么多功能，目前的checks插件里面没有使用起来，官方只是在上面这个截图上演示一下而已，实际的代码中，很多功能都没被用上</p>

<h1 id="从fetcherts谈起">从fetcher.ts谈起</h1>

<p>为什么从这个文件谈起呢，应为就是这个文件控制着checks面板的页面显示样式</p>

<p>看代码，如下，导入了@gerritcodereview里面的各类包</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import {PluginApi} from '@gerritcodereview/typescript-api/plugin';
import {RestPluginApi} from '@gerritcodereview/typescript-api/rest';
import {
  Category,
  ChangeData,
  CheckRun,
  ChecksProvider,
  LinkIcon,
  ResponseCode,
  RunStatus,
} from '@gerritcodereview/typescript-api/checks';
</code></pre></div></div>

<p>这些包的具体位置，是在编译后的<code class="language-plaintext highlighter-rouge">plugins/node_modules/</code>目录里面，</p>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="gerrit" /><summary type="html"><![CDATA[gerrit插件开发]]></summary></entry><entry><title type="html">深度学习</title><link href="https://tuobashaoli.github.io/blog/2024/02/20/deeplearning/" rel="alternate" type="text/html" title="深度学习" /><published>2024-02-20T21:34:18+00:00</published><updated>2024-02-20T21:34:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2024/02/20/deeplearning</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2024/02/20/deeplearning/"><![CDATA[<h1 id="suiyuan说说">suiyuan说说</h1>

<p>最新sora发布，让ai的热度更上一层，而我自己工作在ai相关的行业，平常也试着在使用ai提升工作效率，</p>

<p>也可能感觉很有必要学习这些内容，这些可能是必备的技能了</p>

<p>当然，大量的内容都是从网上搜寻的，自己学力有限，只能拾人牙慧了</p>

<h1 id="从拟合一元一次函数说起">从拟合一元一次函数说起</h1>

<p>先记录一个视频：<a href="https://www.bilibili.com/video/BV1sz4y1c7AK/?share_source=copy_web&amp;vd_source=1e1a8470626ff3354bcd0d9c492ad7d2">再讲梯度下降算法</a></p>

<p>不知道大家在中学时段有没有做过一类题目，给出，1 一组映射，2 已知是一元一次函数，让大家求出或者选择出最接近的函数。</p>

<p>如果映射是(1,2)分别映射（3,5），那对于这个问题，实际上就是组个二元一次方程组就可以解决了，当然，对于这组映射，聪明的人都一眼看出是<code class="language-plaintext highlighter-rouge">y=2x+1</code>。</p>

<p>如果映射是（1.0231,1.9887,4.1008）映射（2.998,5.1212,9.02323)，以及不容易一眼看出的映射，这时，我们就假设这个函数是<code class="language-plaintext highlighter-rouge">y=ax+b</code>,然后设法求出a和b了。</p>

<p>那么如何求a和b呢，想想，我们的目标是找出a和b，让y=ax+b的结果和真实值差距尽可能的小，那么衡量a和b的拟合程度好坏的就是拿a和b去预测出的值和真实值之间的差距，差距越小，说明a、b就拟合的越好</p>

<p>那么第一时间想到的计算这个差距的方式就是将真实值和预测值相减，同时为了避免正负差值的相互抵消带来的影响，使用平方,如下所示</p>

<p><img src="/assets/images/dl1.PNG" alt="avatar" /></p>

\[J(a,b) = \sum_{i=1}^m (ax_i + b - y_i)²\]

<p>m可以是给出的真实数据组数，$x_i$和 $y_i$就是这些真实数据，上述函数就是关于变量a和b的函数，我们的目标是让J(a,b)的值最小，那么对于这个二次函数，可以用数学方法去寻找。</p>

<p>这里，我们先随机给a和b赋值，比方说a=-2，b=8,那么计算出的映射就是（5.9538，4.0026，-0.2016），和真实的结果差距较大，需要调整a和b去进一步缩小差距，</p>

<p>怎么去调整呢？观察函数J，为了让其值更小，需要将a向<strong>当前所在点的斜率反方向去调整</strong>，为什么？对这个二次函数来说，当一点的斜率大于0，函数值会随着自变量增加而增加，反之则减小；那么斜率大于0，a必须要减小，斜率小于0，a必须要增加，这样才能让函数J向更小的值去变化。</p>

<p>那么一次变化多少呢？如果太大，过了最佳的值，下次可能就会往回退，如果太小，就要经过更多的轮次才能找到最佳的值，所以这里的一次变化的大小需要谨慎设置。</p>

<p>如以下的公式，ξ就表示一次变化的大小，又称为<strong>步长</strong>,△J/△a  （$\frac{\delta J}{\delta a}$）和△J/△b （$\frac{\delta J}{\delta b}$）分表表示函数J在这俩个变量上的斜率，减号表示相反的方向</p>

<p><img src="/assets/images/dl2.PNG" alt="avatar" /></p>

\[a = a - ξ \frac{\delta J}{\delta a}\]

\[b = b - ξ \frac{\delta J}{\delta b}\]

<p>通过以上俩个公式，结合计算结果，得知此时，a需要增加，b需要减小</p>

<p>经过多轮迭代后，就会让a和b的值更加接近于拟合效果更好的值</p>

<p>回味一下，这里例子中又a和b俩个变量，可以组成一个平面，(a,b)的变化方向，实际上就是朝着这个平面上(-△J/△a,-△J/△b)   这个向量的方向去变化的，这里可以结合<a href="https://www.zhihu.com/question/323820696/answer/3178822065">梯度向量为什么是函数增加最快的方向？</a> 这个回答里面的图像去理解下，而向量(△J/△a,△J/△b)就是<strong>梯度向量</strong>，向量(-△J/△a,-△J/△b)所指的方向，就是梯度向量的反方向。</p>

<p>我说的有些啰嗦，上面俩个链接里面的内容更加丰富可靠。</p>

<h1 id="万里长征第一步啊">万里长征第一步啊</h1>

<p>通过上面的例子，其实已经初步接触到了深度学习的一些概念，比如梯度向量(△J/△a,△J/△b)，梯度下降，步长ξ，损失函数J，当然还有没接触到的，比如步长ξ在每次迭代中如何调整所涉及的优化方案，如何选取训练数据、批量/随机/小批量，等等概念就要后续渐渐学习了</p>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="deeplearning" /><summary type="html"><![CDATA[learn deeply]]></summary></entry><entry><title type="html">滑动窗口算法的一个应用</title><link href="https://tuobashaoli.github.io/blog/2023/12/27/algo/" rel="alternate" type="text/html" title="滑动窗口算法的一个应用" /><published>2023-12-27T23:17:18+00:00</published><updated>2023-12-27T23:17:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2023/12/27/algo</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2023/12/27/algo/"><![CDATA[<h1 id="我是在瞎说">我是在瞎说</h1>

<p>最近遇到一个问题，就是jenkins job上传日志到es的时候，需要针对关键字前后各100行进行上传，其余的全部丢弃，（别问我为什么丢弃，es不至于这点儿日志里都撑不住），但是日志是实时的流式生成，logstash似乎不直接支持这类处理，所以需要自己想点儿法子</p>

<h1 id="我爱说实话">我爱说实话</h1>

<p>那么维护一个滑动窗口就很有必要了，</p>

<ol>
  <li>初始化一个滑动窗口，这个窗口容量为201，内容是空的</li>
  <li>初始化一个flag = false，表明窗口内有没有包含关键字的一行日志</li>
  <li>每当生成一行日志，
    <ol>
      <li>如果这行日志不包括关键字，那么把这行日志入栈滑动窗口</li>
      <li>如果包括关键字，那么把这行日志入栈滑动窗口，并记录关键字位置/坐标1，并记录此时已经找到关键字了flag = true，准备100行后上传日志，即把窗口的剩余容量调整为100</li>
      <li>如果接下来又遇到了一行包括关键字的日志，但是窗口还没填满，那么再次将窗口的剩余容量调整为100</li>
      <li>一旦窗口满了，如果flag == true，将窗口内的所有日志上传，然后清空重置窗口和flag，当然，flag == false，保留窗口内最新的100个内容，将剩余容量调整为101</li>
      <li>循环以上1-4即可</li>
    </ol>
  </li>
</ol>

<p>时间为O(n)，空间大概也是O(n)吧，不是很确定</p>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="algo" /><summary type="html"><![CDATA[slide window]]></summary></entry><entry><title type="html">gerrit js plugin</title><link href="https://tuobashaoli.github.io/blog/2023/01/14/gerrit/" rel="alternate" type="text/html" title="gerrit js plugin" /><published>2023-01-14T21:56:18+00:00</published><updated>2023-01-14T21:56:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2023/01/14/gerrit</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2023/01/14/gerrit/"><![CDATA[<h1 id="随便说下">随便说下</h1>

<p>当前做了一些gerrit的插件开发，主要是前端的js插件，记录一些坑</p>

<p>主要文档</p>

<ul>
  <li>
    <p><a href="https://www.gerritcodereview.com/plugins.html">官方插件说明</a></p>
  </li>
  <li>
    <p>自己搭建的gerrit的前端插件地址： <code class="language-plaintext highlighter-rouge">http://localhost:89/Documentation/pg-plugin-dev.html</code></p>
  </li>
</ul>

<h1 id="入门">入门</h1>

<p>文档中给了一个最简单的前端插件使用，将下面这个js放到gerrit安装目录中的plugins目录中，就可以直接生效了</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Gerrit</span><span class="p">.</span><span class="nx">install</span><span class="p">(</span><span class="nx">plugin</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">domHook</span> <span class="o">=</span> <span class="nx">plugin</span><span class="p">.</span><span class="nx">hook</span><span class="p">(</span><span class="dl">'</span><span class="s1">reply-text</span><span class="dl">'</span><span class="p">);</span>
  <span class="nx">domHook</span><span class="p">.</span><span class="nx">onAttached</span><span class="p">(</span><span class="nx">element</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">element</span><span class="p">.</span><span class="nx">content</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
    <span class="c1">// element.content is a reply dialog text area.</span>
  <span class="p">});</span>
<span class="p">});</span>

</code></pre></div></div>

<h1 id="hook">hook</h1>

<p>这个<code class="language-plaintext highlighter-rouge">reply-text</code>就是页面上的一个组件，可以hook的组件必须是官方支持的，具体那些页面组件可以hook，在<code class="language-plaintext highlighter-rouge">http://localhost:89/Documentation/pg-plugin-endpoints.html</code></p>

<p>这里面就有一个比较坑人的地方，就是文档里面提示了有一些参数可以使用，比如当hook的对象是<code class="language-plaintext highlighter-rouge">change-view-integration</code>的时候，提示有change和revision这个俩个参数可以使用，</p>

<p>但是结合上面的示例代码，发现根本没有这俩个参数！翻遍了所有文档，都没见到如何使用这俩个参数，试了无数遍，最终发现，这俩个参数是存在<code class="language-plaintext highlighter-rouge">element</code>对象里面的字段，所以可以如下调用</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Gerrit.install(plugin =&gt; {
  const endpoint = 'change-view-integration';
  plugin.hook(endpoint).onAttached(element =&gt; {
    console.log(element.change);
    console.log(element.revision);
  });
});
</code></pre></div></div>

<h1 id="promise">Promise</h1>

<p>在查看到高级api的时候，发现<code class="language-plaintext highlighter-rouge">restApi</code>有接口返回Promise对象,所以需要学习一下</p>

<h1 id="一个复制出所有topic其他提交的按钮">一个复制出所有topic其他提交的按钮</h1>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Gerrit</span><span class="p">.</span><span class="nx">install</span><span class="p">(</span><span class="nx">plugin</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">endpoint</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">commit-container</span><span class="dl">'</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">domHook</span> <span class="o">=</span> <span class="nx">plugin</span><span class="p">.</span><span class="nx">hook</span><span class="p">(</span><span class="nx">endpoint</span><span class="p">);</span>

  <span class="kd">function</span> <span class="nx">copyToClipboard</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">clipboardData</span><span class="p">)</span> <span class="p">{</span>
                <span class="nb">window</span><span class="p">.</span><span class="nx">clipboardData</span><span class="p">.</span><span class="nx">setData</span><span class="p">(</span><span class="dl">'</span><span class="s1">text</span><span class="dl">'</span><span class="p">,</span> <span class="nx">s</span><span class="p">);</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nb">document</span><span class="p">.</span><span class="nx">oncopy</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
                        <span class="nx">e</span><span class="p">.</span><span class="nx">clipboardData</span><span class="p">.</span><span class="nx">setData</span><span class="p">(</span><span class="dl">'</span><span class="s1">text</span><span class="dl">'</span><span class="p">,</span> <span class="nx">s</span><span class="p">);</span>
                        <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
                        <span class="nb">document</span><span class="p">.</span><span class="nx">oncopy</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
                    <span class="p">}</span>
                <span class="p">})(</span><span class="nx">s</span><span class="p">);</span>
                <span class="nb">document</span><span class="p">.</span><span class="nx">execCommand</span><span class="p">(</span><span class="dl">'</span><span class="s1">Copy</span><span class="dl">'</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>


  <span class="nx">domHook</span><span class="p">.</span><span class="nx">onAttached</span><span class="p">(</span><span class="nx">element</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">el</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">));</span>
    <span class="nx">el</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">copy</span><span class="dl">"</span>
    <span class="nx">el</span><span class="p">.</span><span class="nx">onclick</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(){</span>
      <span class="nx">topic_changes</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">app</span><span class="dl">"</span><span class="p">).</span><span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">app-element</span><span class="dl">"</span><span class="p">).</span><span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">main gr-change-view</span><span class="dl">"</span><span class="p">).</span><span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">relatedChanges</span><span class="dl">"</span><span class="p">).</span><span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">gr-endpoint-decorator</span><span class="dl">"</span><span class="p">).</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#sameTopic &gt; gr-related-collapse</span><span class="dl">"</span><span class="p">).</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="dl">"</span><span class="s2">gr-related-change</span><span class="dl">"</span><span class="p">)</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">topic_changes</span><span class="p">.</span><span class="nx">length</span><span class="p">);</span>
    <span class="nx">changes</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">index</span><span class="o">&lt;</span> <span class="nx">topic_changes</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="nx">index</span><span class="o">++</span><span class="p">){</span>
      <span class="nx">change_href</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">,</span><span class="dl">""</span><span class="p">)</span> <span class="o">+</span> <span class="nx">topic_changes</span><span class="p">[</span><span class="nx">index</span><span class="p">].</span><span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">a</span><span class="dl">"</span><span class="p">).</span><span class="nx">getAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">href</span><span class="dl">"</span><span class="p">);</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">change_href</span><span class="p">);</span>
      <span class="nx">changes</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">change_href</span><span class="p">);</span>
    <span class="p">}</span>
      <span class="nx">copyToClipboard</span><span class="p">(</span><span class="nx">changes</span><span class="p">.</span><span class="nx">join</span><span class="p">());</span>

    <span class="p">}</span>
  <span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="gerrit" /><summary type="html"><![CDATA[gerrit js it]]></summary></entry><entry><title type="html">prolog</title><link href="https://tuobashaoli.github.io/blog/2022/08/15/prolog/" rel="alternate" type="text/html" title="prolog" /><published>2022-08-15T18:37:18+00:00</published><updated>2022-08-15T18:37:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2022/08/15/prolog</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2022/08/15/prolog/"><![CDATA[<h1 id="随便说说">随便说说</h1>

<p>最近在写gerrit的rules.pl，一开始我以为是perl语言，看了文档才发现这个是prolog语言，于是，就搜文档看看，这个语言的思路和平常的开发语言很不一样，很清奇</p>

<h1 id="一个例子">一个例子</h1>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">% 表示love(X,Y)为true，语句以.结尾，X -&gt; love -&gt; Y，这个是单向的，不能理解成Y-&gt;love-&gt;X</span>
<span class="ss">love</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span><span class="nv">Y</span><span class="p">).</span>

<span class="c1">% 表示当love(X,Y)为true、且love(Y,X)为true的时候，lovers(X,Y)为true，右边是左边的充要条件</span>
<span class="ss">lovers</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span><span class="nv">Y</span><span class="p">):-</span><span class="ss">love</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span><span class="nv">Y</span><span class="p">),</span><span class="ss">love</span><span class="p">(</span><span class="nv">Y</span><span class="p">,</span><span class="nv">X</span><span class="p">).</span>

<span class="c1">%</span>
</code></pre></div></div>

<h1 id="一些理解">一些理解</h1>

<ul>
  <li>prolog程序没有特定的运行顺序，其运行顺序是由电脑决定的，而不是编程序的人。</li>
  <li>prolog的原理就是关系数据库，它是建立在关系数据库的基础上的。</li>
  <li>强大的递归功能</li>
</ul>

<h1 id="知识点">知识点</h1>

<h2 id="事实">事实</h2>

<p>事实（facts）是prolog中最简单的谓词（predicate）。它和关系数据库中的记录十分相似。</p>

<p>语法结构如下
<code class="language-plaintext highlighter-rouge">pred(arg1, arg2, … argN).</code></p>

<p>其中pred为谓词的名称。arg1，…为参数，共有N个。</p>

<p>参数可以是以下四种之一：</p>
<ol>
  <li>整数（integer)</li>
  <li>原子（atom）
  由小写字母开头的字符串。</li>
  <li>变量（variable）
  由大写字母或下划线（_）开头。</li>
  <li>结构（structure）</li>
</ol>

<p>可以将事实理解成，向数据库里面写数据。</p>

<h2 id="常用的内部谓词">常用的内部谓词</h2>

<ol>
  <li>write/1 把参数当作字符串输出到屏幕</li>
  <li>nl/0 在屏幕上输出回车</li>
  <li>tab/1 输出n个空格</li>
  <li>not/1 取反</li>
  <li>=/2 此谓词当它的两个参数能够联合时成功，反之则失败。它的语法如下：</li>
</ol>

<h2 id="规则">规则</h2>

<p>pred(x):- pred(Y),pred(Z).</p>

<p>前面我们已经说过，谓词是使用一系列的子句来定义的。以前我们所学习的子句是事实，现在让我们来看看规则吧。规则的实质就是储存起来的查询。它的语法如下：</p>

<p>head :- body
其中，</p>

<p>head 是谓词的定义部分，与事实一样，也包括谓词名和谓词的参数说明。
:- 连接符，一般可以读作‘如果’。
body 一个或多个目标，与查询相同。</p>

<h2 id="数学计算">数学计算</h2>

<p>X is <数学表达式></数学表达式></p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">X</span> <span class="ss">is</span> <span class="m">3</span><span class="o">*</span><span class="p">(</span><span class="m">1</span><span class="o">+</span><span class="m">2</span><span class="p">).</span>
<span class="nv">X</span> <span class="o">&gt;</span> <span class="nv">Y</span>
<span class="nv">X</span> <span class="o">&lt;</span> <span class="nv">Y</span>
<span class="nv">X</span> <span class="o">&gt;=</span> <span class="nv">Y</span>
<span class="nv">X</span> <span class="o">=&lt;</span> <span class="nv">Y</span>
</code></pre></div></div>

<p>= 时赋值不计算， is 计算 而=:=是两头都计算</p>

<h2 id="结构">结构</h2>

<p>functor(arg1,arg2,…)</p>

<p>类似于结构体</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location_s(object(candle, red, small, 1), kitchen).
</code></pre></div></div>

<h2 id="联合">联合</h2>

<p>变量&amp;任何项目: 变量可以与任何项目绑定，其中也包括变量
原始项目&amp;原始项目: 两个原始项目（原子或整数）只有当它们相同时才能联合。
结构&amp;结构: 如果两个结构的每个相应的参数能联合，那么这两个结构可以联合。</p>

<p>=(arg1, arg2)</p>

<p>为了方便阅读，也可以写成如下形式：</p>

<p>arg1 = arg2</p>

<h2 id="列表">列表</h2>

<p>list [a,b,c]
[X|Y] X 是列表头(head), 它可以是一个列表，或其他任何数据结构
Y 是列表尾(tail), 它只能可以是一个列表</p>

<h1 id="gerrit">gerrit</h1>

<p>gerrit中的设置submit的规则时，需要设置成，至少有一个+2，并且总分是4分及以上才能submit</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">sum_list</span><span class="p">([],</span> <span class="m">0</span><span class="p">).</span>
<span class="ss">sum_list</span><span class="p">([</span><span class="nv">H</span> <span class="p">|</span> <span class="nv">Rest</span><span class="p">],</span> <span class="nv">Sum</span><span class="p">)</span> <span class="p">:-</span> <span class="ss">sum_list</span><span class="p">(</span><span class="nv">Rest</span><span class="p">,</span><span class="nv">Tmp</span><span class="p">),</span> <span class="nv">Sum</span> <span class="ss">is</span> <span class="nv">H</span> <span class="o">+</span> <span class="nv">Tmp</span><span class="p">.</span>

<span class="c1">% 这部分是统计2的个数</span>
<span class="ss">count_twos</span><span class="p">([],</span> <span class="m">0</span><span class="p">).</span> <span class="c1">% 初始条件</span>
<span class="ss">count_twos</span><span class="p">([</span><span class="m">2</span><span class="p">|</span><span class="nv">T</span><span class="p">],</span> <span class="nv">N</span><span class="p">)</span> <span class="p">:-</span> <span class="ss">count_twos</span><span class="p">(</span><span class="nv">T</span><span class="p">,</span> <span class="nv">N1</span><span class="p">),</span> <span class="nv">N</span> <span class="ss">is</span> <span class="nv">N1</span> <span class="o">+</span> <span class="m">1</span><span class="p">.</span> <span class="c1">% 动态规划之表第一个元素是2</span>
<span class="ss">count_twos</span><span class="p">([</span><span class="nv">H</span><span class="p">|</span><span class="nv">T</span><span class="p">],</span> <span class="nv">N</span><span class="p">)</span> <span class="p">:-</span> <span class="nv">H</span> <span class="err">\</span><span class="o">=</span> <span class="m">2</span><span class="p">,</span> <span class="ss">count_twos</span><span class="p">(</span><span class="nv">T</span><span class="p">,</span> <span class="nv">N</span><span class="p">).</span> <span class="c1">% 动态规划之表第一个元素不是2</span>


<span class="ss">add_category_min_score</span><span class="p">(</span><span class="nv">In</span><span class="p">,</span> <span class="nv">Category</span><span class="p">,</span> <span class="nv">Min</span><span class="p">,</span>  <span class="nv">P</span><span class="p">)</span> <span class="p">:-</span>
    <span class="ss">findall</span><span class="p">(</span><span class="nv">X</span><span class="p">,</span> <span class="ss">gerrit</span><span class="o">:</span><span class="ss">commit_label</span><span class="p">(</span><span class="ss">label</span><span class="p">(</span><span class="nv">Category</span><span class="p">,</span><span class="nv">X</span><span class="p">),</span><span class="nv">R</span><span class="p">),</span><span class="nv">Z</span><span class="p">),</span>
    <span class="ss">count_twos</span><span class="p">(</span><span class="nv">Z</span><span class="p">,</span> <span class="nv">Twos</span><span class="p">),</span> <span class="c1">% 统计2的个数</span>
    <span class="nv">Twos</span> <span class="o">&gt;=</span> <span class="m">1</span><span class="p">,</span> <span class="c1">% 如果打分列表中的2的个数大于1，即至少有一个2</span>
    <span class="ss">sum_list</span><span class="p">(</span><span class="nv">Z</span><span class="p">,</span> <span class="nv">Sum</span><span class="p">),</span>
    <span class="c1">% member(2, Z),!,member没法直接用，这里会报错</span>
    <span class="nv">Sum</span> <span class="o">&gt;=</span> <span class="nv">Min</span><span class="p">,</span> <span class="p">!,</span>
    <span class="ss">gerrit</span><span class="o">:</span><span class="ss">commit_label</span><span class="p">(</span><span class="ss">label</span><span class="p">(</span><span class="nv">Category</span><span class="p">,</span> <span class="nv">V</span><span class="p">),</span> <span class="nv">U</span><span class="p">),</span>
    <span class="nv">V</span> <span class="o">&gt;=</span> <span class="m">1</span><span class="p">,</span>
    <span class="p">!,</span>
    <span class="nv">P</span> <span class="o">=</span> <span class="p">[</span><span class="ss">label</span><span class="p">(</span><span class="nv">Category</span><span class="p">,</span><span class="ss">ok</span><span class="p">(</span><span class="nv">U</span><span class="p">))</span> <span class="p">|</span> <span class="nv">In</span><span class="p">].</span>

<span class="ss">add_category_min_score</span><span class="p">(</span><span class="nv">In</span><span class="p">,</span> <span class="nv">Category</span><span class="p">,</span><span class="nv">Min</span><span class="p">,</span><span class="nv">P</span><span class="p">)</span> <span class="p">:-</span>
    <span class="nv">P</span> <span class="o">=</span> <span class="p">[</span><span class="ss">label</span><span class="p">(</span><span class="nv">Category</span><span class="p">,</span><span class="ss">need</span><span class="p">(</span><span class="nv">Min</span><span class="p">))</span> <span class="p">|</span> <span class="nv">In</span><span class="p">].</span>

<span class="ss">submit_rule</span><span class="p">(</span><span class="nv">S</span><span class="p">)</span> <span class="p">:-</span>
    <span class="ss">gerrit</span><span class="o">:</span><span class="ss">default_submit</span><span class="p">(</span><span class="nv">X</span><span class="p">),</span>
    <span class="nv">X</span> <span class="o">=..</span> <span class="p">[</span><span class="ss">submit</span> <span class="p">|</span> <span class="nv">Ls</span><span class="p">],</span>
    <span class="ss">gerrit</span><span class="o">:</span><span class="ss">remove_label</span><span class="p">(</span><span class="nv">Ls</span><span class="p">,</span><span class="ss">label</span><span class="p">(</span><span class="ss">'Code-Review'</span><span class="p">,</span><span class="nv">_</span><span class="p">),</span><span class="nv">NoCR</span><span class="p">),</span>
    <span class="ss">add_category_min_score</span><span class="p">(</span><span class="nv">NoCR</span><span class="p">,</span><span class="ss">'Code-Review'</span><span class="p">,</span> <span class="m">4</span><span class="p">,</span> <span class="nv">Labels</span><span class="p">),</span>
    <span class="nv">S</span> <span class="o">=..</span> <span class="p">[</span><span class="ss">submit</span> <span class="p">|</span> <span class="nv">Labels</span><span class="p">].</span>
</code></pre></div></div>

<p>通过以上例子发现，prolog语言实际上非常适合用来实现动态规划，简直是完美符合</p>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="prolog" /><summary type="html"><![CDATA[prolog it]]></summary></entry><entry><title type="html">make入门2</title><link href="https://tuobashaoli.github.io/blog/2022/06/16/make2/" rel="alternate" type="text/html" title="make入门2" /><published>2022-06-16T17:50:18+00:00</published><updated>2022-06-16T17:50:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2022/06/16/make2</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2022/06/16/make2/"><![CDATA[<h3 id="随便说说">随便说说</h3>

<p>作为一个集成构建工程师，一直对古老的构建工具make都是一直半解，这样下去是不行的，所以现在需要认真学习了</p>

<h3 id="一个简单的操作">一个简单的操作</h3>

<p>当你的工作目录下有一个叫makefile的文件，并且里面的内容是</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a.txt: b.txt c.txt
	<span class="nb">cat </span>b.txt c.txt <span class="o">&gt;</span> a.txt
</code></pre></div></div>

<p>并且，工作目录下也有b.txt和c.txt文件，那么当你执行<code class="language-plaintext highlighter-rouge">make a.txt</code>的时候，就会在控制台输出<code class="language-plaintext highlighter-rouge">cat b.txt c.txt &gt; a.txt</code>，并且生成了文件a.txt。</p>

<p>注意，<strong>第二行开头必须是一个tab</strong></p>

<h4 id="默认配置文件名是makefile">默认配置文件名是makefile</h4>

<p>当直接执行<code class="language-plaintext highlighter-rouge">make a.txt</code>，这里的a.txt就是在makefile定义的目标名，默认配置文件名是makefile，当然也可以手动指定自己命名的makefile，比如使用<code class="language-plaintext highlighter-rouge">make -f mymakefile a.txt</code>，那么就会使用mymakefile文件中的内容执行构建了。</p>

<p>其实大多数构建系统都有这样的规则，比如使用docker打包镜像的时候，就是默认的Dockerfile文件名就叫Dockefile。</p>

<h4 id="makefile的基本格式">makefile的基本格式</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[目标名]: [依赖]
[tab][shell 命令]
</code></pre></div></div>

<p>目标名就是执行make时所指定的，依赖一般是文件，make会在构建前确认依赖是否存在，shell命令就是执行具体的编译命令。</p>

<p>这么看上去，就很像在jenkins中编写groovy的dsl配置文件。</p>

<h4 id="执行make构建">执行make构建</h4>

<p>很简单，就是make加上目标名即可，实际make做的事情就是，找到对应的目标，然后检查目标的依赖是否存在，然后执行对应的语句。</p>

<h3 id="makefile文件语法">makefile文件语法</h3>

<p>通过上面的例子，明白了makefile文件就是一种约定，约定与执行间有着对应关系，这其实就是想导演手中的剧本，当然，在it领域，我们称之为脚本。</p>

<h4 id="目标可能是文件也可能不是文件伪目标">目标可能是文件，也可能不是文件（伪目标）</h4>

<p>a.txt是一个文件，当执行make的时候，如果发现a.txt已存在，并且依赖文件没有改动，即modify时间没变，那么就不会执行构建，</p>

<p>当执行目标不是文件，比如删除文件</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>clean:
	rm *.o
</code></pre></div></div>

<p>那么，如果这个时候真的有个文件叫clean，那么这个clean就不会被执行，所以需要<strong>伪目标</strong>，</p>

<p>使用<code class="language-plaintext highlighter-rouge">.PHONY</code>去声明伪目标即可，如下</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.PHONY: clean
clean:
        rm *.o temp
</code></pre></div></div>

<h4 id="可以使用recipeprefix指定命令前缀">可以使用RECIPEPREFIX指定命令前缀</h4>

<p>默认使用tab键开始识别shell命令，实际上可以使用RECIPEPREFIX去自定义这标识，下面这个例子就是使用<code class="language-plaintext highlighter-rouge">&gt;</code>去做命令开始标识</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.RECIPEPREFIX = &gt;
all:
&gt;echo hello world
</code></pre></div></div>

<h4 id="shell语句默认在行与行之间无联系">shell语句默认在行与行之间无联系</h4>

<p>如下是无法使用变量foo的</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var-lost:
    export foo=bar
    echo "foo=[$$foo]"
</code></pre></div></div>

<p>有俩种方法，一种是携程一行，可以直接写在一行，也可以用反斜杠表示一行</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var-kept:
    export foo=bar; echo "foo=[$$foo]"
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var-kept:
    export foo=bar; \
    echo "foo=[$$foo]"
</code></pre></div></div>

<p>另一种是使用<code class="language-plaintext highlighter-rouge">.ONESHELL</code>命令</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.ONESHELL:
var-kept:
    export foo=bar;
    echo "foo=[$$foo]"
</code></pre></div></div>

<h4 id="注释默认会被打印可以被关闭打印">注释默认会被打印，可以被关闭打印</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cmd:
	# 这个注释会被打印
    @# 这个注释不会被打印
</code></pre></div></div>

<h4 id="模式匹配">模式匹配</h4>

<p>Make命令允许对文件名，进行类似正则运算的匹配，主要用到的匹配符是%。比如，假定当前目录下有 f1.c 和 f2.c 两个源码文件，需要将它们编译为对应的对象文件</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>%.o: %.c
</code></pre></div></div>

<p>等同于</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>f1.o: f1.c
f2.o: f2.c
</code></pre></div></div>

<h4 id="makefile的变量">makefile的变量</h4>

<p>在makefile中，有如下的方法定义变量</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VARIABLE = value
# 在执行时扩展，允许递归扩展，即展开makefile后最终的值

VARIABLE := value
# 在定义时扩展，即在写这赋值语句的地方付给变量的值

VARIABLE ?= value
# 只有在该变量为空时才设置值。

VARIABLE += value
# 将值追加到变量的尾端。
</code></pre></div></div>

<p><strong>使用shell的变量需要使用俩个$$符号</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>p:
	echo $$HOME
</code></pre></div></div>

<h4 id="内置变量">内置变量</h4>

<p>使用<code class="language-plaintext highlighter-rouge">make -p -f /dev/null</code>可以查看内置变量</p>

<p>常用的内置变量</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">$(CC)</code> 指向当前的C的编译器</li>
  <li><code class="language-plaintext highlighter-rouge">$(CXX)</code> 当前的C++的编译器</li>
  <li><code class="language-plaintext highlighter-rouge">$(MAKE)</code> 指向当前make工具</li>
</ol>

<h4 id="自动变量">自动变量</h4>

<h5 id="指代当前目标"><code class="language-plaintext highlighter-rouge">$@</code>指代当前目标</h5>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a.txt b.txt:
    touch $@
</code></pre></div></div>

<p>等同于</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a.txt:
    touch a.txt
b.txt:
    touch b.txt
</code></pre></div></div>

<h5 id="指代第一个前置条件"><code class="language-plaintext highlighter-rouge">$&lt;</code>指代第一个前置条件</h5>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a.txt: b.txt c.txt
    cp $&lt; $@
</code></pre></div></div>

<p>等同于</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a.txt: b.txt c.txt
    cp b.txt a.txt
</code></pre></div></div>

<h5 id="-指代比目标更新的所有前置条件"><code class="language-plaintext highlighter-rouge">$?</code> 指代比目标更新的所有前置条件</h5>

<h5 id="-指代所有前置条件之间以空格分隔"><code class="language-plaintext highlighter-rouge">$^</code> 指代所有前置条件，之间以空格分隔</h5>

<h5 id="-指代匹配符--匹配的部分"><code class="language-plaintext highlighter-rouge">$*</code> 指代匹配符 % 匹配的部分</h5>

<p>比如% 匹配 f1.txt 中的f1 ，$* 就表示 f1</p>

<h4 id="判断和循环">判断和循环</h4>

<p>使用BASH的判断和循环</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ifeq ($(CC),gcc)
  libs=$(libs_for_gcc)
else
  libs=$(normal_libs)
endif
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LIST = one two three
all:
    for i in $(LIST); do \
        echo $$i; \
    done
</code></pre></div></div>

<h4 id="函数">函数</h4>

<ul>
  <li>shell
    <ul>
      <li>执行shell语句
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>srcfiles := $(shell echo src/{00..99}.txt)
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>subst
    <ul>
      <li>用于字符串替换
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(subst ee,EE,feet on the street)
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
  <li>patsubst
    <ul>
      <li>patsubst 函数用于模式匹配的替换
        <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(patsubst %.c,%.o,x.c.c bar.c)
</code></pre></div>        </div>
      </li>
    </ul>
  </li>
</ul>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="C" /><category term="编译" /><category term="make" /><summary type="html"><![CDATA[let's make]]></summary></entry><entry><title type="html">python dict</title><link href="https://tuobashaoli.github.io/blog/2022/03/26/py/" rel="alternate" type="text/html" title="python dict" /><published>2022-03-26T17:50:18+00:00</published><updated>2022-03-26T17:50:18+00:00</updated><id>https://tuobashaoli.github.io/blog/2022/03/26/py</id><content type="html" xml:base="https://tuobashaoli.github.io/blog/2022/03/26/py/"><![CDATA[<h1 id="定义">定义</h1>

<p>字典结构体的定义在文件<code class="language-plaintext highlighter-rouge">Include/cpython/dictobject.h</code></p>

<p>具体内容：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>typedef struct {
    PyObject_HEAD

    /* Number of items in the dictionary */
    Py_ssize_t ma_used; //字典中的元素个数

    /* Dictionary version: globally unique, value change each time
       the dictionary is modified */
    uint64_t ma_version_tag;//字典版本号，每次字典有变更，这个值也会变更

    PyDictKeysObject *ma_keys; //指针，指向存储的键值对

    /* If ma_values is NULL, the table is "combined": keys and values
       are stored in ma_keys.

       If ma_values is not NULL, the table is splitted:
       keys are stored in ma_keys and values are stored in ma_values */
    PyObject **ma_values; //指向指针的指针，如果为空，则key和value都是存储在ma_keys,否则，key存放在ma_keys,value存放在ma_values，一般创建的字典都是联合表字典combined-table，分离表字典split-table字典用于__dict__属性，它们的键表被缓存在类型属性中，并允许所有该类型的实例共享值。
} PyDictObject;
</code></pre></div></div>

<p>而存储键值对的结构，PyDictKeysObject，即<code class="language-plaintext highlighter-rouge">_dictkeysobject</code>，定义在<code class="language-plaintext highlighter-rouge">Objects/dict-common.h</code>中</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>struct _dictkeysobject {
    Py_ssize_t dk_refcnt; //引用计数

    /* Size of the hash table (dk_indices). It must be a power of 2. */
    Py_ssize_t dk_size; // 容量，是2的倍数，这样在寻址的时候，可以用异或

    /* Function to lookup in the hash table (dk_indices):

       - lookdict(): general-purpose, and may return DKIX_ERROR if (and
         only if) a comparison raises an exception.

       - lookdict_unicode(): specialized to Unicode string keys, comparison of
         which can never raise an exception; that function can never return
         DKIX_ERROR.

       - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further
         specialized for Unicode string keys that cannot be the &lt;dummy&gt; value.

       - lookdict_split(): Version of lookdict() for split tables. */
    dict_lookup_func dk_lookup; // hash查找函数

    /* Number of usable entries in dk_entries. */
    Py_ssize_t dk_usable; // 可使用的entry

    /* Number of used entries in dk_entries. */
    Py_ssize_t dk_nentries; //已使用的entry

    /* Actual hash table of dk_size entries. It holds indices in dk_entries,
       or DKIX_EMPTY(-1) or DKIX_DUMMY(-2).

       Indices must be: 0 &lt;= indice &lt; USABLE_FRACTION(dk_size).

       The size in bytes of an indice depends on dk_size:

       - 1 byte if dk_size &lt;= 0xff (char*)
       - 2 bytes if dk_size &lt;= 0xffff (int16_t*)
       - 4 bytes if dk_size &lt;= 0xffffffff (int32_t*)
       - 8 bytes otherwise (int64_t*)

       Dynamically sized, SIZEOF_VOID_P is minimum. */
    char dk_indices[];  /* char is required to avoid strict aliasing. */
    //存入的entries，元素类型是PyDictKeyEntry
    /* "PyDictKeyEntry dk_entries[dk_usable];" array follows:
       see the DK_ENTRIES() macro */
};
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
typedef struct {
    /* Cached hash code of me_key. */
    Py_hash_t me_hash; //存储me_key的hash值
    PyObject *me_key; //键
    PyObject *me_value; /* This field is only meaningful for combined tables */ //值
} PyDictKeyEntry;
</code></pre></div></div>

<p>内存结构图如下</p>

<p><img src="/assets/images/python_dict.png" alt="avatar" /></p>

<h1 id="hash冲突">hash冲突</h1>

<p>当某俩个不同的key，计算的hash一样的时候，就产生了hash冲突。
产生hash冲突的时候，就需要解决冲突，一般有以下的思路</p>

<ol>
  <li>用某种方法再找个地址，直到没有冲突，这个思路下有以下方法
    <ul>
      <li>将key用其他方式再hash</li>
      <li>查找这个地址之前/之后的可用地址
 以上新增时可能会增加时间，比如出发扩容和多次查找会很耗时，但是访问速度快，可以序列化</li>
    </ul>
  </li>
  <li>将这些冲突的key，额外存储下
    <ul>
      <li>用一个链表/红黑树存储这些key，适合经常插入和删除，访问的时间可能会增加</li>
      <li>建立公共益处区，专门存储冲突的key</li>
    </ul>
  </li>
</ol>

<p>dict的各个接口函数的实现，都在文件<code class="language-plaintext highlighter-rouge">Objects/dictobject.c</code>文件中，</p>

<p>一般都是创建dict和给dict添加元素的时候，就会遇到hash冲突，所以可以直接观察插入元素的实现逻辑，从中看看python如何解决hash冲突的</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
static int
insertdict(PyDictObject *mp/*被插入的字典*/, PyObject *key/*需要插入字典的键*/, Py_hash_t hash/*需要插入字典的键的hash*/, PyObject *value/*需要插入字典的值*/)
{
    PyObject *old_value;
    PyDictKeyEntry *ep;

    Py_INCREF(key); //添加引用计数
    Py_INCREF(value); //添加引用计数
    if (mp-&gt;ma_values != NULL &amp;&amp; !PyUnicode_CheckExact(key)) {
        // ma_values 不为空表示该字典是分离表字典，PyUnicode_CheckExact检查key是否是unicode对象，不能是unicode子类对象
        // 如果mp是分离表字典，且key不是unicode对象
        if (insertion_resize(mp) &lt; 0)
            // 如果无法给mp申请内存，转换成联合表字典，那么直接失败，
            goto Fail;
    }

    Py_ssize_t ix = mp-&gt;ma_keys-&gt;dk_lookup(mp, key, hash, &amp;old_value);
    // 调用dk_lookup查找需要插入的键值对所在的位置，理论上，为了保证该位置可以插入，那么这个查找函数就需要返回解决hash冲突后的地址了
    // 所以查看dk_lookup的实现
    if (ix == DKIX_ERROR)
        goto Fail;

    assert(PyUnicode_CheckExact(key) || mp-&gt;ma_keys-&gt;dk_lookup == lookdict);
    MAINTAIN_TRACKING(mp, key, value);

    /* When insertion order is different from shared key, we can't share
     * the key anymore.  Convert this instance to combine table.
     */
    if (_PyDict_HasSplitTable(mp) &amp;&amp;
        ((ix &gt;= 0 &amp;&amp; old_value == NULL &amp;&amp; mp-&gt;ma_used != ix) ||
         (ix == DKIX_EMPTY &amp;&amp; mp-&gt;ma_used != mp-&gt;ma_keys-&gt;dk_nentries))) {
        if (insertion_resize(mp) &lt; 0)
            goto Fail;
        ix = DKIX_EMPTY;
    }

    if (ix == DKIX_EMPTY) {
        /* Insert into new slot. */
        assert(old_value == NULL);
        if (mp-&gt;ma_keys-&gt;dk_usable &lt;= 0) {
            /* Need to resize. */
            if (insertion_resize(mp) &lt; 0)
                goto Fail;
        }
        Py_ssize_t hashpos = find_empty_slot(mp-&gt;ma_keys, hash);
        ep = &amp;DK_ENTRIES(mp-&gt;ma_keys)[mp-&gt;ma_keys-&gt;dk_nentries];
        dictkeys_set_index(mp-&gt;ma_keys, hashpos, mp-&gt;ma_keys-&gt;dk_nentries);
        ep-&gt;me_key = key;
        ep-&gt;me_hash = hash;
        if (mp-&gt;ma_values) {
            assert (mp-&gt;ma_values[mp-&gt;ma_keys-&gt;dk_nentries] == NULL);
            mp-&gt;ma_values[mp-&gt;ma_keys-&gt;dk_nentries] = value;
        }
        else {
            ep-&gt;me_value = value;
        }
        mp-&gt;ma_used++;
        mp-&gt;ma_version_tag = DICT_NEXT_VERSION();
        mp-&gt;ma_keys-&gt;dk_usable--;
        mp-&gt;ma_keys-&gt;dk_nentries++;
        assert(mp-&gt;ma_keys-&gt;dk_usable &gt;= 0);
        ASSERT_CONSISTENT(mp);
        return 0;
    }

    if (old_value != value) {
        if (_PyDict_HasSplitTable(mp)) {
            mp-&gt;ma_values[ix] = value;
            if (old_value == NULL) {
                /* pending state */
                assert(ix == mp-&gt;ma_used);
                mp-&gt;ma_used++;
            }
        }
        else {
            assert(old_value != NULL);
            DK_ENTRIES(mp-&gt;ma_keys)[ix].me_value = value;
        }
        mp-&gt;ma_version_tag = DICT_NEXT_VERSION();
    }
    Py_XDECREF(old_value); /* which **CAN** re-enter (see issue #22653) */
    ASSERT_CONSISTENT(mp);
    Py_DECREF(key);
    return 0;

Fail:
    Py_DECREF(value); //异常情况，去除添加的引用计数
    Py_DECREF(key); //异常情况，去除添加的引用计数
    return -1;
}
</code></pre></div></div>

<p>没有直接查找到<code class="language-plaintext highlighter-rouge">dk_lookup</code>的直接实现,是在实际调用中，给<code class="language-plaintext highlighter-rouge">dk_lookup</code>赋值为各种<code class="language-plaintext highlighter-rouge">lookdict</code>函数</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>static Py_ssize_t _Py_HOT_FUNCTION
lookdict(PyDictObject *mp, PyObject *key,
         Py_hash_t hash, PyObject **value_addr)
{
    size_t i, mask, perturb;
    PyDictKeysObject *dk;
    PyDictKeyEntry *ep0;

top:
    dk = mp-&gt;ma_keys;
    ep0 = DK_ENTRIES(dk);
    mask = DK_MASK(dk);
    perturb = hash;
    i = (size_t)hash &amp; mask;

    for (;;) {
        Py_ssize_t ix = dictkeys_get_index(dk, i);
        if (ix == DKIX_EMPTY) {//如果查找出的位置是空的，表示，这个位置可以插入新的键值对，那么直接返回这个地址
            *value_addr = NULL;
            return ix;
        }
        if (ix &gt;= 0) {
            PyDictKeyEntry *ep = &amp;ep0[ix];
            assert(ep-&gt;me_key != NULL);
            if (ep-&gt;me_key == key) {// key一致，表示这个位置就是要找的地方
                *value_addr = ep-&gt;me_value;
                return ix;
            }
            if (ep-&gt;me_hash == hash) { // key不一致，但是hash一直，表示发生了hash冲突
                PyObject *startkey = ep-&gt;me_key;
                Py_INCREF(startkey);
                int cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);
                Py_DECREF(startkey);
                if (cmp &lt; 0) {
                    *value_addr = NULL;
                    return DKIX_ERROR;
                }
                if (dk == mp-&gt;ma_keys &amp;&amp; ep-&gt;me_key == startkey) {
                    if (cmp &gt; 0) {
                        *value_addr = ep-&gt;me_value;
                        return ix;
                    }
                }
                else {
                    /* The dict was mutated, restart */
                    goto top;
                }
            }
        }
        perturb &gt;&gt;= PERTURB_SHIFT;
        i = (i*5 + perturb + 1) &amp; mask; // ix &lt;0,否则，将i运算后，进行下一轮计算
    }
    Py_UNREACHABLE();
}
</code></pre></div></div>

<p>通过上面的源码，看出来，python使用了实现起来最简单的开放地址法解决冲突，果然时刻践行着”怎么简单怎么来”的思想</p>]]></content><author><name>Tuo Bashaoli</name></author><category term="blog" /><category term="pythonic" /><summary type="html"><![CDATA[it is pythonic]]></summary></entry></feed>