Simply Patrick

版本控制的個人效率追尋:從 git-revise、git-branchstack 到 Jujutsu

AI 生成聲明: 本文初稿由 Claude AI Sonnect 4.5 模型 生成, 再由個人手動修改而成。

前言:Git 工作流的痛點

當我們使用 Git 進行開發時, 經常會遇到這些情況:

  • 想修改歷史 commit, 需要用 git rebase -i 進入互動模式
  • 管理多個相關的 feature branches, 需要手動追蹤分支間的依賴關係
  • 不小心改錯了想回到之前的狀態, 得靠 git reflog 救援
  • Commit 之後才發現忘記加檔案, 又要 git commit --amend

這些操作雖然強大, 但往往需要記住複雜的命令組合, 對新手不友善。更重要的是, 它們打斷了開發者的思考流程, 讓人把注意力從「解決問題」轉移到「操作工具」。

對於追求高效工作流的開發者來說, 這些摩擦累積起來是不可忽視的時間成本。於是, 一些人開始探索更符合自己工作習慣的版本控制方式…


第一步:git-revise — 簡化歷史編輯

誕生背景

git-revise 是為了解決 git rebase -i 的複雜性而生。對於經常需要修改 commit 歷史的開發者來說, 互動式 rebase 的流程實在太繁瑣了。git-revise 提供了更直接的方式來達成同樣的目標。

核心特點

# 傳統 git rebase 的方式
git rebase -i HEAD~3  # 進入編輯器, 標記 edit/reword/squash...

# git-revise 的方式
# 1. 先修改檔案
vim some_file.py
# 2. Stage 你的修改
git add some_file.py
# 3. 應用到目標 commit
git revise <commit-hash>  # 將 staged changes 應用到該 commit

優勢

  • 更安全: 自動保留原始狀態,隨時可以恢復
  • 更直覺: 直接操作目標 commit,不需要互動式編輯器
  • 避免衝突: 智能處理 commit 間的依賴關係

限制

  • 仍然基於 Git 的 branch 模型
  • 無法徹底解決多分支管理的複雜性

第二步:git-branchstack — Branchless 工作流的個人實踐

為什麼需要它?

許多開發者發現, 傳統的「一個 feature 一個 branch」模式在實際工作中很不順手:

  • 每次切換 branch 都要重新 build
  • 多個相關的 PR 需要手動維護依賴關係
  • 在不同 branch 間來回跳轉打斷思緒

git-branchstack 是一個個人效率工具, 讓你能用更自然的方式工作: 所有開發都在一個 branch 上進行, 只在需要提交 PR 時才生成對應的分支。

問題場景

你想在單一分支上開發多個功能, 每個功能對應一個 PR:

  • 功能 A: 重構 authentication
  • 功能 B: 新增 profile 頁面 (依賴 A)
  • 功能 C: 一個無關的 bugfix

傳統做法需要:

  1. 為每個功能建立獨立分支
  2. 在分支間來回切換
  3. 每次切換都重新 build, 浪費時間
  4. 手動管理分支間的依賴關係

git-branchstack 的創新解法

git-branchstack 讓你留在本地分支, 用 commit message 的標籤來標記主題:

# 在本地分支上正常開發
git commit -m "[auth-refactor] Update login logic"
git commit -m "[auth-refactor] Add JWT support"
git commit -m "[profile-page:auth-refactor] Add profile component"
git commit -m "[bugfix-123] Fix memory leak"

# 一行命令生成所有分支
git branchstack

這會自動生成:

* local-branch (HEAD)
|   - [bugfix-123] Fix memory leak
|   - [profile-page:auth-refactor] Add profile component  
|   - [auth-refactor] Add JWT support
|   - [auth-refactor] Update login logic
|
|-- bugfix-123 (branch)
|     - Fix memory leak
|
|-- profile-page (branch)
|     - Add profile component
|     (based on auth-refactor)
|
|-- auth-refactor (branch)
      - Add JWT support
      - Update login logic

核心概念

  1. 標籤語法: [topic][topic:dependency]

    • [auth-refactor] - 獨立主題
    • [profile:auth-refactor] - profile 依賴 auth-refactor
  2. Branchless 開發體驗:

    • 所有工作在一個分支上進行
    • 不需要切換分支, build 不會失效
    • 修改任何 commit 後, 重跑 git branchstack 更新所有分支
  3. 利用 git-revise:

    • 底層使用 git-revise, 不碰工作目錄
    • 速度快, 不影響正在進行的工作

實際工作流

# 1. 開發階段 - 在本地分支上自由開發
git commit -m "[feature-A] Implement part 1"
git commit -m "[feature-A] Implement part 2"
git commit -m "[feature-B:feature-A] Build on A"

# 2. 需要修改之前的 commit
git revise --interactive --edit  # 編輯 commit message 加標籤
git revise <commit-hash>          # 或直接修改內容

# 3. 生成/更新分支用於 PR
git branchstack

# 4. 推送到 GitHub
git push origin feature-A
git push origin feature-B

優勢與限制

優勢:

  • 真正的 branchless 體驗, 留在本地分支工作
  • 自動處理依賴關係
  • 支援 git rerere 記住衝突解決方案
  • 永不修改工作目錄, 不影響 build

限制:

  • 需要在 commit message 中手動標記主題
  • 依賴關係的改變需要修改 commit message
  • 作者本人已轉向 Jujutsu (根據 git-branchstack README, 作者表示 jj 提供了更乾淨和強大的解決方案)

革命性轉變:Jujutsu (jj) — 重新定義個人版本控制體驗

為什麼誕生?

即使有了 git-revise 和 git-branchstack 這些工具, Git 的基礎模型仍然存在根本性的摩擦。Jujutsu 的作者 Martin von Zweigbergk (同時也是 Google 內部版本控制系統的開發者) 決定: 與其不斷在 Git 上打補丁, 不如從頭設計一個真正符合現代工作流的版本控制系統。

這不是為了取代 Git 成為「標準」, 而是為追求極致效率的個人開發者提供更好的選擇。

核心理念轉變

Jujutsu 不是在 Git 上加工具, 而是重新思考版本控制該是什麼樣子:

  1. Change-based, 而非 Commit-based

    • 每個修改都是一個獨立的 “change”
    • Change 可以隨時修改, 不需要 amend 或 rebase
  2. Working Copy 即 Commit

    • 你的工作目錄就是一個 commit
    • 不需要 git add + git commit 兩階段
  3. 真正的非線性歷史

    • 不需要 branch 也能管理多條開發線
    • 衝突作為一等公民存在於歷史中

實際操作對比

場景一:修改歷史 Commit

Git 方式:

git rebase -i HEAD~3
# 標記要修改的 commit 為 'edit'
# 進行修改
git add .
git commit --amend
git rebase --continue

git-revise 方式:

# 進行修改
vim some_file.py
# Stage 修改
git add some_file.py
# 應用到目標 commit
git revise <commit-hash>  # 直接更新該 commit

Jujutsu 方式:

jj edit <change-id>  # 直接切換到該 change
# 進行修改
jj commit -m "updated"  # 自動更新該 change

場景二:管理多個功能開發

傳統 Git 方式:

git checkout -b feature-A
# 開發 feature-A
git commit -m "A"

git checkout -b feature-B
# 開發 feature-B
git commit -m "B"

# 要修改 A 時
git checkout feature-A
# 修改
git commit --amend
git checkout feature-B
git rebase feature-A  # 手動 rebase

git-branchstack 方式:

# 所有工作在本地分支進行
git commit -m "[feature-A] Implement A"
git commit -m "[feature-B:feature-A] Implement B"

# 生成分支
git branchstack

# 要修改 A 時
git add modified_files
git revise <commit-hash-of-A>  # 修改 A 的 commit

# 重新生成分支, 自動處理依賴
git branchstack

Jujutsu 方式:

# 不需要建立 branch
jj new -m "feature A"  # 創建新 change
# 開發 feature A

jj new -m "feature B"  # 基於當前創建新 change
# 開發 feature B

# 要修改 A 時
jj edit <change-A-id>
# 修改完自動處理依賴

Jujutsu 的核心特性

1. 自動追蹤所有歷史

jj op log  # 查看所有操作歷史
jj op undo  # 撤銷任何操作
jj op restore  # 恢復到任何時間點

不需要 git reflog, 所有操作都可追溯和回復。

2. 衝突處理的新思路

# Jujutsu 讓你可以 commit 帶有衝突的狀態
jj new  # 即使有衝突也能繼續開發
jj resolve  # 稍後解決衝突

3. 與 Git 無縫協作

# Jujutsu 可以操作 Git repo
jj git clone <url>
jj git push
jj git fetch

# 現有 Git repo 也能用 jj
cd my-git-repo
jj init --git-repo .

為什麼 Jujutsu 是個人效率工具的終極形態?

心智模型更簡單

  • Git: Branch → Stage → Commit → Push → Rebase → Merge
  • Jujutsu: Change → Edit → Evolve

對於個人開發者來說, 這意味著更少的認知負擔, 更多的時間專注在實際問題上。

更安全的實驗

  • 所有操作都可以輕鬆撤銷
  • 不需要擔心「搞壞」歷史
  • 鼓勵更頻繁的實驗和重構

更適合現代個人工作流

  • Code Review 驅動的開發
  • 頻繁的 Commit 修改
  • 多個 PR 同時進行
  • 不需要團隊統一工具 (可以單人使用 jj, 推送時轉為 Git)

實戰案例:用 Jujutsu 管理 Feature Stack

# 初始化
jj git clone https://github.com/user/repo.git
cd repo

# 創建第一個功能
jj new -m "Add user authentication"
# 開發...
jj describe -m "Add login form and validation"

# 基於此創建第二個功能
jj new -m "Add user profile page"
# 開發...

# 創建第三個功能
jj new -m "Add profile editing"
# 開發...

# 查看當前狀態
jj log

# 發現第一個功能需要修改
jj edit <auth-change-id>
# 修改...
jj describe -m "Updated authentication logic"

# 自動處理後續依賴, 不需要手動 rebase!

# 推送到 GitHub
jj git push --change <change-id>  # 為每個 change 推送對應分支

學習曲線

  1. 現有 Git 用戶: 可以繼續在 Git repo 中使用 jj, 逐步適應
  2. 新專案: 直接用 jj git init 開始, 享受完整體驗
  3. 團隊協作: 個人可以用 jj 提升效率, 推送時轉換為 Git, 不影響團隊其他成員

這是一個個人選擇, 不需要說服整個團隊改變工作流程。


結語:效率工具的個人化旅程

git-revisegit-branchstack, 再到 Jujutsu, 這是一段持續追求更高效率的旅程。每個工具都代表了開發者對「版本控制應該如何輔助我的工作」的不同思考。

這些工具的共同點是:

  • 它們都是個人效率工具, 不需要整個團隊採用
  • 它們都在挑戰 Git 的既有模式
  • 它們都專注於減少摩擦, 讓開發者更專注於解決問題

Jujutsu 的特別之處在於, 它不只是改善 Git 的某個面向, 而是重新思考了整個版本控制的心智模型。對於願意投資學習新工具來提升長期效率的開發者來說, 它可能是最佳選擇。

最重要的是: 這是一個個人選擇。你不需要等待團隊共識, 不需要說服所有人。如果 Git 的複雜性讓你感到疲憊, 如果你想要更流暢的開發體驗, 那就試試這些工具吧。

或許你會發現, 提升效率的關鍵不是更努力地使用現有工具, 而是找到更適合你的工具。


延伸資源

試試看,你可能再也回不去 Git 了! 🚀


版本控制的「柔術」🥋

AI 生成聲明: 本文初稿由 Gemini AI 2.5 Flash 模型 生成, 再由個人手動修改而成。

從 Git 修訂工具到 Jujutsu 的優雅進化

許多資深開發者都對 Git 的強大功能愛恨交織。它強大, 但某些操作(特別是修改歷史和管理一系列依賴的變更時)卻異常複雜, 像是 git rebase -i 這樣的指令既是利器, 也常是困擾的源頭。

我們將探討一種更優雅、更直覺的版本控制解決方案:Jujutsu (jj)。它並不是要取代 Git, 而是作為一個建立在 Git 基礎上的現代化前端, 帶來革命性的工作流程。

第一階段:Git 生態系統的修補與嘗試

Jujutsu 誕生之前, 許多工具試圖修補 Git 的「歷史修改」痛點。這些嘗試為 jj 的核心設計奠定了基礎。

痛點一:修訂歷史的麻煩 - git-revise 的出現

  • 問題: 在 Git 中修改一個比較舊的 commit, 你需要用 git rebase -i, 找到那一行, 將 pick 改成 edit, 修改後再 git commit --amend, 最後 git rebase --continue。過程繁瑣且容易出錯。
  • 解決方案的萌芽:git-revise 這樣的工具, 讓修訂一個舊 commit 變得更簡單, 通常只需一個指令, 就能自動處理中斷和繼續的步驟。它證明了「歷史修訂可以更簡單」的可能性。

痛點二:依賴變更的管理 - git-branchstack 的概念

  • 問題: 當你在一個功能上提交了多個依賴的 commits, 如果底層重構的 commit 需要修改, 後續所有 commits 都需要手動 rebase, 或者面對重複的衝突解決。
  • 解決方案的探索: git-branchstack 等工具, 嘗試建立一個「堆疊式分支」的概念, 讓開發者能更清楚地追蹤和操作一系列相互依賴的變更。這突顯了開發者對「自動化處理變基」的渴望。

第二階段:Jujutsu 的誕生與核心概念

Jujutsu 正是吸收了這些工具的經驗和願景, 將其融入一個全新的版本控制系統介面中。它重新定義了幾個核心概念:

核心概念一:工作區即提交 (Working Copy is a Commit)

  • 告別暫存區 (Staging Area):jj 中, 你的工作目錄狀態永遠是一個提交(commit)。你對檔案的修改會自動被視為對當前工作提交的修改。
    • 好處: 不再需要 git add, 也沒有 git stash 的概念。

核心概念二:不變的「變更 ID」 (Change ID)

  • Git 的問題: 在 Git 中, 你修改一個舊提交(例如使用 amendrebase)會產生一個全新的 Commit ID (SHA-1 hash)。
  • Jujutsu 的解決: jj 為每個邏輯變更(Change)分配一個不變的 Change ID。當你修訂一個 commit 時, 它的 Commit ID 會改變, 但 Change ID 保持不變。
    • 好處:jj 能夠在底層追蹤變更的演進, 即使歷史被重寫, 邏輯變更仍可追溯。

核心概念三:自動、安全且強大的變基 (Automatic Rebasing)

  • 終結 rebase 的恐懼: 這是 jj 的殺手鐧。當你修改歷史中的某個提交時, 所有後續依賴於它的提交會自動且安全地進行變基。
    • 例如: 你使用 jj edit <old-commit-id> 修改了一個底層提交, 你後續的 commits 會像「柔術」一般優雅地、自動地移到新修訂的提交之上。

第三階段:Jujutsu 帶來的工作流程變革

jj 的指令設計更直覺、更以「變更」為中心, 極大地簡化了日常操作。

jj 操作 傳統 Git 工作流 優勢與變革
jj undo 難以實現, 需要 git reflog 一鍵撤銷:所有操作(包括變基、修訂)都是可撤銷的, 提供了無損的工作安全網
jj squash git rebase -igit commit --amend 輕鬆合併: 快速將當前工作區的變更併入父提交。
jj split git rebase -i + 手動操作 輕鬆拆分: 在不修改原提交的前提下, 將其部分內容拆分為新提交。
jj log git log --graph 清晰日誌: 預設輸出更易讀的變更圖, 並顯示 Change ID。

結語:版本控制的未來已來

Jujutsu 將版本控制的複雜性抽象化, 提供了更直覺、更安全、更符合人腦思考邏輯的操作介面。它將我們從 git-revise 和 git-branchstack 試圖解決的局部痛點中解放出來, 提供了一個更全面、優雅的解決方案。

如果你厭倦了 Git 繁瑣的歷史修改流程, 渴望更流暢、更少出錯的開發體驗, 那麼是時候擁抱這門版本控制的「柔術」了。


建置 Jupyter Notebook 的 Rust 運作環境

最近重新安裝了 macOS Big Sur,想試試 Jupyter Notebook 的 Rust 環境,再加上這幾天放假比較有時間,就把安裝的過程記錄下來。

安裝 Miniconda

過去一直覺得 Anaconda 有點肥大,所以這次決定先從安裝比較精簡的 miniconda 開始:

$ brew install --cask miniconda

新增 conda-forge channel 以便安裝需要的套件如 jupyterlab:

$ conda config --add channels conda-forge
$ conda config --set channel_priority strict

然後需要初始化 conda 的 base 環境,一般都是跑 conda init,但它會加一段不是很通用的設定到我個人的 .zshrc,我不是很喜歡這種做法,所以就先只套用在目前的 shell 裡:

$ eval `conda shell.zsh hook`

創建新的 conda 環境

創建一個新的環境叫 rust-notebook:

(base) $ conda create -n rust-notebook python=3
(base) $ conda activate rust-notebook

安裝 JupyterLab

在新的環境指定安裝 jupyterlab 版本 2.2.9,因為最新的 3.0 似乎與最新的 jupyterlab-plotly 不相容:

(rust-notebook) $ conda install jupyterlab=2.2.9

然後安裝 jupyterlab-plotly:

(rust-notebook) $ jupyter labextension install jupyterlab-plotly

安裝 evcxr_jupyter

假設 Rust 已經安裝完成,直接用 Cargo 安裝 Rust 的 Jupyter kernel:

$ cargo install evcxr_jupyter
$ evcxr_jupyter --install

啟動 JupyterLab:

(rust-notebook) $ jupyter lab

如果可以看到下列 Rust 圖示應該就是成功了:

範例執行結果

試一下:

println!("Hello, Jupyter!");
Hello, Jupyter!

來畫個圖:

:dep plotters = { git = "https://github.com/38/plotters", default_features = false, features = ["evcxr", "line_series"] }
extern crate plotters;
use plotters::prelude::*;
use plotters::series::*;

let figure = evcxr_figure((640, 480), |root| {
    root.fill(&WHITE);
    let mut chart = ChartBuilder::on(&root)
        .caption("y=x^2", ("Arial", 50).into_font())
        .margin(5)
        .x_label_area_size(30)
        .y_label_area_size(30)
        .build_ranged(-1f32..1f32, -0.1f32..1f32)?;

    chart.configure_mesh().draw()?;

    chart.draw_series(LineSeries::new(
        (-50..=50).map(|x| x as f32 / 50.0).map(|x| (x, x * x)),
        &RED,
    )).unwrap()
        .label("y = x^2")
        .legend(|(x,y)| PathElement::new(vec![(x,y), (x + 20,y)], &RED));

    chart.configure_series_labels()
        .background_style(&WHITE.mix(0.8))
        .border_style(&BLACK)
        .draw()?;
    Ok(())
});
figure
y=x^2 0.0 0.2 0.4 0.6 0.8 -0.8 -0.6 -0.4 -0.2 0.0 0.2 0.4 0.6 0.8 1.0 y = x^2

參考


看 log 的好工具: klogg

工作上常需要看 log 解決問題,推薦一個好用的工具 klogg,它是一個開源、跨平台、速度飛快的 log 檢視器。

klogg 是 glogg 的分支,它有以下這些特色:

  • 開源、跨平台的 GUI 應用程式
  • 速度快,可以處理大型 log 檔案
  • 使用正規表示式進行搜尋
  • 支援 Windows, macOS and Linux 三大作業系統
  • 支援 Perl 相容的正規表示式
  • log 和搜尋結果都有語法高亮
  • 即時監控檔案變化
  • 為現代 CPU 進行了最佳化
  • 提供可攜式版本
  • 自動偵測文字編碼
  • 進階的文字高亮規則

Setup Fuchsia Rust Development on macOS

基本上照著官方的指引 Rust Editor Configuration,設置 Fuchsia OS 開發的 Rust 開發環境主要就兩個步驟:

  1. 產生 Cargo.toml
  2. 設定編輯器使用 Rust 的 Language Server (RLS)

產生 Cargo.toml

很簡單,就是用 fx gen-cargo //garnet/foo/path/to/target:label 產生對應的 Cargo.toml。例如 fx gen-cargo //garnet/bin/log_listener:bin 會產生 /garnet/bin/log_listener/Cargo.toml

設定 RLS

官方文檔提到要先把 Fuchsia 自帶的 Rust 連結到 rustup,並且設成預設的 toolchain:

$ rustup toolchain link fuchsia /<your Fuchsia root>/buildtools/<platform>/rust
$ rustup default fuchsia

我是有做第一步,但第二步我比較持保留態度,之後試過似乎不做也沒影響,我會建議跳過這一步。

Vim

跟 C++ 類似,只是 Language Server 我們必須從 clangd 換成 RLS,所以可以在 Vim 裡用 :CocInstall coc-rls 來安裝 coc-rls extension,如果你系統之前就有裝過 RLS 的話,在 Vim 裡打開對應的目錄就會有 code completion 的功能了。

如果你的系統沒有 RLS 或你覺得用 Fuchsia 自帶的 Rust toolchain 比較保險,可以用 :CocConfig 把下列設定加進去:

{
  "rust.target": "x86_64-fuchsia",
  "rust.target_dir": "/<your Fuchsia root>/out/cargo_target",
  "rust.unstable_features": true,
  "rust-client.rlsPath": "/<your Fuchsia root>/buildtools/<platform>/rust/bin/rls",
  "rust-client.disableRustup": true,

  // Some optional settings that may help:
  "rust.goto_def_racer_fallback": true,
  "rust-client.logToFile": true,
}

Visual Studio Code

VSCode 則是需要安裝 Rust (rls) 這個 plugin。同樣如果你的系統沒有 RLS 或你覺得用 Fuchsia 自帶的 Rust toolchain 比較保險,加入下列的設定到 VS Code 裡:

{
  "rust.target": "x86_64-fuchsia",
  "rust.target_dir": "/<your Fuchsia root>/out/cargo_target",
  "rust.unstable_features": true,
  "rust-client.rlsPath": "/<your Fuchsia root>/buildtools/<platform>/rust/bin/rls",
  "rust-client.disableRustup": true,

  // Some optional settings that may help:
  "rust.goto_def_racer_fallback": true,
  "rust-client.logToFile": true,
}