Hash
觉得什么笔记软件都不好用,恰好疲于选择博客主题,因此使用 mdBook 记下一点东西。
才疏学浅,应该有很多不对的地方,欢迎和我一起讨论 ^^
关于我
网名“一粒”、常用提交 ID qaqland
,是 Alpine Linux && deepin Linux 桌面用户。
我的计算机知识和代码水平比较肤浅:
- 会一点 C 但是既没编译过内核也没搞过驱动
- 会一点 Rust 不过日常看不懂生命周期
- 会一点 Linux 但总是听不懂群友在讨论什么
说要做很多项目,但是至今 3000 行以上的没写出来几个。目前还在关注的有:
- Wless:定位上高于 cage 约等于 dwl 的窗管,基于 wlroots
- BuShi:目标是取代 cgit 成为 gitweb 的首选,基于 Rust 和 SQLite
以上两个项目进行了初步尝试,位于 30% 阶段。
2025-10
Alpine Linux
- https://gitlab.alpinelinux.org/alpine/aports/-/issues/17499
- https://gitlab.alpinelinux.org/alpine/aports/-/issues/17605
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/90725
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91364
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91610
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91611
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91612
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91614
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91664
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91692
原计划在国庆假期把 Python 相关的包努力做一做,setup.py install
相关函数将要在
月底废弃,但是经过努力决定开摆,原因有下:
- 当前在不测试的情况下完全无法分辨是否依赖有缺失
- 很多上游不着急,构建系统的切换还是上游做合适
- 缺少项目经验,不明白 nox、pytest、uv 等的工作原理
- 使用的 py3-gpep517 脚本协议错误,提了修改没人管
这个月 pmos 的人引入了 systemd 子包功能到 abuild,也算是对社区的又一次冲击(摊手), 不知道 WHLUG 能不能做个闪电分享讲讲这些事。
Wless & Wayland
隔了一整个 8 月和 9 月没写,之前写的代码有点看不懂了,于是也重新看。
- 在 sway 中 layer-shell 是挂在 output 下,渲染时 reparent 到 server
- 在 dwl 中 border 是挂在 client 下,对于我的场景应该适合挂在 output 里
wlr_scene_node_set_size
需要考虑到 pending 状态(tinywl 的 resize 没考虑)
之前拆多文件是为了使用 AI 写代码理解方便,但是体验了 VSCode + cline + DeepSeek, 小几千行的情况下,单文件对 AI 和对我的原生 Vim 来说都更方便,我就改回去了。
协议 wlr output management 是为了客户端 Read 或者 Write 服务端的显示器设置
-
zwlr_output_manager_v1
- request
create_configuration
>>zwlr_output_configuration_v1
- request
stop
- event
head
>>zwlr_output_head_v1
只读的广播,没什么逻辑 - event
done
- event
finished
- request
-
zwlr_output_configuration_v1
- request
enable_head
>>zwlr_output_configuration_head_v1
+zwlr_output_head_v1
- request
disable_head
>>zwlr_output_head_v1
- request
apply
- request
test
- request
destroy
- request
-
zwlr_output_configuration_head_v1
- request
set_{mode,position,etc.}
- request
-
https://wayland.app/protocols/wlr-output-management-unstable-v1
Bushi & Git
重新审视了这个项目的整体架构,思考下一步真的要做什么。
项目已有的“祖先跳表”设计令人兴奋,但是内部数据的生成还是依赖 git fast-export
,
这不优雅:
- 很难控制 git-cli 本体的内存占用,据测算需要 800 Mb(使用 aports.git 仓库)
- 命令行输出的解析是用的手抠字符串,虽然能跑但相当原始
- 初始化更新和后续更新可能逻辑不一致,写两套函数无法接受
所以最好这里再细节一点。
先尝试的是字符串解析,尝试使用 nom 但阅读文档后发现,这个库必须先把字符串读取为
&str
,这就很不合适,因为管道重定向是 Stream 流:数据量大、输出时间长、
不保证字符编码。目前也没有什么其他库做这件事很合适,所以搁置。
用原版 git-cli 和 libgit2 各做 1000 次提交差异对比,发现原版速度就是快一个数量级
export GIT_PAGER=cat
git diff --no-color --name-only "$commit_old" "$commit_new"
看了会儿 libgit2 的源码思考为什么如此慢。发现它读取 object-tree 这里可以改一下,
用 data
的 size
除以 oid_t
+ mode + 6,然后拿这个数值去初始化数组,
避免很多次 realloc。修改后对比测试,略有提升,效果微乎其微,平均百万次读取超大
object-tree 调用提升 10s(口算)。
- https://github.com/libgit2/libgit2/blob/58d9363f02f1fa39e46d49b604f27008e75b72f2/src/libgit2/tree.c#L405
- https://linux.die.net/man/3/realloc
- https://en.cppreference.com/w/c/memory/realloc
读取有 object 后的 diff 运算没有可以优化的地方,就是简单的双指针单向遍历。
从 git-sizer 程序的解析结果看,把仓库中所有的 object-tree 事先解析并塞到 SQLite 的方法会导致程序体积占用很大,预计有 20 倍的膨胀,所以不可取。理想预期:
- 【内存占用小】全程不超过 200 Mb,而且缓存大小可控
- 【硬盘占用小】因为必须保留原生 git-objects 做兼容,所以尽可能产物小于同期 fossil
- 【CPU 消耗低】初始化不作要求,查询时单线程可运行
- 【解析时间短】初始化时间低于 git-fastexport 耗时的三倍,查询时间低于 git-cli
下一步计划:当前的性能瓶颈有两个,object-tree 的 read 和 diff,它们被广泛压缩在 packfile 中,不仅 IO 紧张而且 CPU 紧张。但是逆转过来想,压缩本身也是一种信息? 可能需要写一点优化后的读取方法,在读取时抓取更多信息。
Misc
做了一个用于下载、缓存、解压源码的 rust 小工具 fetchsrc,我的 Option 和错误处理 经验还是太匮乏了,这里写的很吃力(和 shell 差不多吃力了!)后面有空找个开源项目 跟踪一段时间。
Alpine Linux 合并了对 systemd 子包的支持,后面也许可以自己造点工具链 自立门户。
QT
发现一个 Bug,右键弹出菜单中,分隔线上的点击事件无效但会导致弹出菜单消失。 使用 AI 写了个复现小 demo,提交问题到了 QT 上游。
# 感谢子冲帮忙修复 LSP 爆红
sudo apt install libstdc++-13-dev
不知道下一步会不会学习 QT 做点小工具。
Vim
学到了 Vim 自带的 grep 命令,非常好用,再也不需要终端里查询复制到 Vim 中再打开了。
现在是接入了 ripgrep 日常使用,配置文件略微修改:
if executable("rg")
set grepprg=rg\ --vimgrep\ --smart-case
set grepformat+=%f:%l:%c:%m
endif
把之前在 Vim 上修的小补丁 cherry-pick 到了 NeoVim 上
- https://github.com/vim/vim/commit/ce4f9d2a1016ade19fa07c5b66e58eb084719192
- https://github.com/neovim/neovim/pull/36221
LSP
clangd 原来默认也会搜索 build/compile_commands.json
,再也不抱怨 LSP 不干活了,
之前全靠软链接和 .clangd
配置文件苟活。
Alpine Linux 软件包仓库一致性检查
2025 年 6 月 6 日
问题描述
镜像中共有两种文件,APKINDEX
索引文件和具体的 pkgname-pkgver-rpkgrel.apk
安装包。当用户执行安装软件操作时,会进行以下过程:
- 下载最新的 APKINDEX 到本地
- 解析索引,拿到对应的具体包名
- 下载对应的软件包文件
- 执行安装、脚本等
如果索引文件的版本与具体软件包不匹配,就会造成安装时找不到软件包,报错信息等同于软件名输入错误,示例如下:
$ doas apk add cage
ERROR: unable to select packages:
cage (no such package):
required by: world[cage]
当前进展
- 联系上游制作基于本地的一致性检查工具
- 本地测试
- 联系镜像站测试 TODO
- 发现问题后如何解决?
使用方法
Go 项目很好编译,调用参数为 APKINDEX.tar.gz
所在的目录路径:
$ ./repo-tools/repo-tools local check test-repo/
# 如果缺失
Package zfs-scripts-2.3.2-r0.apk mentioned in the index not found
# 如果成功无显示 TODO 可能要进程返回值设置为 1
个人猜想
可以做成两步同步:先单独拉索引文件,生成 rsync 同步 apk 文件列表,再用这个列表做同步,最后检查没问题时,使用新索引文件覆盖掉旧的。
引用网址
- https://gitlab.alpinelinux.org/alpine/infra/repo-tools/-/merge_requests/1
- https://salsa.debian.org/mirror-team/archvsync/
TinyWL 源码学习记录
2025 年 5 月 22 日 后面补充上了一点 dwl 的东西
Why are there "key" and "modifiers" two events at the same time?
根据协议,key 表示物理按键序列,而 modifiers 用来同步当前修饰键的逻辑状态
- 客户端 enter 之后会收到一次 modifiers 同步
- 修饰键 key 触发后也会收到 modifiers 状态激活
在 wlroots/types/seat/wlr_seat_keyboard.c
中可以看到调用顺序:
wlr_seat_keyboard_notify_enter
wlr_seat_keyboard_enter
wlr_seat_keyboard_send_modifiers
第二种情况暂未发现 // TODO
对窗口的移动
客户端主动发出请求 xdg_toplevel::move
,服务端在 xdg_toplevel_request_move
中处理
server->grabbed_toplevel = toplevel;
server->cursor_mode = TINYWL_CURSOR_MOVE;
server->grab_x = server->cursor->x - toplevel->scene_tree->node.x;
server->grab_y = server->cursor->y - toplevel->scene_tree->node.y;
处理该请求主要为记录状态,包括希望移动的窗口、触发时的鼠标位置等。后续具体移动逻辑由服务端在鼠标事件中负责:
*toplevel = server->grabbed_toplevel;
wlr_scene_node_set_position(
&toplevel->scene_tree->node,
server->cursor->x - server->grab_x,
server->cursor->y - server->grab_y
);
使用“鼠标的实时位置”与请求触发时“窗口顶点与鼠标的相对距离”设置移动过程与最终的窗口位置。
对窗口的尺寸调整
与移动相似,但窗口需要更多调整,涉及到窗口的大小、位置改变。
- 记录光标位置与窗口边缘坐标
- 注意窗口最小尺寸(当调整左上边框时)
- 计算得到新窗口大小并设置
- 计算得到新窗口坐标并设置
对于坐标来说,wlr_xdg_toplevel
的坐标不等于 wlr_surface
的坐标,因此需要考虑两者之间的相对值。
键盘状态-焦点 surface
struct wlr_surface* previous_surface = seat->keyboard_state.focused_surface;
// tinywl.c @focuse_toplevel
在 wlroots
的 seat 中这个状态鼠标键盘各有一个:
struct wlr_surface *pointer_focus = wlr_seat->pointer_state.focused_surface;
if (pointer_focus != NULL &&
wl_resource_get_client(pointer_focus->resource) == client) {
wlr_seat->pointer_state.focused_client = seat_client;
}
struct wlr_surface *keyboard_focus = wlr_seat->keyboard_state.focused_surface;
if (keyboard_focus != NULL &&
wl_resource_get_client(keyboard_focus->resource) == client) {
wlr_seat->keyboard_state.focused_client = seat_client;
}
// types/seat/wlr_seat.c @static struct wlr_seat_client *seat_client_create()
当 wlr_seat
没有资源时,持有的焦点也要销毁(不过一般一个 WM 只有一个实例)
if ((capabilities & WL_SEAT_CAPABILITY_POINTER) == 0) {
if (focused_client != NULL && focused_surface != NULL) {
seat_client_send_pointer_leave_raw(focused_client, focused_surface);
}
}
// types/seat/wlr_seat.c @void wlr_seat_set_capabilities()
所以这个变量可以为空,仅表示当前 seat 相关的焦点状态。常规修改发生在一些 enter 事件上:
wlr_seat_keyboard_enter
wlr_seat_pointer_enter
不过注释说更应该使用 wlr_seat_*_notify_enter
,内部会自行调整。
focus_toplevel
主要在以下三个位置调用:
handle_keybinding
做窗口轮换时重新获得焦点server_cursor_button
鼠标点击事件xdg_toplevel_map
新窗口生成时拿到焦点
对于这三种情况基本可以满足 focuse_output
的等效替换,不过要考虑不可最大化的窗口处理
// TODO
wlr_scene_tree
的创建
在 dwl.c
的 mapnotify
函数中,有以下创建:
c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]);
c->scene_surface = c->type == XDGShell
? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg)
: wlr_scene_subsurface_tree_create(c->scene, client_surface(c));
c->scene->node.data = c->scene_surface->node.data = c;
这里的 scene
scene_tree
类型均为 wlr_scene_tree
指针。对比两种函数:
/**
* Add a node displaying an xdg_surface and all of its sub-surfaces to the
* scene-graph.
*
* The origin of the returned scene-graph node will match the top-left corner
* of the xdg_surface window geometry.
*/
struct wlr_scene_tree *wlr_scene_xdg_surface_create(
struct wlr_scene_tree *parent, struct wlr_xdg_surface *xdg_surface);
/**
* Add a node displaying a surface and all of its sub-surfaces to the
* scene-graph.
*/
struct wlr_scene_tree *wlr_scene_subsurface_tree_create(
struct wlr_scene_tree *parent, struct wlr_surface *surface);
// 没有太大差别,XDG 和 XWAYLAND 不适配
进程与环境变量
2025年5月18日 在想办法用环境变量做程序的配置系统
数据结构
理论上讲应该采用 HashMap 的数据格式保存,以实现 O(1) 的访问速度。
#include <unistd.h>
char **__environ = 0;
weak_alias(__environ, ___environ);
weak_alias(__environ, _environ);
weak_alias(__environ, environ);
// https://git.musl-libc.org/cgit/musl/tree/src/env/__environ.c
但实际使用形如 KEY=VALUE
的字符串数组。
系统调用
对环境变量的读和写均发生在用户态:
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char *getenv(const char *name)
{
size_t l = __strchrnul(name, '=') - name;
if (l && !name[l] && __environ)
for (char **e = __environ; *e; e++)
if (!strncmp(name, *e, l) && l[*e] == '=')
return *e + l+1;
return 0;
}
// https://git.musl-libc.org/cgit/musl/tree/src/env/getenv.c
仅初始化与系统调用有关:
#include <unistd.h>
#include "syscall.h"
int execve(const char *path, char *const argv[], char *const envp[])
{
/* do we need to use environ if envp is null? */
return syscall(SYS_execve, path, argv, envp);
}
// https://git.musl-libc.org/cgit/musl/tree/src/process/execve.c
Bash
在 Bash 中很坏,“环境变量”只是普通变量的一个属性,一般用 export
关键字标识
#define att_exported 0x0000001 /* export to environment */
...
#define att_local 0x0000020 /* variable is local to a function */
// bash/variables.h
/* An array which is passed to commands as their environment. It is
manufactured from the union of the initial environment and the
shell variables that are marked for export. */
char **export_env = (char **)NULL;
// bash/variables.c
所以会出现以下情况,环境变量随变量修改而改变:
export foo=bar
foo=barbar
sh -c 'echo $foo'
# output: barbar
其它对环境变量的构建和修改与普通函数无异
static inline char *
mk_env_string (name, value, attributes)
const char *name, *value;
int attributes;
{
...
{
p = (char *)xmalloc (2 + name_len + value_len);
memcpy (p, name, name_len);
q = p + name_len;
}
q[0] = '=';
memcpy (q + 1, value, value_len + 1);
}
// bash/variables.c
int
shell_execve (command, args, env)
char *command;
char **args, **env;
{
...
execve (command, args, env);
}
// bash/execute_cmd.c
JuDou 句读
2025 年 5 月 14 日 记录一下项目想法
概述
JuDou 是一个基于 LSP 的代码审阅辅助工具,通过在本地建立预缓存,实现在网页端的语义高亮和引用跳转。
- 服务端:资源消耗极低
- 客户端:无需环境配置
- 流水线:自动集成构建
名称
“常买旧书的人,有时会遇到一部书,开首加过句读,夹些破句,中途却停了笔:他点不下去了。”
技术栈
- Golang
- Vue
- Json-RPC 2.0
- Json
盈利
前期贴皮广告,中期 SAAS,后期私有化部署。
直接竞争对手为 GitHub 和 Sourcegraph,潜在包括各类 LLM 分析工具。
参考
- https://github.com/netmute/ctags-lsp
- https://github.com/sourcegraph/jsonrpc2
- https://microsoft.github.io/language-server-protocol/
Git 祖先跳表
2025年5月4日 介绍一下 Bushi 所使用的祖先跳表
目标
快速判断 Commit A 是否是 Commit B 的祖先
- git-ref-contains-commit
- git-log-path-filter
限制
本项目仅考虑提交的第一个 Parent Commit
原理
判断
验证
选择 Aports 仓库中 Commit-ID 分别为 100 和 20000 的两个提交,对应的 Commit-OID 如下
sqlite> SELECT * FROM commits WHERE commit_id = 100 OR commit_id = 20000;
commit_id commit_hash commit_mark parent_id depth repo_id
--------- ---------------------------------------- ----------- --------- ----- -------
100 44a369d15ac69464584099d339a0e1ec1ec7fa66 100 99 99 1
20000 89d3e577e9a10d8f75b87d5ec44d801efbb7f3c7 20000 19999 13920 1
查看网页确实在同一分支中,两者 depth 之差为 13821,转为二进制数得到 2 的 N 次幂
>>> bin(13821)
'0b11010111111101'
即为 13、12、10、8、7、6、5、4、3、2、0 这些序列,下面开始在祖先表中查找
sqlite> SELECT * FROM ancestors WHERE commit_id = 20000 AND level = 13;
commit_id level ancestor_id
--------- ----- -----------
20000 13 7618
sqlite> SELECT * FROM ancestors WHERE commit_id = 7618 AND level = 12;
commit_id level ancestor_id
--------- ----- -----------
7618 12 2133
sqlite> SELECT * FROM ancestors WHERE commit_id = 2133 AND level = 10;
commit_id level ancestor_id
--------- ----- -----------
2133 10 777
sqlite> SELECT * FROM ancestors WHERE commit_id = 777 AND level = 8;
commit_id level ancestor_id
--------- ----- -----------
777 8 372
sqlite> SELECT * FROM ancestors WHERE commit_id = 372 AND level = 7;
commit_id level ancestor_id
--------- ----- -----------
372 7 244
sqlite> SELECT * FROM ancestors WHERE commit_id = 244 AND level = 6;
commit_id level ancestor_id
--------- ----- -----------
244 6 161
sqlite> SELECT * FROM ancestors WHERE commit_id = 161 AND level = 5;
commit_id level ancestor_id
--------- ----- -----------
161 5 129
sqlite> SELECT * FROM ancestors WHERE commit_id = 129 AND level = 4;
commit_id level ancestor_id
--------- ----- -----------
129 4 113
sqlite> SELECT * FROM ancestors WHERE commit_id = 113 AND level = 3;
commit_id level ancestor_id
--------- ----- -----------
113 3 105
sqlite> SELECT * FROM ancestors WHERE commit_id = 105 AND level = 2;
commit_id level ancestor_id
--------- ----- -----------
105 2 101
sqlite> SELECT * FROM ancestors WHERE commit_id = 101 AND level = 0;
commit_id level ancestor_id
--------- ----- -----------
101 0 100
最终得到 Commit-ID 与目标 ID 相同,两者位于同一分支中。
GitWeb Collect
2025 年 4 月 12 日 cosplay 一下产品经理,对潜在精品进行汇总
汇总有遗漏,请额外查看:
- https://forge.runxiyu.org/stuff/-/repos/code-forge-list/
- https://en.wikipedia.org/wiki/Comparison_of_source-code-hosting_facilities
- https://en.wikipedia.org/wiki/Forge_(software)#Examples
Library
- libgit2 (c)
- git2-rs (c-rust)
- pygit2 (c-python)
- rugged (c-ruby)
- nodegit (c-javascript)
- go-git (golang)
- gitoxide (rust)
- ungit (javascript)
- Gitaly (git-golang)
严格来说 Gitaly 不算库,它是 GitLab 在替换掉 Rugged 之后的 RPC 远程调用,内部是对 Git 原版命令的封装
Simple
Perl
- GitWeb(Perl, Git builtin)
- https://github.com/yuki-kimoto/gitweblite
- https://github.com/cantonios/gitwebhub
- https://github.com/broquaint/Gitalist
Rust
- https://github.com/w4/rgit
- https://github.com/alexwennerberg/mygit
- https://github.com/qaqland/bushi Here is mine
Golang
- https://github.com/icyphox/legit
- https://github.com/emersion/matcha
- https://github.com/esote/gitweb
- SoftServe
C
- https://git.zx2c4.com/cgit/about
- https://github.com/oxalorg/stagit
- https://github.com/Ernest1338/GitWebSee
Java
Lua
Full
VSCode Remote SSH 连接 Alpine Linux
2025 年 4 月 11 日
远程连接时的步骤大致如下:
- 安装 remote-cli 到服务端
- remote-cli 下载并启动 vscode-server
- 本地客户端与 vscode-server 连接
基础
$ apk add bash curl git libstdc++ procps-ng
如果还是在启动时报错,可以查看 vscode 仓库对应脚本 resources/server/bin/helpers/check-requirements-linux.sh
插件
// TODO
References
Git 很慢
//
2025 年 1 月 23 日 抱怨一下 Git 的数据结构
2025 年 4 月 13 日 后续:ROGit 改名为 Bushi 卜筮,基本解决了这个问题,其余部分还在做
//
一年半之前,我有一个 ROGit 项目计划,旨在构建一个 cgit 同生态位的现代替代品
- ReadOnly:网页所有操作对 Git 仓库来说都是只读的,区别于 GitHub 或者 Gitea
- Rust:没有学习过 Rust 所以借此机会尝试一点新东西
- Online:以后可能除了 http 还会做 gemini 协议网站,所以很在线
得益于技术栈发展,cgit 所使用的 CGI 协议已相当少见。ROGit 相比 cgit 有以下几个目标:
- 现代 CSS 界面及交互设计
- 避免 Git 源码依赖
- 灵活的 API 接口及扩展
当前正在做后端的数据初始化部分,希望在启动时对所有的提交及其变动修改预缓存到 SQLite,但是遇到了严重的性能问题:解析 aports 仓库需要 3 个小时(对于初始化而言 3 分钟是可以接受的)。
注:使用 libgit2 作为解析库,火焰图时关闭缓存、关闭 Hash 验证,在开启自带缓存后性能略有提高
Git Object
理论上讲 Git 中有三个大类别的对象:Commit、Tree、Blob,具体到解析时还有 Commit 的同类 Tag 及 Note。这些对象以各自 Object 的 hash 作为索引,经 zlib 压缩后保存在 .git/objects
下的文件中,纯正文件系统驱动,并在需要时被 Git 解析。
Git 的数据结构为写多读少设计,因此其他程序应避免将 Git 作为数据库使用。具体保存格式有 Loose Objects 和 Packfiles 两类,参考阅读:
- https://git-scm.com/book/en/v2/Git-Internals-Packfiles
- https://github.blog/open-source/git/gits-database-internals-i-packed-object-store/
Git Commit
提交 Commit 指向自己的历史来源,有 0 个、1 个、2 个或者更多个 Parent Commit。不同的数量表示了这次提交的不同种类,是 root 节点还是 fast-forward 这种单线链表,或者是合并分支的 Merge 节点,超过 2 的情况不多见,是特殊的合并节点,比如内核的提交中出现的:
https://github.com/torvalds/linux/commit/9b25d604182169a08b206306b312d2df26b5f502
当然像提交信息、提交人、提交时间、以及 Committer 与 Author 之间的区别这里不再赘述。Commit 还保存了提交时的文件(树)快照,指向当前 Commit 相随的 Tree。
- Tree Hash
- Parent Commit Hash
- Author
- Committer
- Commit Message
这就是一个 Commit 对象信息的全部,当一个普通线性提交发生时,Git 会扫描当前 WorkTree 生成 TreeHash,底层保存的数据中并不关心此次提交的修改。
Git Log
Git 并没有在 Commit 中描述这次提交修改了哪些文件,所以若要知晓给定文件的最后修改日期,过程较为艰难:
- 获取 Commit 的 TreeHash 与 Parent Commit 的 TreeHash
- 在两个 Tree Object 中遍历,查找修改的文件
- 检查给定文件是否在本次 Diff 中修改
- 重复上述操作,直到两次提交的树之间存在期望差异
Git 内部在第二步和第三步之间应该有查询优化,仅对比给定的路径,但整体在没有对应数据结构的情况下进行类似 SQL 的结构化查询还是相当消耗性能,面对稍微大一点的仓库,时间来到秒级
$ /usr/bin/time git -c core.commitGraph=false log --oneline -n 10 community/xmake/
81380060446 community/xmake: upgrade to 2.9.9
7c21bea5624 */*: replace non-POSIX find -not option
a92fe0ba060 community/xmake: upgrade to 2.9.7
12374870a8f community/xmake: upgrade to 2.9.6
12c188ec967 community/xmake: upgrade to 2.9.5
0096aeef07c community/xmake: upgrade to 2.9.4
4b130eb7f5a community/xmake: upgrade to 2.9.3
d516ffbb476 community/xmake: upgrade to 2.9.2
d74b311e776 community/xmake: move from testing
real 0m 51.11s
user 0m 41.44s
sys 0m 9.56s
Git CommitGraph
Git 在 2.18 版本后引入了提交图的概念,使用单独的提交图文件对提交关系做缓存,所以在理想情况下,最快的查询条件为:
$ git gc --aggressive
$ git commit-graph write --changed-paths
- 全量 pack 保存,避免 Git Object 保存在 loose 时反复文件 IO
- 开启提交图缓存,包括提交修改记录
$ /usr/bin/time git -c core.commitGraph=true log --oneline -n 10 community/xmake/
81380060446 community/xmake: upgrade to 2.9.9
7c21bea5624 */*: replace non-POSIX find -not option
a92fe0ba060 community/xmake: upgrade to 2.9.7
12374870a8f community/xmake: upgrade to 2.9.6
12c188ec967 community/xmake: upgrade to 2.9.5
0096aeef07c community/xmake: upgrade to 2.9.4
4b130eb7f5a community/xmake: upgrade to 2.9.3
d516ffbb476 community/xmake: upgrade to 2.9.2
d74b311e776 community/xmake: move from testing
real 0m 1.99s
user 0m 1.17s
sys 0m 0.81s
提升很明显(但是其它第三方 Git 实现都没类似功能)
Git Blame
Blame 是 Log 的细节版本,不仅找到相关的提交,而且按照行定位每行的最后修改提交,类似糖豆人的蜂巢关卡,直到完成对目标文件当前所有行的修改提交定位,终止遍历。
所以这里有个 ddos 的小技巧,请求一个不存在的文件,Git 不得不遍历所有提交定位最终修改(但是似乎大家普遍缓存了每个文件的最终修改提交,所以可能没用)
References
- https://github.com/libgit2/libgit2-backends
- https://github.com/go-git/go-git/issues/811
- https://github.com/rust-lang/git2-rs/issues/222
- https://github.com/libgit2/libgit2/issues/3027
- https://github.com/eafcc/eafcc-core/blob/master/src/storage_backends/git/p2p/custom_backend_demo.rs
- https://git-scm.com/docs/git-commit-graph
- https://devblogs.microsoft.com/devops/supercharging-the-git-commit-graph/
- https://devblogs.microsoft.com/devops/updates-to-the-git-commit-graph-feature/
Alpine Linux 软件打包
2024 年 1 月 14 日
软件仓库的总部叫 aports,目前是官方 GitLab 实例上的一个超大 repository,所有与官方有关的软件包提交修改都基于仓库的流水线,有什么问题可以查阅 Wiki,也可以前往 oftc.net 上的 #alpine-devel
频道(IRC)寻求帮助。
$ git clone --depth=1 ...
据观察基于邮箱的协作好像不太能用,只能注册 GitLab 使用。找到官方的仓库 fork 到自己名下(就像 GitHub 那样),接下来的大致流程:
- 绑定密钥、设置用户名与用户邮箱
- 克隆自己的 fork 到本地,耐心等待
- 为自己需要修改的地方创建分支
- 修改(测试)并提交,推送新分支到仓库
- 在网页里自己的仓库页面提交合并请求
Alpine Linux 软件包的配置文件 APKBUILD 与 Arch Linux 的 PKGBUILD 非常相似,可以偷偷去他们的网站学习打包经验,但是不要抄袭——发行版不同、工具链不通用、分包策略也不同。同时也非常推荐 Fedora 家的包,他们相比 Arch 更加严格规范,易于参考。
网址
- 包管理器使用 https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper
- 新建软件包 https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package
- 配置文件说明 https://wiki.alpinelinux.org/wiki/APKBUILD_Reference
- 打包相关工具 https://wiki.alpinelinux.org/wiki/Abuild_and_Helpers
- 与 aports 仓库相关的 aports/README.md
- 代码与提交格式要求 CODINGSTYLE.md COMMITSTYLE.md
- Arch Linux 的包 https://archlinux.org/packages/
- Fedora 的包 http://src.fedoraproject.org/
FAQ
由于 musl 引起的编译错误如何解决?
应该都有人遇到过,aports 搜一搜自己也做个补丁
如何知道软件需要的依赖?
构建依赖:abuild rootbld
一个一个添加 dev 包测试
运行依赖:打包时会自动识别大部分动态库,手动加上额外的运行时依赖
编译需要的 gcc 是依赖吗?
build-base
已有,不需要写,apk info -r build-base
查看类似基础组件
网络问题拉不下来构建需要的源码
使用 http 代理,例如:https_proxy=http://localhost:7890/ abuild checksum
软件包依赖另外一个自己打的本地包
abuild rootbld
时添加 ~/packages/testing/
到 ~/aports/testing/.rootbld-repositories
怎么知道安装包里包含什么文件
新版 tar 会自动识别压缩格式,执行tar -vtf file.apk
获得文件列表
每次下载软件包耗时很久
① 开启 setup-apkcache
② $ mirror=http://mirrors.tuna.tsinghua.edu.cn/alpine abuild rootbld
最后如果有其他问题可以联系我
Apktools
2025 年 4 月 13 日 Alpine Linux 相关的基础使用,主要是系统包管理器
mirror
所有软件包的镜像信息保存在 /etc/apk/repositories
,系统默认自带有 vi 编辑器,可以尝试修改。
有三种选择推荐:指定版本号、最新稳定版、滚动版,不推荐混合使用可能造成依赖冲突。
# 国内推荐使用:南阳理工学院开源软件镜像站
# https://mirror.nyist.edu.cn/
# 指定版本号
http://mirror.nyist.edu.cn/alpine/v3.21/main
http://mirror.nyist.edu.cn/alpine/v3.21/community
# 最新稳定版
http://mirror.nyist.edu.cn/alpine/latest-stable/main
http://mirror.nyist.edu.cn/alpine/latest-stable/community
# 滚动版
http://mirror.nyist.edu.cn/alpine/edge/main
http://mirror.nyist.edu.cn/alpine/edge/community
http://mirror.nyist.edu.cn/alpine/edge/testing
修改后刷新生效(不刷新也没事,默认间隔后使用会自动刷新)
$ apk update
$ apk upgrade
cli
已安装软件包信息保存在 /etc/apk/world
中,查看此文件了解系统中显式安装的软件包。
搜索比较弱鸡,推荐用谷歌、官方搜包站、和本人自建服务辅助进行
# 默认
$ apk search helix
# 搜索终端命令
$ apk search cmd:hx
# 搜动态链接库
$ apk search so:libgit2.so
安装和卸载软件包
$ apk add xxx
$ apk del xxx
查看此系统中哪些包依赖 xxx
$ apk info -r xxx
查看已安装的给定包 xxx 内部包含哪些文件
$ apk info -L xxx
example
docker
world
/etc/apk/world
$ apk fix
Setup Aports
2025年4月30日 设置 Aports 本地环境
Abuild
配置本地 Abuild,遵照官方 Wiki
apk add abuild abuild-rootbld alpine-sdk
addgroup xxx abuild
abuild-keygen -a -i
在 ~/.abuild/abuild.conf
可以配置部分设置选项,如颜色、镜像等
Ssh
官方的 GitLab 注册并配置 Ssh 免密,与常见操作相同
cd .ssh
ssh-keygen # 生成 key
cat xxx.pub # 复制到网页里
vim config
ssh -T [email protected]
Git
推荐 pull 时从镜像拉,因为官方的仓库又大又慢,以下是本人初始化过程
Clone
从镜像进行首次 Clone,默认只包括 master 分支
git config --global user.name qaqland
git config --global user.email "[email protected]"
git clone https://mirror.nyist.edu.cn/git/aports
cd aports/
git remote -v
Remote
将镜像上游改名到 mirror,设置官方上游到 origin
git remote add mirror https://mirror.nyist.edu.cn/git/aports
git remote set-url origin [email protected]:qaqland/aports.git
最终效果如下
$ git remote -v
mirror https://mirror.nyist.edu.cn/git/aports (fetch)
mirror https://mirror.nyist.edu.cn/git/aports (push)
origin [email protected]:qaqland/aports.git (fetch)
origin [email protected]:qaqland/aports.git (push)
Branch
更新 remote 并把 master 绑定到 mirror,因为 master 只用来同步 Pull
git fetch origin
git fetch mirror
git branch --set-upstream-to=mirror/master
git branch -vv
每当自己有新修改时,推送自己的新分支到 origin
git checkout -b xxx
git commit
git push -u origin xxx
Abuild & Rust(Cargo)
rustup
setup mirror
keep offline
options="net" # cargo fetch
prepare() {
default_prepare
cargo fetch --target="$CTARGET" --locked
}
build() {
cargo auditable build --frozen --release
:
}
clap(completions)
subpackages="
$pkgname-bash-completion
$pkgname-fish-completion
$pkgname-zsh-completion
"
build() {
:
./target/release/mdbook completions bash > $pkgname.bash
./target/release/mdbook completions fish > $pkgname.fish
./target/release/mdbook completions zsh > $pkgname.zsh
}
package() {
:
install -Dm644 $pkgname.bash "$pkgdir"/usr/share/bash-completion/completions/$pkgname
install -Dm644 $pkgname.fish "$pkgdir"/usr/share/fish/vendor_completions.d/$pkgname.fish
install -Dm644 $pkgname.zsh "$pkgdir"/usr/share/zsh/site-functions/_$pkgname
}
make patches
$ git init .
$ git add Cargo.toml Cargo.lock
$ git diff > ../../01-update-lock.patch
download patch from GitHub and GitLab
tests
options="!check" # upstream has no test
check() {
cargo test --frozen
}
$ cargo test -- --skip xxx
patches
use system library
- env
- config.toml
- patch