惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
The Last Watchdog
The Last Watchdog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
G
Google Developers Blog
博客园 - 叶小钗
雷峰网
雷峰网
人人都是产品经理
人人都是产品经理
博客园_首页
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 三生石上(FineUI控件)
Help Net Security
Help Net Security
Cloudbric
Cloudbric
AI
AI
N
News | PayPal Newsroom
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 【当耐特】
Forbes - Security
Forbes - Security
美团技术团队
Stack Overflow Blog
Stack Overflow Blog
SecWiki News
SecWiki News
H
Heimdal Security Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
S
Security @ Cisco Blogs
Google DeepMind News
Google DeepMind News
V
V2EX
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
S
Security Affairs
L
LangChain Blog
The Hacker News
The Hacker News
F
Full Disclosure
aimingoo的专栏
aimingoo的专栏
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
Webroot Blog
Webroot Blog
A
About on SuperTechFans
H
Hacker News: Front Page
Cyberwarzone
Cyberwarzone
WordPress大学
WordPress大学
L
LINUX DO - 热门话题
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Attack and Defense Labs
Attack and Defense Labs
M
MIT News - Artificial intelligence

Butler's Log

Agentic Version Control Benchmarks Grit: rewriting Git in Rust with agents Git Merge 2026 Agent-safe Git with GitButler We’ve raised $17M to build what comes after Git Announcing the GitButler CLI for Linux The Great CSS Expansion A couple of git nits Simplifying Git by Using GitButler Introducing the GitButler CLI GitButler 0.19 - "Commander Keen" But Head: Crafting a Custom Font MCP vs RAG: Two Very Different Ways to Gain Context Getting Started With GitButler Agents Using the GitButler MCP Server to Build Better AI-Driven Git Workflows Using GitButler With Multiple GitHub Accounts Advent of Code! Upcoming GitButler Events Use GitButler for your Gerrit workflow Integrating GitButler and GitHub Enterprise Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 3 Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 2 Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 1 Grid Happens: Because Flexbox Wasn’t Enough Using Cursor Hooks for automatic version control Deep Dive into the new Cursor Hooks A Responsive Item Counter with CSS only GitButler 0.16 - "Sweet Sixteen" GitButler's Claude Code tab GitButler's Annual Open Source Pledge Report Git Mini Summit 2025 Videos Automate Your AI Workflows with Claude Code Hooks Managing Multiple Claude Code Sessions Without Worktrees GitButler 0.15 - "Quirky Quinceañera" 20 years of Git. Still weird, still wonderful. GitButler's new patch based Code Review (Beta) Going down the rabbit hole of Git's new bundle-uri How to do patch-based review with git range-diff How Core Git Developers Configure Git Why is Git Autocorrect too fast for Formula One drivers? Stacked Branches with GitButler Git Merge 2024 Talks are Up GitButler 0.13 - "Lucky Baseball" Fearless Rebasing Git Merge 2024 Why GitHub Actually Won GitButler is joining the Open Source Pledge The New Era of Town Hall Chat The Future of Open Source GitButler is now Fair Source Git Merge 2024 GitButler 0.12 - "Stingy Baker" The Birth of THE MERGE GitButler for Windows Fixing up Git with Autosquash The Git Zeitgeist DevWorld Git Slides Git Tips and Tricks Git Tips 1: Oldies but Goodies Git Tips 2: New Stuff in Git Git Tips 3: Really Large Repositories FOSDEM Git Talk Opening Up GitButler Debugging Tauri in VS Code Advent of GitButler Code Signing Commits in Git, Explained Virtual Branches Alpha Our We Are Developers Adventure Building Virtual Branches DevDays in Vilnius The Future of Software and Open Source Introducing GitButler
Git Worktrees and GitButler
Scott Chacon · 2024-03-04 · via Butler's Log

Last week, I gave a talk at DevWorld with more "So You Think You Know Git" style tips. One of the things I covered in this new talk was git worktree and since I've heard it mentioned a lot lately, I thought it was time to do a little writeup explaining it.

The "worktree" tool in Git helps you work on multiple branches at the same time.

Since GitButler solves the same basic problem, I'll take a minute to explain what git worktree does for those who haven't heard of it, and then also to compare it to how GitButler solves the same core issue.

So, what is the core issue here? Basically, it's trying to do work on two topics in your code at the same time.

Let's take an example. Let's say you are working on a feature. Your boss interrupts you (🙄, bosses, right?) and says "we need this bug fixed". Or maybe you just notice the bug as you're working. Whatever it is, you need to switch contexts while in the middle of something.

Now you have a few options.

  • You can fix the bug and commit it into your feature branch and try to get them both deployed together.
  • You can stash everything, create a new branch, switch to it, fix the bug, commit and push it, then switch back to what you were working on.
  • You could locally clone the repository to a new directory and work on the other branch there, then push it back into the main one and then push from there back upstream. 🙄

Surely there's a better way!

This is the basic use case for git worktree. Worktrees allow you to have a separate working directory (and staging area) for each branch you're actively working on.

Let's say we're working on our project in the my-project directory and we decide that we want to work on a new branch called bugfix-123. We can run the git worktree add command and give it a directory to create and checkout that branch into.

❯ git worktree add -b bugfix-123 ../my-project-branches/bugfix-123
Preparing worktree (new branch 'bugfix-123')
HEAD is now at 5b67eb2 first commit

Now we have two directories that are linked. When we edit files in the my-project-branches/bugfix-123 directory, it's on the bugfix-123 branch.

Now, the cool thing is that both checkouts share the same core Git database. So if we commit in either of them, those commits on those branches are visible by all of them.

❯ pushd ../my-project-branches/bugfix-123/
~/my-project-branches/bugfix-123 ~/my-project

❯ echo 'new commit content' >> README.md; git commit -am 'new commit'
[bugfix-123 2ff5902] new commit
 1 file changed, 1 insertion(+)

❯ popd
~/my-project

❯ git log --oneline bugfix-123
2ff5902 (bugfix-123) new commit
5b67eb2 (HEAD -> main) first commit

This can work for lots of branches, and git worktree remembers where they all are for your project and what branches they're all currently on:

❯ git worktree list
~/my-project                              5b67eb2 [main]
~/my-project-branches/bugfix-123          2ff5902 [bugfix-123]
~/my-project-branches/feature-foobar-baz  b9b9280 [foobar]
~/my-project-branches/feature-new-widget  daf62e4 [new-widget]

You can remove ones you're not using anymore with git worktree remove or you can also just delete the directory and run git worktree prune to remove it from the list.

❯ rm -Rf ../my-project-branches/feature-foobar-baz

❯ git worktree list
~/my-project                              5b67eb2 [main]
~/my-project-branches/bugfix-123          2ff5902 [bugfix-123]
~/my-project-branches/feature-foobar-baz  5b67eb2 [foobar] prunable
~/my-project-branches/feature-new-widget  5b67eb2 [new-widget]

❯ git worktree prune

❯ git worktree list
~/my-project                              5b67eb2 [main]
~/my-project-branches/bugfix-123          2ff5902 [bugfix-123]
~/my-project-branches/feature-new-widget  5b67eb2 [new-widget]

Now, there are some downsides to this approach too.

  • If you're using an editor like VS Code, you need a new window for each directory/branch.
  • If you have any ignored files like build artifacts, you have to rebuild them for each worktree. This is less of an issue when you're just stashing or switching branches in one directory.
  • The worktrees are separate, so you can easily create merge conflicts between them without knowing.

However, in some cases, worktrees can be a nice way to give yourself a new place to work in a new context without messing up the context you're currently in.

So how is this different from GitButler's virtual branches? They also provide a way for you to work on multiple branches at the same time, right?

Well, the big difference is that virtual branches use a single working directory for all the branches.

Let's take a very simple example. Let's say we want to add translations of our README into Spanish and French and we want to open a Pull Request for each one to be reviewed by different people at possibly different times.

With worktrees, we could open a new worktree for each one, commit in each and push up.

First, we create the two new worktrees.

❯ git worktree add -b sp ../my-project-branches/spanish
Preparing worktree (new branch 'sp')
HEAD is now at 5b67eb2 first commit

❯ git worktree add -b fr ../my-project-branches/french
Preparing worktree (new branch 'fr')
HEAD is now at 5b67eb2 first commit

Now, we go into each directory, make the changes and commit them.

❯ cd ../my-project-branches/spanish

❯ echo 'Léeme' > README.es.md

❯ git add README.es.md; git commit -am 'spanish readme'
[sp 4d57bfb] spanish readme
 1 file changed, 1 insertion(+)
 create mode 100644 README.es.md

❯ cd ../french/

❯ echo 'Lis-moi' > README.fr.md

❯ git add README.fr.md; git commit -am 'french readme'
[fr 48df685] french readme
 1 file changed, 1 insertion(+)
 create mode 100644 README.fr.md

Now if we go back to our main directory, we can see both branches there and can create PRs from them.

❯ cd ../../my-project

❯ git log --oneline --graph --all
* 48df685 (fr) french readme
| * 4d57bfb (sp) spanish readme
|/
* 5b67eb2 (HEAD -> main) first commit

In GitButler, it's much simpler. You can simply edit both files, then drag them into separate branch lanes, commit and push them at the same time.

Let's say we just edit the files:

❯ echo 'Léeme' > README.es.md
❯ echo 'Lis-moi' > README.fr.md

Then we go to our GitButler UI and we see that an anonymous virtual branch has been automatically created, because it saw new changes. We can just drag one of the files into a new virtual branch lane, then commit and push both at the same time.

Notice how much less friction this is. You don't have to create new branches or switch directories. It's a much faster and smoother way to handle this type of change management.

How does this work, exactly?

In essence, we do a git diff to see everything that has been changed and then have our own mapping of which changes are owned by which branch. Then when you hit the "commit" button, we run a sort of git apply of those changes to what you started with.

It's sort of like what git add -p does, except that we can keep doing it over and over and keep things separate.

For instance, with the normal Git tooling, we could just add one of the READMEs here and commit and push that, but then doing the same for the second file would require us to reset and then add the other and then change the active branch and commit, etc. It's not simple.

So, there are advantages and disadvantages to both approaches, depending on what you want to do.

Since GitButler only has a single working directory, you cannot have two branches applied that have conflicting work. You can't have conflicts if you have one copy of each file.

This could be seen as an advantage or a limitation, but we find that it's quite nice. Essentially you're starting with the merged product and then extracting branches out of it.

For instance, lets say that both the french and the spanish branches are eventually merged. It doesn't matter what order they're merged in, we know they will not only merge cleanly, but we know that when they do both get merged, what we'll end up with is the state of the working directory when we started.

So, that's how worktrees and virtual branches approach the same basic problem in rather different ways. Hope this has been helpful!