[{"content":"git pull 默认会创建 merge commit，时间一长 main 上就长成毛线球。rebase 能让本地未推送的提交\u0026quot;重演\u0026quot;到最新分支顶端，产生线性历史。但只对未推送的本地提交 rebase——一旦提交被推到了共享分支，rebase 改写历史会让队友的本地仓库脱节。\n日常流程 1 2 3 4 5 6 7 8 9 10 11 # 切到 feature 分支 git checkout feature/login # 拉取 main 上的最新提交，重演到当前分支顶端 git fetch origin git rebase origin/main # 解决冲突后继续 git rebase --continue # 或放弃 git rebase --abort 常见工作流：feature/... 分支在合到 main 之前先 rebase 一次，main 历史会是干净的直线。\n交互式 rebase 整理提交 推送前常用 git rebase -i HEAD~n 把本地散落的 commit 合并、拆分、改写：\n1 2 3 4 pick a1b2c3 修复登录页样式 squash d4e5f6 同上的小幅调整 reword f7g8h9 WIP: 加 token 检查 pick i0j1k2 完成 token 校验 pick 保留，squash 合并到上一个，reword 改写 commit message，drop 丢弃。\n千万别 rebase 公开提交 1 2 3 # 反例：team 里三个人都在 dev 分支上推过 git checkout dev git rebase main # 你重写了 dev 历史，其他人 pull 后会大量冲突 替代方案：把要清理的提交挪到一个临时分支，由原作者 rebase 后用 git push --force-with-lease 推一次，其他人 rebase 一次再说——但这只在团队约定一致的情况下可行。\n几个救命命令 git rebase --abort：放弃当前 rebase，回到 rebase 之前 git rebase --continue：解决完冲突后继续 git reflog：rebase 搞砸了可以在 reflog 里找到旧 commit hash，git reset --hard \u0026lt;hash\u0026gt; 救回来 git push --force-with-lease：比 --force 安全，远程有你不知道的提交时会拒绝覆盖 总结 本地提交随便 rebase，历史干净 推送过的提交不要 rebase，除非团队约定且用 --force-with-lease 出问题先 reflog，几乎都能找回 ","date":"2026-08-13T00:00:00Z","permalink":"/p/git-rebase-in-practice/","title":"Git rebase 实战：避免合代码时的\"merge 地狱\""},{"content":"Hugo 0.153.0 弃用了 LibSass，0.153.2 之后 extended 二进制甚至不再带 LibSass——SCSS 编译成了\u0026quot;你需要自己装 transpiler\u0026quot;。\n现象 1 TOCCSS: failed to transform \u0026#34;/scss/style.scss\u0026#34; ... you need the extended version 或者（更精确的报错）：\n1 failed to transpile SCSS: sass not found 修法 两步，缺一不可。\n1. 装 Dart Sass 本地：\n1 npm install -g sass-embedded 注意：用 sass-embedded，不要用 sass。sass 包是 JS 包装脚本，Hugo exec 二进制时拿到的不是真正的 CLI、协议握手就报 got unexpected EOF。sass-embedded 自带 Dart 运行时和原生 snapshot。\nCI（GitHub Actions）：\n1 2 3 4 5 6 7 - name: Install Dart Sass env: DART_SASS_VERSION: \u0026#34;1.83.0\u0026#34; run: | curl -sLJO \u0026#34;https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz\u0026#34; tar -C \u0026#34;$HOME/.local\u0026#34; -xf \u0026#34;dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz\u0026#34; echo \u0026#34;$HOME/.local/dart-sass\u0026#34; \u0026gt;\u0026gt; \u0026#34;$GITHUB_PATH\u0026#34; Dart Sass 的版本要 ≥ 1.79，否则 silenceDeprecations 里某些 ID 会被忽略。\n2. 覆盖主题的 Head style 模板 在 dev/layouts/_partials/head/style.html 里写：\n1 2 3 4 5 6 7 {{- $opts := dict \u0026#34;transpiler\u0026#34; \u0026#34;dartsass\u0026#34; \u0026#34;targetPath\u0026#34; \u0026#34;css/style.css\u0026#34; \u0026#34;silenceDeprecations\u0026#34; (slice \u0026#34;import\u0026#34; \u0026#34;global-builtin\u0026#34; \u0026#34;color-functions\u0026#34;) -}} {{- $style := resources.Get \u0026#34;scss/style.scss\u0026#34; | toCSS $opts | minify | fingerprint \u0026#34;sha256\u0026#34; -}} \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;{{ $style.RelPermalink }}\u0026#34;\u0026gt; 不写这个 override，Hugo 默认走 libsass，extended 和 standard 都会失败。\nGitHub Actions 启用 Pages 还有个坑 第一次部署时 actions/configure-pages@v5 必须传 enablement: true，否则 deploy job 报 success 但 pages/builds/latest 永远 404。\n1 2 3 4 - name: Configure Pages uses: actions/configure-pages@v5 with: enablement: true 验证 构建命令：\n1 hugo --gc --minify 本地构建零 ERROR 零 WARN、产物里有 public/css/style.min.\u0026lt;hash\u0026gt;.css 大小正常（几 KB 到几十 KB），就证明整条链路通了。\nCI 端用服务端 fetch 看站点是否能拉到样式、CSS 是否生效；如果拉到页面 HTML 但样式丢了，八成是 Dart Sass 没装对。\n","date":"2026-08-13T00:00:00Z","permalink":"/p/hugo-dart-sass-deploy/","title":"Hugo 静态站点用 Dart Sass 部署到 GitHub Pages"},{"content":"API 错误响应不一致是后端长期债。一种常见且实用的格式：\n1 2 3 4 5 6 7 8 9 10 11 { \u0026#34;error\u0026#34;: { \u0026#34;code\u0026#34;: \u0026#34;validation_failed\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;请求参数校验失败\u0026#34;, \u0026#34;details\u0026#34;: [ { \u0026#34;field\u0026#34;: \u0026#34;email\u0026#34;, \u0026#34;rule\u0026#34;: \u0026#34;required\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;邮箱不能为空\u0026#34; }, { \u0026#34;field\u0026#34;: \u0026#34;password\u0026#34;, \u0026#34;rule\u0026#34;: \u0026#34;min_length\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;密码至少 8 位\u0026#34; } ], \u0026#34;request_id\u0026#34;: \u0026#34;req_01abc\u0026#34; } } 关键点 code：机器读。稳定字符串，前端按它映射文案、做埋点、写测试 message：人读。可以国际化，但默认给开发者一句能定位问题的话 details：字段级错误。表单校验时必填，让前端能精确定位到 input request_id：日志关联。前端报错时把这个 ID 报给用户，客服 / 工程师一查日志就定位 HTTP 状态码 vs 业务码 不要把 HTTP 状态码当成业务码。两套分开：\nHTTP 状态码：网关、CDN、监控、SLA 用。403 表示\u0026quot;你这个请求没权限解析\u0026quot;，不是\u0026quot;你这个用户没权限\u0026quot; 业务码：code 字段，给前端用。哪怕 HTTP 是 200，业务上\u0026quot;余额不足\u0026quot;也算失败 实际工程里：\n场景 HTTP 业务 code 字段校验失败 400 validation_failed 未登录 401 unauthenticated 已登录但权限不够 403 forbidden 资源不存在 404 not_found 余额不足 200 insufficient_balance 服务挂了 500 internal_error + request_id Go 实战 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type APIError struct { Code string `json:\u0026#34;code\u0026#34;` Message string `json:\u0026#34;message\u0026#34;` Details []FieldError `json:\u0026#34;details,omitempty\u0026#34;` RequestID string `json:\u0026#34;request_id\u0026#34;` } type FieldError struct { Field string `json:\u0026#34;field\u0026#34;` Rule string `json:\u0026#34;rule\u0026#34;` Message string `json:\u0026#34;message\u0026#34;` } func writeError(w http.ResponseWriter, status int, e APIError) { if e.RequestID == \u0026#34;\u0026#34; { e.RequestID = uuid.NewString() } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(status) _ = json.NewEncoder(w).Encode(map[string]APIError{\u0026#34;error\u0026#34;: e}) } 使用：\n1 2 3 4 5 6 7 writeError(w, http.StatusBadRequest, APIError{ Code: \u0026#34;validation_failed\u0026#34;, Message: \u0026#34;请求参数校验失败\u0026#34;, Details: []FieldError{ {Field: \u0026#34;email\u0026#34;, Rule: \u0026#34;required\u0026#34;, Message: \u0026#34;邮箱不能为空\u0026#34;}, }, }) 中间件层把任何 panic 转成 internal_error 并带上 request_id：\n1 2 3 4 5 6 7 8 defer func() { if r := recover(); r != nil { log.Printf(\u0026#34;panic: %v, request_id=%s\u0026#34;, r, reqID) writeError(w, http.StatusInternalServerError, APIError{ Code: \u0026#34;internal_error\u0026#34;, Message: \u0026#34;服务暂不可用\u0026#34;, RequestID: reqID, }) } }() 几个原则 永远带 request_id，无论成功失败 不要在 message 里泄露内部信息（SQL、堆栈、绝对路径），那种信息打到日志 details 用数组而不是嵌套对象，前端用一个 for 循环就能渲染 文档化每个 code，OpenAPI 里给每个业务码写一句解释 总结 错误响应不是\u0026quot;返回个 500 完事\u0026quot;。格式统一后，前端能用一个 error.code switch 走完整套错误提示，运维能用 request_id 拉日志，最后是工程师排障时不用一个端点一个端点扒源码。\n","date":"2026-08-13T00:00:00Z","permalink":"/p/rest-api-error-design/","title":"REST API 错误响应设计：让前端少写一半判断"}]