You are right - it is something we did intentionally, but I would like to learn more from your use case - what is the reason to prefer isolation of changes?
Is it the case that you wish to have multiple agents working on the same task and then picking the best implementation? Or do you have a reason to prefer multiple tasks to be implemented in complete isolation from one another?
Hey, I just wanted to say I do like the product you guys made, really love it. ideally I want to be able to have multiple agent sessions running, and it feels to me odd to have different sessions run into each other. ideally, i could have an agent running per stack of branches. additionally, sometimes i'd also like to edit some code while having claude run in the background.
(co-founder of gb here)
I am really sorry for the frustration - the app should do better and we will do better. In the past few months we have been putting a very deliberate effort to eliminate all conditions from which such poor experience can come about.
The work is not complete but we have stability and correctness as a primary goal, and something that is a requirement for us to declare a v1.0.
The app has a built-in mechanism for going back in time (an operations log) which can be used for undoing situations that should not arise in the first place. It can be accessed via the app (there's a history tab) as well as via the CLI https://docs.gitbutler.com/commands/but-oplog
NB - the CLI version of GitButler is not yet at feature parity with the graphical version of the app yet
We chose not to use separate git worktrees under the hood for this functionality. Let me try to break down why, maybe there's an opportunity for me to learn more here.
In my head I separate between use cases of 1) "different tasks" and 2) "best of n, same task".
The app that we built already had the ability to separate changes into branches while in the worktree (on disk) it renders the integration of the branches. Our canonical use case back in the days was "A developer works on a feature branch and wishes to commit & publish a bugfix from separate branch". When we learned that people were using this for running multiple parallel agents we added some additional tooling for it.
So in practice what happens when you have multiple agents coding in parallel with GitButler is that the system captures information after an agent completes an edit (via the agent hooks) and uses that to 1) stage the particular edit to a branch dedicated to the agent and 2) perform a commit into that branch (GB can have multiple staging areas, one per applied branch).
The system will not allow multiple agents to edit the same file at the same time (via a locking mechanism in the pre-edit hook), but agents do see each others changes.
In the context of the "different tasks for different agents" use case, we have found that them seeing edits by others to have a positive effect on the outcomes. The first one that comes to mind is - no merge conflicts. But beyond merge conflicts, we have found that there is a lower likelihood of reaching a state where code diverges semantically.
In my own usage, I have found it helpful when I am hands on programming on something and wish to have an agent do some auxiliary task, for us to share a workspace (so that I can nudge it one way or another).
Is there something I am missing here? Of course for best-of-n of the same task this doesn't exactly make sense, but with regards to different tasks, what are some additional reasons to require full isolation? (as different worktrees would provide)
It has happened to me in the past to wonder why certain files/folders are ignored by git, only to realise that I had a global git ignore for the particular pattern.
Not sure l’d recommend this as a good default, but perhaps others have better memory than I do.
My gitignore is just a pile of things I _always_ want to ignore:
# OS
.DS_Store
# Editors
.vscode-server/
.idea/
# Javascript
.npm/
node_modules/
...more stuff per language
I find it really nice to just keep all that in one place and not have to configure it per project. There's nothing too broad or situational in there that might get in the way - those kinds of things can go into the project specific .gitignores.
There's also `git status --ignored` to check if you're missing anything.
My reason for having each and every common ignore in each individual repo is that on the off chance that someone else wants to contribute to any of my projects, those files are already ignored for those people too.
And also, sometimes I work from different machines and I don’t really want to have yet another dotfile to sync between all my current and future machines.
(Yes, I know dotfile managers exist, but I literally only care about syncing my zsh config files and one or two other dotfiles mainly so I do that with my own little shell script and basically don’t care about syncing other dotfiles.)
Generally agree with you but I'm not going to clutter my project's .gitignore with stuff that's in the responsibility of the user to keep in their own ignore list like .DS_Store.
E.g. Each js project gets a /.npm /node_modules, each py proj a .pyc etc...
Editor is generally one per project which checked in config (.vscode) and if you want to use vim, have your own rules for ~ which you likely have anyway.
Also, both are not exclusive: .env can be checked in and in .gitignore
I just mean, it’s intentionally not a fancy setup with all kinds of things.
Just the most essential stuff and some symlinks. For the few dotfiles I really care about.
In an ideal world, I wouldn’t need any dotfiles at all. And my home directories would only contain files that I myself put there. Like my photos, my music, and code that I write. Those kinds of things.
That’s the main reason I don’t like managing all of my dotfiles. Because I don’t really want them in the first place. The only thing I want less in my home dirs but which I also have to live with is all of the other garbage that programs I use put there on their own. Like caches and different weird files that the programs decided should be there.
It makes sense to have global defaults specific to your machine if and only if your workflow (editor, personal tooling) creates files that you don't want as a part of any project. Things like vim or emacs temp files, macOS's DS Store files, etc. This doesn't apply if your team uses standardized editor configs.
Tangentially you can set vims temp/swap files to something like ~/.vim/tmp/ so they don’t clutter repo directories. At least that’s my pref for this annoyance/clutter
I have to put a complete one in every project. I work with a lot of junior devs. On many occasions they're committing too much - cause they a) don't have good global config and b) are not selective about git add. A teaching opportunity but annoying to clean up.
This option in general seems odd to me. Wouldn't all contributors to the project need to coordinate their exclusions manually this way? Am I missing something?
You would normally use both this and a project-specific gitignore.
The project-specific file is for stuff that should be shared amongst all users of a particular project. So for a Node project you might have `node_modules` in there, for a Python project you might have `.venv` and `*.pyc`. If your project uses env files, you might have a line like `.env` in there for that.
Meanwhile, the global gitignore is for stuff that's specific to your system. If you're on MacOS, you might have an entire for `.DS_Store`, or if you use Vim a lot you might have an entry for `*~` files (i.e. vim backup files). This is stuff that's specific to you.
Git can then combine the project-specific ignores (located in the project respiratory) and your user-specific ignores (located in your global gitignore file), and ignores anything that matches either case.
It's useful for bespoke developer settings. I like to keep a scratch folder in virtually every repo I work on, so I have `/tomscratch` in my global excludes file. Adding this to .gitignore in every repo I work on would just be noise for everyone else.
I've been putting a .gitignore file containing a single * inside my scratch directories. But ~/.gitignore sounds like a good idea for local things that I do in every repository.
I disagree. If it's minimalistic and focused on your commonalities across projects in a computer, such as VScode stuff which shouldn't be committed to a general repo, I think it's the right and unobtrusive choice.
I agree with you. In fact, I think it's better to free up "configuration" memory, and use it to store "best practice" or "process" in general. I find that convention over configuration works out better on a long run, and when switching between projects - either the developer themselves, or when parts of the team leave and others join.
Interesting take. Settings are typically something I setup once and move on. A GUI is nice (I'm sure there will be one), but not required in a v1. One of Ghostty's stated goals is sane defaults that mostly work out the box. I just checked my settings file and I changed 3 things. I think they did a pretty good job with defaults.
Finally, somewhere else in this thread someone linked a web tool that will generate the settings if you absolutely can't look up the couple you need to change.
Aren't plaintext configuration files standard? It's how I configure most applications (and how I like it, because it makes the configuration shareable).
As a DSL, it's key-value pairs. It doesn't get much simpler than that.
It's a desktop/GUI application. It's definitely not a standard for those, no.
I share the OPs frustation that I don't even know what I can configure and have to search web to be able to change something. And while the documentation is nice, it's not exactly great for this.
Also, having an explorable and searchable UI doesn't mean it's not saved in the same shareable and readable file.
The ultimate end format for the config stored on disc is completely irrelevant to how the config is created. Simple doesn’t mean easy to experiment with, simple to understand, fast to write and so on.
No one is confused as to how the ghostty config works. Key value pairs. Sure. A window full of GUI controls and system color pickers can create that config file, or I can manually by hand. I’d prefer the former.
kitty has a nicely formatted config which lists all settings along with their default values and a very detailed description of each one. It's pretty much the same as having a GUI. Maybe Ghostty can adopt this?
Same here. I was curious, so I installed it. First thing I tried to do was to change the text color, and saw there was no settings UI. This is a bit baffling to be honest.
Uninstalled it right away, I'll keep using the default MacOS Terminal app.
We are a small team of 9 people who love Git, building new functionality on top of the Git format (branching, code review & more). Our own @schacon has published the Pro Git book as well as co-founded GitHub.
Currently looking for Senior Software Engineers with Rust, TypeScript (frontend) or Ruby skills to join us onsite in Berlin. Job ads: https://gitbutler.homerun.co
We have been using Tauri for about a year (a desktop app) and I am very happy with our choice. What I initially found attractive was the smaller binary sizes. Over time I have come to really appreciate their stance on security and implementation of the Isolation pattern[0].
When we were deciding whether we want to build our business[1] around Tauri, the final argument that helped me decide was the video manifesto[2] on their site as I felt that we were aligned on values. Having interacted with the community over this one year I have had a very positive experience, therefore Tauri definitely gets my recommendation.
This limitation stems from the fact that GB introduces an additional dimension of versioning on top of Git. One way of thinking of what it does with virtual branches is like "multiplexing" multiple branches onto the same working directory. On the way out they get "demuxed" into plain git trees.
With that said, the tool is very cautious not to mess with any existing branches. This is the very reason it operates on a separate integration branch.
Switching between the "special/integration" branch and any other branch is also not an issue.
Maybe the problem is that the docs aren't selling me anything that I don't currently achieve by (manually) rebasing? So I'm left thinking I'd rather stick with my current workflow (or maybe motivated to alias it up a bit more) than adopt this limitation.
I think you are right that our documentation does not sufficiently communicate what the application does especially in various corner cases.
For my own sake, allow me to articulate the core value proposition once more. GitButler's virtual branches permit two novel use cases:
- A developer can lazily assign diffs/changes to belong to separate logical branches while maintaining their content within the same working dir. Those logical branches can be converted to plain git trees at any time. The canonical use case here is doing a bugfix while working on an unrelated feature - with the proposed workflow one can separate those contributions into discrete PRs while still having the content of both within the working dir.
- A developer can apply and unapply the content of remote branches to their working directory for the purpose of testing & review. This is distinct from rebasing and merging because it does not introduce merging or rebasing into the branch that the developer was originally working on.
In any case, we will work on communicating and documenting the tool better.
This convinced me that it's maybe worth trying. Given the number of tools out there and how easy git feels for me right now (pretty easy), that's a highish bar
Well, one thing we can do is "merge" together multiple non-conflicting branches in your working directory without creating (or, I suppose, having to undo) merge artifacts.
Like if you had three branches and you wanted to see how they will work when they're all merged, and you find an issue in one and want to fix it, GB makes this really simple. You just apply them all and then fix something and make sure it's on the right branch and commit there (and push again if you want).
With Git, you would have to actually do either two merges or a three-head merge, see how it goes, undo the merge, switch to the target branch, commit a fix there, re-merge them all to see if that worked, etc. In Git there isn't a _real_ great way to combine trees without modifying history. We make those things a little more orthogonal.
Let me first of all be clear that I believe you have good reasons, and reiterate that I think it's great and want the features, I'm just trying to understand why it doesn't/can't work with my current rebasing workflow.
Take my example before, X-->Y-->Z(HEAD) where each of X,Y,Z here are (at least potentially) multi-commit branches, not just commits; based on top of each other.
If I need to fix something in the 'merged' (I said 'amalgamated' previously exactly to avoid that, but we can stick with scarequotes if you like ;)) result, and it belongs say with Y, then I will either:
git fixup <some-commit-between-X-and-Y>
# fixup = my alias for:
"!f(){ target=\"$(test -n \"$1\" && git rev-parse \"$1\" || git fzsha rev-parse)\"; git commit --fixup=\"$target\" ${@:2} && EDITOR=true git rebase -i --autostash --autosquash \"$target^\"; }; f"
# basically commit -m'!fixup <target-commit>', and then rebase target-commit^ to apply it
or if it belongs as a standalone commit on that branch:
git commit -m 'whatever'
git rebase -i Y # and move it to the top so the Z stuff follows it
and then in either case:
git branch -f Y <new-location>
# if I'm doing this repeatedly, <new-location> is often HEAD~N, for however many N commits on Z.
For this feature at least, it seems like GB could be implemented as automation/abstraction of that workflow, which (clearly) is perfectly penetrable to git.
I've been working like this for more than a decade, so even if GB's better & easier it's a tough sell to partially break interoperability; to need to use GB GUI to manage the situation rather than have it as a choice.
I think there may be a misunderstanding here. While the tool does something unorthodox locally, the output that it generates is plan Git trees that do represent a consistent snapshot.
It is the process of arriving at those snapshots (locally) that we feel we can make more ergonomic.
Disclaimer: I am a Co-founder
> While the tool does something unorthodox locally, the output that it generates is plan Git trees that do represent a consistent snapshot.
Yes, they're snapshots of something, but they're not snapshots of states that you were ever in when developing or testing.
If you develop PRs A and B together then you have no idea if there's a hidden dependency between them, because you only ever tested (A+B). This is risky but manageable if they are actually related changes. Things get worse with the suggested workflow of "work on A, find bug, create B and submit bugfix, then go back to A".
And worse, you don't even have a clue which point in A matches up to which point in B. The history that you do generate ends up being effectively worthless.
Saying that you generate snapshots is like saying that RAM is just a linear array. Sure, you can read it, but the meaning is completely lost without context.
Is it the case that you wish to have multiple agents working on the same task and then picking the best implementation? Or do you have a reason to prefer multiple tasks to be implemented in complete isolation from one another?