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
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91928
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91929
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/92020
原计划在国庆假期把 Python 相关的包努力做一做,setup.py install 相关函数将要在
月底废弃,但是经过努力决定开摆,原因有下:
- 当前在不测试的情况下完全无法分辨是否依赖有缺失
- 很多上游不着急,构建系统的切换还是上游做合适
- 缺少项目经验,不明白 nox、pytest、uv 等的工作原理
- 使用的 py3-gpep517 脚本协议错误,提了修改没人管
这个月 pmos 的人引入了 systemd 子包功能到 abuild,也算是对社区的又一次冲击(摊手), 不知道 WHLUG 能不能做个闪电分享讲讲这些事:
- https://gitlab.alpinelinux.org/alpine/abuild/-/merge_requests/272
- https://gitlab.alpinelinux.org/alpine/abuild/-/merge_requests/433
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/91516
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/84539
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/85504
- https://github.com/systemd/mkosi/pull/3781
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
10 月用两个周末的时间完全重写了之前的配置文件(主要是快捷键)的逻辑,
大幅度依赖 getopt、getsubopt、wordexp 这三个标准库函数。
修改之后实现了两个目标:
- 决定把部分复杂功能从 WM 本体中丢出去,降低配置的复杂度
- 命令行与配置文件使用同一套解析函数
认真学习了 wlr_output_layout_output 相关内容(并不认真):
显示器的 enable 在非原生后端时可能会修改,但是如果 output 不在整个 layout 中,
我们也应该认为这个显示器是 disable 的。应用到具体场景就是用户的笔记本显示器熄屏,
此时物理上显示器连接,但是显示器没有真正投入使用,用户可能扣着盖子用外接屏幕。
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-fast-export 耗时的三倍,查询时间低于 git-cli
下一步计划:当前的性能瓶颈有两个,object-tree 的 read 和 diff,它们被广泛压缩在 packfile 中,不仅 IO 紧张而且 CPU 紧张。但是逆转过来想,压缩本身也是一种信息? 可能需要写一点优化后的读取方法,在读取时抓取更多信息。
备用计划:手动封装 git
- 找个支持流式解析库,优化现在的 fast-import
- 魔改 fast-export 输出 json,然后接 serde::json
- 封装原版 git-diff-stat 用 nom
QT
发现一个 Bug,右键弹出菜单中,分隔线上的点击事件无效但会导致弹出菜单消失。 使用 AI 写了个复现小 demo,提交问题到了 QT 上游。
# 感谢子冲帮忙修复 LSP 爆红
sudo apt install libstdc++-13-dev
不知道下一步会不会学习 QT 做点小工具。
论文
导师喊我改论文,这让我非常难过,尽管我知道这件事情只要完成就再也不会有烦恼了。 但我还是很难过,无法诉说的压力和恐惧,让我没有失去了对生活 60% 的注意力和快乐。 有以下几个方面需要改:
- 标点符号、文字、句子之间的连贯性
- 图表、公式、大小写
好像没了。我尽量去做吧。祝我开心。
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
Misc
博客 or 主页
修好了个人主页,去掉万年不更新的 Hugo 放了个简单的 Html 单文件,CSS 是从之前的 hugo-rss-only 仓库薅来的,设计风格来自 Nginx 默认页面。
计划在以后的 hash 按照月为单位更新日志,有其他长短文字按照内容分类,也放这里。
FetchSrc
做了一个用于下载、缓存、解压源码的 rust 小工具 fetchsrc,我的 Option 和错误处理 经验还是太匮乏了,这里写的很吃力(和 shell 差不多吃力了!)。 后面有空找个开源项目,跟踪一段时间学习错误处理。
LSP
clangd 原来默认也会搜索 build/compile_commands.json,再也不抱怨 LSP 不干活了,
之前全靠软链接和 .clangd 配置文件苟活。
SSH
终于(又一次)理解了端口转发的方向
ssh [-L|-R] [FROM_IP:]FROM_PORT:DESTINATION:DESTINATION_PORT [USER@]SSH_SERVER
对于 -L 本地端口转发来说,流量从本地端口进入,转发到 DESTINATION,所以是 LOCAL
1024
本来想搭配“麒麟”这首歌做个混剪小视频,但是和 xingji 交流后发现代码提交截图 不适合做素材,远远不如画画、徒步等,所以还是算了。
Donations
这次选择了 asciinema 项目,恰好新版本发布。
捐款上也有一些小想法:
- 参与代码贡献的项目不捐:出力不出钱,比如 Alpine Linux
- 账面还有很多钱的不捐:钱不能解决这种维护问题,比如 libgit2
- 没用过 or 不好用的不捐:特指希望倒闭的 matrix
没事可以去 https://opencollective.com/search 逛逛,想想以后自己的项目怎么搞钱
如果 11 月没想好给谁就去订阅一下 LWN,或者给服务器加点钱什么的。
2025-11
wless
本月主要关注client的resize和output的frame事件之间的关系,相关参考内容有:
- https://github.com/DreamMaoMao/mangowc/issues/293
- https://github.com/swaywm/sway/blob/055be4ec35eec4eaaf066a18ccbf5132ebed0694/sway/desktop/output.c#L374
- https://github.com/swaywm/sway/pull/2072
- https://wayland.app/protocols/xdg-shell#xdg_surface:request:ack_configure
问题的矛盾在于窗口的大小和窗口的位置改变不同步,如果我想让窗口进入全屏:
- WM立即将窗口的左上顶点移动对齐到左上角
- WM请求窗口改变大小,此时窗口占据了1/4屏幕(举例)
- 窗口响应WM,变为全屏,此时窗口全面覆盖
窗口的位置是WM没有延迟随意操作的,而窗口的大小需要客户端自己去响应,两者几乎不会在同一帧完成。 在这个时间裂缝里,窗口从1/4到全屏不可避免的闪了一次。 还没想好怎么处理,可能需要卡住显示器,或者等窗口响应的pre-commit里再去改变位置。
12月应该没时间看,可能又要等到明年了。
bushi
nom似乎可以对stream进行解析,之前可能没注意到? 研究了一番放弃了这个灵车的想法!(见后)
从i5换到了n5105,耗时差不多是两倍:
git-log--stat 5m16s
git log --oneline --stat > /dev/null
git-fast-export 3m45s
git fast-export --no-data --fake-missing-tagger --signed-tags=strip --all > /dev/null
据研究packfile包含的压缩信息确实可用(好像废话,不然git是怎么把20G压缩到600M的)
git verfy-pack -v .git/objects/pack/pack-*.idx
但是没有稳定性保证,压缩的基准与提交顺序有关系但不保证, 高概率出现ABCD等多个tree都基于同一个base差值压缩的情况。 因此当前决定还是不做这个了, 我们还是愉快的攀附在git二进制文件上,对输出进行解析吧!
- 扫描所有refs,存储到数据库
- 从refs中的一个开始,遍历commit到存在时停止
- 重新于root索引commits
- 基于git-log拿到changed-path,文件修改存数据库
- 重复2直到完成所有
- 从上到下构建祖先跳表,存另一个表
想了两周要不要每个仓库分一个SQLite数据库
- 更方便管理(主要原因)
- 可能剩下一个复合主键的空间(但是sha256sum对主键来说还是太长了)
但是缺少能够管理256-2048个独立SQLite数据库的现有库(对CGI来说这不是问题), 所以还是保持现状吧。
记录下体积大小的对比,所以不压缩真顶不住。
$ du -sh loose-aports/
28.7G loose-aports/
$ du -sh aports/
790.1M aports/
重新设计了bushi的架构:
- 把之前头疼的配置问题拆到第一部分(之前网络端口设置和仓库设置混在一起感觉别扭)
- 为了避免插入中String -> CString -> SQLite的多次复制问题,索引更新部分换C了
- 网站托管部分还是上Rust的Tokio生态,此时SQLite只读,所以可以大幅度开启连接池加速
bushi-hook(-)
collect git repositories' path and rediect them to next program's stdin
cron, sleep or triggered by something else
copy bushi.db and send signal to bushi-web, it will refresh connection
bushi-index(C)
$ bushi-index [-vh] [-c] [-t stage.db] repo ...
$ bushi-index [-vh] [-c] [-t stage.db] -
-c cleanup unused repositories
-t target sqlite database
- read from stdin
repo GIT_DIR
read path from stdin and update index in stage.db
bushi-web(Rust)
$ bushi-web [-vh] [--index bushi.db] [--host HOST] [--port PORT]
open bushi.db with read only option, host web
firefox & dde
火狐在dde上一直有缩放问题,主要症状是系统设置缩放(非1)后,火狐界面异常大。 在论坛上有很多反馈:
- Firefox火狐浏览器界面异常变大原因-论坛-深度科技
- V25和火狐浏览器有点冲突-论坛-深度科技
- deepin 25 火狐浏览器莫名的分辨率变大-论坛-深度科技
- 火狐浏览器firefox高分屏设置无效-论坛-深度科技
- 火狐浏览器问题-论坛-深度科技
- Firefox 窗体整体缩放变大问题-论坛-深度科技
- 火狐浏览器缩放-论坛-深度科技
- 火狐 mozilla 中文版标题栏宽大-论坛-深度科技
- 火狐浏览器分辨率问题。-论坛-深度科技
- 火狐浏览器缩放过大在浏览器中禁用注销后自动复原-论坛-深度科技
- 火狐浏览器的显示比例有问题-论坛-深度科技
- 火狐浏览器界面放大-论坛-深度科技
- 火狐的界面变得好大,怎么办-论坛-深度科技 2022-08-12
从症状上看,缩放不正常时,打开 about:config 配置页面,layout.css.devPixelsPerPx
的默认值 -1 被修改为了当前系统缩放值,如果恢复默认就缩放表现正常。
重启火狐,这个值又被改了,所以肯定是系统干的。 在GitHub搜索发现是dde-daemon仓库的逻辑。关掉相关服务,一切正常:
systemctl --user stop org.dde.session.Daemon1.service
从代码看,dde-daemon扫描用户的 .mozilla/firefox/*/prefs.js 文件并设置上述项,
删掉这块的逻辑就好了,测试下来确是如此。
这部分的代码是从startdde搬过来的,而在startdde项目当年的提交是在2017年:
xsettings: Add dpi supported for firefox · linuxdeepin/startdde@2636914
https://github.com/linuxdeepin/startdde/commit/263691490fb4e1ce36859b606361c1b718bfef30
回到论坛,用户有提出一个规避方案,设置browser.display.os-zoom-behavior的值为0。 这个选项的作用是设置火狐如何响应操作系统的缩放,在2022年7月26日发布的103版本中新增
Bug 1773633 - Allow configuring OS zoom behavior. r=tnikkel · mozilla-firefox/firefox@c7106fc
https://github.com/mozilla-firefox/firefox/commit/c7106fcb8111c4139094f444b78c5ed1632883ec
到这里就一目了然了,2022年103版本开始,火狐新增了一个默认开启的随系统缩放选项, 而我们的系统在2017年添加了手动修改缩放的逻辑,与此处的随系统缩放叠加,造成了界面异常大。 删掉2017年的缩放逻辑就好了。
deepin & sound after startup it quiet
- https://bbs.deepin.org/post/293046
- https://bbs.deepin.org/post/292279
- https://bbs.deepin.org/post/292101
- https://bbs.deepin.org/post/292006
- https://bbs.deepin.org/post/293722
论坛有用户说开机重启后音量很小,我自己使用两个sink都没复现。 目前希望用户多给一点日志,包括关机前后的音量:
pactl get-sink-volume @DEFAULT_SINK@
pactl list sinks
有点怀疑是WirePlumber的音量保存与DDE内部的逻辑冲突,因为他们的音量计算方法不同, 我们系统上的立方音量改为了1.8
import math
# percent to dB volume
20 * math.log10(X ** 3)
# dB to percent volume
(10 ** (-Y / 20)) ** (1/3)
更新:发现了另外一个会导致音量变小的bug,是我们自己补丁中的MONO算法问题, 猜测关系很大,等这个问题修复再测。
alpine
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/92432
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/92719
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/92722
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/92723
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/92730
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/92781
- https://gitlab.alpinelinux.org/alpine/alpine-conf/-/merge_requests/272
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/93058
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/93070
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/93494
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/93543
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/93739
- https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/93741
双十一购入的电子产品是32G的U盘,在Windows里成功装上了「Linux To Go」
- 下载官网的ISO镜像,标准版就行,虚拟机里网络一般都很好
- 创建vbox虚拟机并挂载盘片,默认512M内存,不需要本地硬盘
- 虚拟机以USB3.0格式挂载U盘设备,
lsblk应该能看到设备是/dev/sdX - 设置环境变量
USE_EFI=1SWAP_SIZE=0并执行setup-alpine
虚拟机一般不开启UEFI所以需要手动指定一下脚本中的EFI不然实体机不识别。 SWAP对U盘要比较高,关掉提高寿命。其他的优化方法暂未发现后续再看。
misc
alsa-lib
alsa居然合并了,上周本来想关掉这个PR来着。 原本的逻辑是先判断大小,再进行线性缩放,可能因为整数除法掉出最开始的判断范围, 所以我改成了先计算最后判断。
snd_tlv_convert_to_dB: Fix mute handling forMINMAX_MUTEtype by qaqland · Pull Request #478 · alsa-project/alsa-lib
alsa-utils
代码盯着看总是有收获,不小心发现errno返回时丢了个负号
alsactl: fix error handling in
check_control_cdev()by qaqland · Pull Request #310 · alsa-project/alsa-utils
WirePlumber
wpctl: add bash completions (!762) · Merge requests · PipeWire / wireplumber · GitLab
https://gitlab.freedesktop.org/pipewire/wireplumber/-/merge_requests/762
其实发现PipeWire本体也没有,但是那边命令太多了,就只做了这里。
language
突然想到有没有什么嵌入式的解释型语言可以同时满足以下几点
- 与C交互良好
- 不带JIT也速度快
- 语法简洁现代
aosc dde port
A: 我觉得这个事情既然不是一锤子买卖,就真得有人持续做
B: 是的,所以现阶段只能drop,不然搞个人临时处理两天意义也不大,不能持续搞的话对大家来讲都是个负面的
后续如果有人做aosc的port,也许能从这里再捡回来
deepin Desktop Environment: drop, orphaned by MingcongBai · Pull Request #13548 · AOSC-Dev/aosc-os-abbs
slog
https://github.com/qaqland/slog
slog是一个C语言适用的结构化日志输出库,基本完工正在修bug写测试补文档阶段。
在推进的过程中收到了XJJ和C语言中文群群友的大力帮助、无私指导, 以后应该能把聊天记录下来,出本小册子《C语言系统编程技巧》。
donations
一开始希望去OpenBSD,但是只有paypal捐款途径,我的国区账号不支持,所以换了家
Git Summary
2025-11-12
Git介绍及初始化
- https://git-scm.com/
- https://git-scm.com/book/zh/v2
- https://git-scm.com/docs
- https://github.com/git-guides
- https://lore.kernel.org/git/
- https://training.github.com/downloads/zh_CN/github-git-cheat-sheet/
- https://planet.deepin.org/deepin-2022-01-21-git-from-the-bottom-up-git/
Git是一个分布式(Version Control Software,CVS)版本控制工具,事实上的业界标准。
重要提示:Git一定要和SSH搭配用!
生成SSH密钥
# 进入用户的 ssh 目录
$ cd .ssh/
# 本地创建密钥对
$ ssh-keygen
Generating public/private ed25519 key pair.
Enter file in which to save the key (~/.ssh/id_ed25519): key-name # 输入名字
Enter passphrase for "key-name" (empty for no passphrase): # 留空
Enter same passphrase again: # 留空
Your identification has been saved in key-name
Your public key has been saved in key-name.pub
The key fingerprint is:
SHA256:cUcRFTTxmKf1hXklQBBrfE8Muk8+AdwSwi9sM7Gc4KA anguoli@anguoli-PC
The key's randomart image is:
+--[ED25519 256]--+
| .. ++B*Bo.|
| . . o+ * o O.|
| . o +.=X + B *|
| E . X+.* o =o|
| .S+. o o .|
| + . |
| + |
| . |
| |
+----[SHA256]-----+
# 生成了两个文件
$ ls -l key-*
-r-------- 1 anguoli anguoli 411 11月10日 14:06 key-name
-rw-r--r-- 1 anguoli anguoli 100 11月10日 14:06 key-name.pub
# 公钥可以随意上传、分发
$ cat key-name.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGKl7MdmTVHQPbjJ2jKDzcMlLZre/eaEgUZaC9HcODR1 anguoli@anguoli-PC
# 私钥保存在本地,注意0400权限
$ cat key-name
-----BEGIN OPENSSH PRIVATE KEY-----
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
LZre/eaEgUZaC9HcODR1AAAAEmFuZ3VvbGlAYW5ndW9saS1QQwECAw==
-----END OPENSSH PRIVATE KEY-----
创建SSH配置
# ~/.ssh/config
Host gitlabwh.uniontech.com
Hostname gitlabwh.uniontech.com
User git
IdentityFile ~/.ssh/gerrit
Host gerrit.uniontech.com
Hostname gerrit.uniontech.com
Port 29418
User git
IdentityFile ~/.ssh/gerrit
Host github.com
Hostname ssh.github.com
User git
Port 443
ProxyCommand nc -v -x 127.0.0.1:7897 %h %p
IdentityFile ~/.ssh/github
Git使用到的SSH与普通用户字段基本相同
- Host 别名/配置名,对SSH有用
- Hostname 真实主机地址,IP或者域名
- IdentityFile 私钥绝对路径
配置代理推荐使用netcat-openbsd包提供的nc命令,可以加速走SSH的全部Git操作
- ProxyCommand 代理
-4/-6指定仅IPv4或IPv6-X [4|5|connect]SOCKS v.4, SOCKS v.5 and HTTPS proxy,默认5-x address:port%hHostname,%pPort
测试Git&SSH
ssh -T git@Host
Disable pseudo-terminal allocation. 禁用伪终端分配,不会交互式Shell会话
$ ssh -T [email protected]
Connection to ssh.github.com 443 port [tcp/https] succeeded!
Hi qaqland! You've successfully authenticated, but GitHub does not provide shell access.
$ ssh -T gitlabwh.uniontech.com
Welcome to GitLab, @ut006245!
$ ssh -T gerrit.uniontech.com
[email protected]: Permission denied (publickey).
Git配置
# 默认分支
git config --global init.defaultBranch main
# 用户和邮箱
git config --global user.name "qaqland"
git config --global user.email "[email protected]"
# 编辑器
git config --global core.editor "vim"
# 快捷别名
git config --global alias.ss status
git config --global alias.can "commit --amend --no-edit --date=now"
这些配置都会写入文件~/.gitconfig
[user]
name = qaqland
email = [email protected]
[init]
defaultBranch = main
# 配置http/https需要的代理
[http "https://github.com/"]
proxy = socks5h://127.0.0.1:7897
[alias]
can = commit --amend --no-edit --date=now
ss = status
[core]
editor = vim
Git的基本数据结构
- 标准Git仓库的版本数据保存在仓库的
.git路径中 - Git命令支持的环境变量
GIT_DIR应当指向仓库的.git路径 - 裸仓库(常见于服务端)只有
.git路径和内容
Objects
Git的大部分数据都以Object的形式保存在.git/objects路径下,
每个Object都有自己的UUID也叫做Oid,以SHA1或SHA256格式存在,同一仓库不能混合使用,
大部分操作都可以尝试Oid的前缀缩写。
可以通过命令git cat-file -t OID检查类型,常见Object种类有:
- CommitObject 提交
- TreeObject 目录
- BlobObject 内容
$ git init test-backend
已初始化空的 Git 仓库于 /tmp/test-backend/.git/
$ cd test-backend/
$ echo 1 > 1
$ echo 2 > 2
$ git add .
$ git commit -m test
[main(根提交) 084ca84] test
2 files changed, 2 insertions(+)
create mode 100644 1
create mode 100644 2
$ tree .git/objects/
.git/objects/
├── 08
│ └── 4ca8409b7102d53b1b279627cb41ccba5bac98 # commit
├── 0c
│ └── fbf08886fca9a91cb753ec8734c84fcbe52c9f # blob
├── d0
│ └── 0491fd7e5bb6fa28c517a0bb32b8b506539d4d # blob
├── de
│ └── 0ea882503cdd9c984c0a43238014569a123cac # tree
├── info
└── pack
7 directories, 4 files
Loose & Packed
Git有两种Objects的保存后端,分别是Loose松散后端和Packed紧实后端。
如上文所示以Oid的前2位为前缀创建目录树的行为就是Loose松散后端。 松散后端由本地提交产生,其中对象仅以zlib算法压缩,占用空间较大,但写入速度快。
本地执行git gc会repack这些对象到紧实后端
$ git gc
枚举对象中: 4, 完成.
对象计数中: 100% (4/4), 完成.
使用 8 个线程进行压缩
压缩对象中: 100% (2/2), 完成.
写入对象中: 100% (4/4), 完成.
总共 4(差异 0),复用 0(差异 0),包复用 0(来自 0 个包)
$ tree .git/objects/
.git/objects/
├── info
│ ├── commit-graph
│ └── packs
└── pack
├── pack-5468214027a9484a198c7f3c5a6df15f12f48d9d.idx
├── pack-5468214027a9484a198c7f3c5a6df15f12f48d9d.pack
└── pack-5468214027a9484a198c7f3c5a6df15f12f48d9d.rev
3 directories, 5 files
为了节省带宽、减小请求,通常在网络交互时也会repack(传输的部分)
# 文件协议,也算传输,有repack
git clone file:///tmp/test-backend/ test-clone-file
# 路径克隆,直接复制,没有repack
git clone /tmp/test-backend/ test-clone-path
先看Loose,我们写个小脚本查看实际保存的Object
#!/usr/bin/env python3
import zlib, sys
data = open(sys.argv[1], "rb").read() if len(sys.argv) > 1 else sys.stdin.buffer.read()
result = zlib.decompress(data)
sys.stdout.buffer.write(result)
CommitObject
提交Commit指向自己的历史来源,有0个、1个、2个或者更多个Parent Commit。 不同的数量表示了这次提交的不同种类,是root节点还是fast-forward这种单线链表, 或者是合并分支的Merge节点。超过2的情况不多见,是特殊的合并节点,比如内核里有:
Merge branches 'arch-alpha', 'arch-arm', 'arch-arm64', 'arch-avr32', … · torvalds/linux@9b25d60
https://github.com/torvalds/linux/commit/9b25d604182169a08b206306b312d2df26b5f502
A commit object may have any number of parents. With exactly one parent, it is an ordinary commit. Having more than one parent makes the commit a merge between several lines of history. Initial (root) commits have no parents.
当然像提交信息、提交人、提交时间、以及Committer与Author之间的区别这里不再赘述。 Commit还保存了提交时的文件(树)快照,指向当前Commit相随的Tree。
- TreeOid
- Parents' CommitOid
- Author
- Committer
- Commit Message
这就是一个Commit对象包含的全部,当一个普通线性提交发生时, Git会扫描当前WorkTree生成TreeOid,底层保存的数据中并不关心此次提交的修改。
$ git cat-file -p 91520a2890c9cd9e99bf6cf0148811c33ffe5a3b
tree f93e3a1a1525fb5b91020da86e44810c87a2d7bc
author qaqland <[email protected]> 1762827659 +0800
committer qaqland <[email protected]> 1762827659 +0800
add readme
$ git cat-file -t 91520a2890c9cd9e99bf6cf0148811c33ffe5a3b
commit
$ git cat-file -s 91520a2890c9cd9e99bf6cf0148811c33ffe5a3b
155
$ git cat-file commit 91520a2890c9cd9e99bf6cf0148811c33ffe5a3b | wc -c
155
以这次提交为例,把原始文件zlib解压后16进制打印输出如下
$ ./zlib-cat.py .git/objects/91/520a2890c9cd9e99bf6cf0148811c33ffe5a3b | xxd
00000000: 636f 6d6d 6974 2031 3535 0074 7265 6520 commit 155.tree
00000010: 6639 3365 3361 3161 3135 3235 6662 3562 f93e3a1a1525fb5b
00000020: 3931 3032 3064 6138 3665 3434 3831 3063 91020da86e44810c
00000030: 3837 6132 6437 6263 0a61 7574 686f 7220 87a2d7bc.author
00000040: 7161 716c 616e 6420 3c71 6171 4071 6171 qaqland <qaq@qaq
00000050: 2e6c 616e 643e 2031 3736 3238 3237 3635 .land> 176282765
00000060: 3920 2b30 3830 300a 636f 6d6d 6974 7465 9 +0800.committe
00000070: 7220 7161 716c 616e 6420 3c71 6171 4071 r qaqland <qaq@q
00000080: 6171 2e6c 616e 643e 2031 3736 3238 3237 aq.land> 1762827
00000090: 3635 3920 2b30 3830 300a 0a61 6464 2072 659 +0800..add r
000000a0: 6561 646d 650a eadme.
起始commit和155表示类型和大小(不包括头部),和前文的cat-file -t|-s对应
| HEX | Description |
|---|---|
00 | Null character |
20 | Space |
0a | Line Feed |
30 | Zero |
TreeObject
$ git cat-file -p f93e3a1a1525fb5b91020da86e44810c87a2d7bc
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 README.md
$ git cat-file -t f93e3a1a1525fb5b91020da86e44810c87a2d7bc
tree
$ git cat-file -s f93e3a1a1525fb5b91020da86e44810c87a2d7bc
37
$ git cat-file tree f93e3a1a1525fb5b91020da86e44810c87a2d7bc | wc -c
37
$ git cat-file tree f93e3a1a1525fb5b91020da86e44810c87a2d7bc | xxd
00000000: 3130 3036 3434 2052 4541 444d 452e 6d64 100644 README.md
00000010: 00e6 9de2 9bb2 d1d6 434b 8b29 ae77 5ad8 ........CK.).wZ.
00000020: c2e4 8c53 91
一个TreeObject保存任意数量的TreeObject和BlobObject,保存信息有子目录或文件的 权限(条目类型)、条目名称、条目Oid。
$ ./zlib-cat.py .git/objects/f9/3e3a1a1525fb5b91020da86e44810c87a2d7bc | xxd
00000000: 7472 6565 2033 3700 3130 3036 3434 2052 tree 37.100644 R
00000010: 4541 444d 452e 6d64 00e6 9de2 9bb2 d1d6 EADME.md........
00000020: 434b 8b29 ae77 5ad8 c2e4 8c53 91 CK.).wZ....S.
仓库的Oid长度是统一且固定的,如果采用SHA1就是40个十六进制字符,SHA256拓展到64个。
在TreeObject对象中,条目是按照路径排序的,TreeObject条目会在排序时默认带上尾随斜杠
The entries in a tree are ordered in the path order, which means that a directory entry is ordered by adding a slash to the end of it.
So a directory called "a" is ordered after a file called "a.c", because "a/" sorts after "a.c".
git/fsck.c at 6074a7d4ae6b658c18465f10bbbf144882d2d4b0 · git/git
$ git ls-tree 00a741484baadea211c493ebbf5fb00208f86493
100644 blob 85d4df20ce3cce7d9bf31f98ee2239683fdc776e .editorconfig
100644 blob 0493cf8daa1629bdba77e9bdde6106ff9783fc50 .gitattributes
040000 tree 1297f3467c63c4ff48a98fd2a24d747c68aa3f80 .githooks
040000 tree b79b5cc40348d01f293bd9cbf8483cc077459c38 .github
100644 blob 43fb11d16d0008c3314eeab288f49b6189d680dd .gitignore
100644 blob 70d4ae27a4b250d03eaded0509f86347e6192c42 .gitlab-ci.yml
040000 tree aefeddacabdf8150f47faf3aabd098c5c7c32440 .gitlab
100644 blob b7bf3d42eb30206489f669629bd95c8fcb2d2ee6 .mailmap
100644 blob 8d11373a95ba9d87c8a51193c37d5aa02e2dd301 CODINGSTYLE.md
100644 blob ab8c36d86bcf6eaaff348f1c24bf6594bdcabdfb COMMITSTYLE.md
100644 blob 27d11c0186c4e846c5d5f64af7121cadbb8d785f README.md
040000 tree 42aae92dacdae1a5d53531d23ddbac8aeabd13e8 community
040000 tree 0cc9f0ba0475a8302206e431ca51319ce21d6b54 main
040000 tree dcaedd687a45f21b59296a063787a5ee15385716 scripts
040000 tree 8f5eebf9181a091b171b123f6377108560f5ecdf testing
BlobObject
没有任何技巧和优化的:类型、大小、内容,不保存自己的名称
假如文件内容为Hello World\n:
# 文件末尾有换行
$ hexdump -C hello
00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 0a |Hello World.|
0000000c
# 文件长度为12
$ wc -c hello
12 hello
可以计算得到此时的sha1sum
# 类型 + 空格 + 长度 + NUL + 内容
$ printf "blob 12\0Hello World\n" | sha1sum
557db03de997c86a4a028e1ebd3a1ceb225be238 -
添加文件并提交后可以看到git给出的BlobOid与我们手动计算的相同
$ tree .git/objects/
.git/objects/
├── 11
│ └── 7c62a8c5e01758bd284126a6af69deab9dbbe2
├── 55
│ └── 7db03de997c86a4a028e1ebd3a1ceb225be238 <<< 这里
├── f7
│ └── a2bdf7b9df15cdbc88907855a2f55170839af8
├── info
└── pack
6 directories, 3 files
Reference
引用(Reference)类似C语言的指针,
分为直接引用(指向具体的Oid)和符号引用(指向其他引用)两种,
保存在.git/*HEAD、.git/packed-refs和.git/refs/路径下。
直接引用(Direct Reference)存储的是完整的Oid, 如果指向的提交被删除或修改,直接引用可能失效(成为悬空引用), 如果直接引用被删除,可能导致指向的一系列对象变为垃圾对象(分离状态)。 常见的直接引用有:
refs/heads/main分支refs/tags/v1.0标签refs/remotes/origin/main远程分支
符号引用(Symbolic Reference)本身不指向具体对象,而是指向另一个引用,相当于间接指针。
最常见的HEAD就是符号引用,指向当前分支(如refs/heads/main)
$ cat .git/HEAD
ref: refs/heads/main
$ cat .git/refs/heads/main
f7a2bdf7b9df15cdbc88907855a2f55170839af8
$ git log -1
commit f7a2bdf7b9df15cdbc88907855a2f55170839af8 (HEAD -> main)
Author: qaqland <[email protected]>
Date: Tue Nov 11 13:55:14 2025 +0800
hello
// HEAD @ ~ ^
Git的日常使用方法
老生常谈的“工作区”、“暂存区”、“版本库”三个概念,他们是为了确保提交的原子性(完整性)。 类比到Wayland:
- Git:
git commit操作是原子性的。 要么整个暂存区的内容全部成功提交,创建一个新的版本记录;要么失败,版本库保持原样。 永远不会看到一个“只提交了一半文件”的版本库状态。 - Wayland:缓冲区交换操作也是原子性的。 在垂直同步信号到来时,系统会瞬间将指向屏幕的指针从“前缓冲区”切换到“完成后缓冲区”。 用户永远不会看到一帧“画了一半”的图像。
这个“原子性”确保了从一个状态(上次提交的版本/上一帧画面)平滑过渡到下一个状态(新的提交/新的一帧画面)。
git-log
# 显示提交图
$ git log --oneline --graph --all
# 显示提交代码
$ git log -p
# 添加路径筛选
$ git log -- PATH
# 限制数量
$ git log -n NUM
$ git log -5
# 指定分支/标签/提交的历史
$ git log HEAD
如果希望查看当前其他分支的最新提交
$ git branch -v
bump-py3-pytest-asyncio-1.2.0 072086e312b community/py3-pytest-asyncio: upgrade to 1.2.0
lazydocker 729c43ddb8b community/lazydocker: add runtime depends ncurses
* master 00c9c05d721 [ahead 186] main/{kea,pgpool}: rebuild against postgresql 18
new-linyaps-box 9fc79988ded testing/linyaps-box: new aport
new-microsocks 29e2abdbc99 testing/microsocks: new aport
git-diff
对比差异,观察修改。下面举例说明顺位规律,先创建两次提交,针对同一文件做修改:
# 反序输出,从上到下
$ git log --oneline --reverse -p
221152d 111 <<< 第一次提交
diff --git a/hello b/hello
new file mode 100644
index 0000000..2e3e313
--- /dev/null
+++ b/hello
@@ -0,0 +1 @@
+第一次增加
c95a70b (HEAD -> main) 222 <<< 第二次提交
diff --git a/hello b/hello
index 2e3e313..6c27065 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
第一次增加
+第二次增加
git diff期待两个位置参数,旧提交在前,新提交在后。
$ git diff 221152d c95a70b
diff --git a/hello b/hello
index 2e3e313..6c27065 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
第一次增加
+第二次增加
接下来对文件进行修改,添加修改进暂存区,再次修改,结果如下:
$ git blame -s hello
^221152d 1) 第一次增加
c95a70bf 2) 第二次增加
00000000 3) 暂存区增加(未提交)
00000000 4) 工作区增加
工作区 vs 暂存区
$ git diff
diff --git a/hello b/hello
index e364217..9332b3e 100644
--- a/hello
+++ b/hello
@@ -1,3 +1,4 @@
第一次增加
第二次增加
暂存区增加(未提交)
+工作区增加
暂存区 vs HEAD
$ git diff --staged
$ git diff --cached
diff --git a/hello b/hello
index 6c27065..e364217 100644
--- a/hello
+++ b/hello
@@ -1,2 +1,3 @@
第一次增加
第二次增加
+暂存区增加(未提交)
工作区 vs HEAD
$ git diff HEAD
$ git diff HEAD
diff --git a/hello b/hello
index 6c27065..9332b3e 100644
--- a/hello
+++ b/hello
@@ -1,2 +1,4 @@
第一次增加
第二次增加
+暂存区增加(未提交)
+工作区增加
生成补丁的格式为Unified Diff Format,一种标准化的补丁格式,被广泛用于软件开发和版本控制系统。
$ git diff
diff --git a/hello b/hello
index e364217..9332b3e 100644
--- a/hello
+++ b/hello
@@ -1,3 +1,4 @@
第一次增加
第二次增加
暂存区增加(未提交)
+工作区增加
对于补丁来说,前两行算注释可以删掉,也可以手动增加一点描述信息。
重要的部分从@@行开始,这是变更块头(chunk header)描述了修改发生的上下文:
@@ -旧版本起始行,旧版本行数 +新版本起始行,新版本行数 @@
如果需要手动修改生成的补丁,注意补丁显示的代码行数要和变更块头的描述对应:
这是注释部分,可以随意写。以下两条命令生成的补丁上下文范围不同,但效果相同
$ git diff -U2 <<< 指定上下文范围2
diff --git a/hello b/hello
index e364217..9332b3e 100644
--- a/hello
+++ b/hello
@@ -2,2 +2,3 @@ <<<
第二次增加
暂存区增加(未提交)
+工作区增加
$ git diff -U3 <<< 指定上下文范围3
diff --git a/hello b/hello
index e364217..9332b3e 100644
--- a/hello
+++ b/hello
@@ -1,3 +1,4 @@ <<<
第一次增加
第二次增加
暂存区增加(未提交)
+工作区增加
git-email
用的不多,推荐教程
Learn to use email with git!
git-reset
软重置,将之前的提交"取消",所有修改保留在暂存区
$ git reset --soft
Before: A - B - C (HEAD)
After: A - B (HEAD)
C 的修改在暂存区
默认混合重置,重置暂存区,修改保留在工作区
$ git reset [--mixed]
Before: A - B - C (HEAD)
After: A - B (HEAD)
C 的修改在工作区(未暂存)
硬重置,丢弃所有未提交的修改,完全回退到指定提交的状态
$ git reset --hard
Before: A - B - C (HEAD) + 工作区修改
After: A - B (HEAD) # 完全回到B的状态,所有修改丢失
硬重置很危险,如果误删可以尝试恢复:
# 查看所有操作历史
$ git reflog
# 找到重置前的提交哈希
$ git reset --hard HEAD@{1} # 恢复到前一个状态
恢复历史将会在git gc后清空。
git-rebase
更新当前分支到主分支最新状态
# 初始状态
A - B - C (main)
\
D - E (feature)
$ git checkout feature
$ git rebase main
# 变基后状态
A - B - C (main)
\
D' - E' (feature)
交互式操作提交
# 不包含起点
$ git rebase -i 基准OID
# 修改最后5个提交
$ git rebase -i HEAD~5
# 把其中的pick改为edit
$ git add
$ git commit --amend
$ git rebase --continue
注意rebase会修改基准提交之后所有提交的Oid
git-bundle
如何把本地的仓库备份或发送给别人
$ git clone --mirror https://github.com/alpinelinux/apk-tools.git
$ cd apk-tools
$ git bundle create apk-tools.bundle --all
生成的bundle文件就是一个完整仓库的打包,企业微信或者U盘发送后,再clone出来就行
$ du -sh apk-tools.bundle
4.1M apk-tools.bundle
$ git clone --bare apk-tools.bundle <new directory>
如果都是自己的电脑有SSH,直接clone就行
$ git clone n5105:~/dotfiles
正克隆到 'dotfiles'...
remote: Enumerating objects: 23, done.
remote: Counting objects: 100% (23/23), done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 23 (delta 4), reused 0 (delta 0), pack-reused 0 (from 0)
接收对象中: 100% (23/23), 完成.
处理 delta 中: 100% (4/4), 完成.
Git的内部优化算法
理论上讲Git中有三个大类别的对象:Commit、Tree、Blob,具体到解析时还有Commit的同类Tag及Note。
这些对象以各自Object的hash作为Oid索引,经zlib压缩后保存在.git/objects下的文件中,
纯正文件系统驱动,并在需要时被解析。
Git 的数据结构为写多读少设计,因此其他程序应避免将 Git 作为数据库使用。
CommitGraph
Git中的每次提交都会创建对应的CommitObject,但是当需要遍历仓库历史的时候, 大量读就成了一个问题。 Git并没有在Commit中描述这次提交修改了哪些文件,所以若要知晓给定文件的最后修改日期,过程较为艰难:
- 获取Commit的TreeOid与Parent Commit的TreeOid
- 在两个Tree Object中遍历,查找修改的文件
- 检查给定文件是否在本次Commit中修改
- 重复上述操作,直到两次提交的树之间存在期望差异
Git内部在第二步和第三步之间有Diff优化,仅对比给定的路径, 但整体在没有对应数据结构的情况下进行类似的结构化查询还是相当消耗性能, 面对稍微大一点的仓库,时间来到秒级:
$ /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在2.18版本后引入了提交图(CommitGraph)的概念,保存提交相关的额外索引信息到
.git/objects/info/commit-graph。
针对每个对象,使用独立的布隆过滤器对提交修改做缓存,理想情况下,最快的查询条件为:
# 激进垃圾回收并repack
$ git gc --aggressive
# 创建带有路径修改信息的提交图索引
$ git commit-graph write --changed-paths
经过上述修改性能提升明显:
$ /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-commit-graph Documentation
- Supercharging the Git Commit Graph - Azure DevOps Blog
- Updates to the Git Commit Graph Feature - Azure DevOps Blog
Packfile
算法部分暂时略,核心思想就是差异压缩:
- 所有对象按照Oid顺序保存方便mmap与二分
- 在文件开头创建范围索引
- 对象之间按照窗口期查找“基对象”进行差异压缩
- 多个Packfile之间创建Oid索引
经过差异压缩,体积可实现有20倍的缩小。接下来进行演示
读一点随机数模拟文件内容
$ head -c 500 /dev/urandom | base64 | head -n 50 > random_output.txt
在这个文件的基础上创建文件2
$ cp random_output.txt random_output_2.txt
$ echo "add new line" >> random_output_2.txt
直接添加提交,Git不会主动repack压缩,此时是原始Loose Objects存储
$ ls -lh
总计 8.0K
-rw-rw-r-- 1 anguoli anguoli 690 11月11日 22:12 random_output_2.txt
-rw-rw-r-- 1 anguoli anguoli 677 11月11日 22:09 random_output.txt
$ tree -h .git/objects/
[ 160] .git/objects/
├── [ 60] 02
│ └── [ 556] 3dc71e0501e56c41bc7a2b6236f167c7094360 <<<
├── [ 60] 06
│ └── [ 112] ffbb31a44b95173b16baa580941ce54984cb04
├── [ 60] c3
│ └── [ 567] c41712bdc2d4c2de08aee19d8f3e319fb82e83 <<<
├── [ 60] fd
│ └── [ 89] 040d11c4b1b4af03ac409dbefbf6fb94c50b1c
├── [ 40] info
└── [ 40] pack
7 directories, 4 files
$ git cat-file -s 023dc71e
677
通过手动gc达成repack压缩
$ git gc
枚举对象中: 4, 完成.
对象计数中: 100% (4/4), 完成.
使用 8 个线程进行压缩
压缩对象中: 100% (3/3), 完成.
写入对象中: 100% (4/4), 完成.
总共 4(差异 1),复用 4(差异 1),包复用 0(来自 0 个包)
$ tree -h .git/objects/
[ 80] .git/objects/
├── [ 80] info
│ ├── [1.1K] commit-graph
│ └── [ 54] packs
└── [ 100] pack
├── [1.2K] pack-6184e7108e795f0abc08d03baa4d2b49cd6d5d80.idx
├── [ 805] pack-6184e7108e795f0abc08d03baa4d2b49cd6d5d80.pack <<<
└── [ 68] pack-6184e7108e795f0abc08d03baa4d2b49cd6d5d80.rev
3 directories, 5 files
$ git verify-pack -v .git/objects/pack/pack-6184e7108e795f0abc08d03baa4d2b49cd6d5d80.pack
06ffbb31a44b95173b16baa580941ce54984cb04 commit 144 107 12
023dc71e0501e56c41bc7a2b6236f167c7094360 blob 677 549 119
c3c41712bdc2d4c2de08aee19d8f3e319fb82e83 blob 21 33 668 1 023dc71e0501e56c41bc7a2b6236f167c7094360
fd040d11c4b1b4af03ac409dbefbf6fb94c50b1c tree 92 84 701
非 delta:3 个对象
链长 = 1: 1 对象
.git/objects/pack/pack-6184e7108e795f0abc08d03baa4d2b49cd6d5d80.pack: ok
最后分别显示的是
<unpack-size> <size-in-packfile> <offset-in-packfile>
Git周边生态及开发
代码托管
- https://github.com
- https://about.gitlab.com/
- https://gitee.com/ 用了很多前者的基建
- https://gogs.io/ CVE修得少,不推荐
- https://about.gitea.com/ MIT,前者的fork
- https://forgejo.org/ GPL,前者的fork
- https://sourcehut.org/ Old School
- https://git.zx2c4.com/cgit/about/ 只读网页
- https://github.com/sitaramc/gitolite 无界面
- https://www.gerritcodereview.com/ 补丁审阅
其他链接:
- https://en.wikipedia.org/wiki/Comparison_of_source-code-hosting_facilities
- https://en.wikipedia.org/wiki/Forge_(software)#Examples
- https://onedev.io/ Java,v2ex有创始人
- https://pagure.io/ Fedora家的
- https://gerrit.googlesource.com/gitiles Gerrit搭档
- https://radicle.xyz/ Web3分布式
- https://forge.lindenii.org/
- https://github.com/yuki-kimoto/gitprep
- https://github.com/gitblit-org/gitblit
- https://gitlist.org/ PHP只读
- https://github.com/PGYER/codefever 蒲公英家的
- https://github.com/oxalorg/stagit 静态页面
当从GitHub拉仓库的时候,如果选择SSH则地址如下:
>>> [email protected]:user/repo.git
约等于SSH中git用户在操作github.com这个Host的user/repo.git路径下的仓库。
对个人来说最简单的情况不需要考虑权限,就是直接用SSH地址。
鉴权 & 认证
// 服务端与本地如何交互
// SSH认证
// Hooks鉴权
ChangeId
开源项目
- https://github.com/libgit2/libgit2 Pure C
- https://github.com/go-git/go-git Pure Go
- https://github.com/GitoxideLabs/gitoxide Pure Rust
- https://github.com/FredrikNoren/ungit Pure JavaScript
- https://gitlab.com/gitlab-org/gitaly/ Git RPC
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_enterwlr_seat_keyboard_enterwlr_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_enterwlr_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 相同,两者位于同一分支中。
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
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