gitbutler icon indicating copy to clipboard operation
gitbutler copied to clipboard

Git worktrees and bare repositories support

Open Byron opened this issue 2 months ago • 11 comments

This issue is for gaining a better understanding on how worktrees could be integrated into the application. Bare repositories then are just a special case where there is no worktree at all.

Since every worktree has an associated HEAD reference, the main repository is able to see the committed state of all worktrees.

The opened repository itself may have its own worktree, or it may be bare.

It's trivial to obtain a repository at one of the worktrees to enable status checks and checkouts of actual files.

Problems and Solutions

The Project abstraction assumes a worktree path

While the current usage and code expects it to be a directory, it's basically always used to instantiate a Repository instance. And that will work whether the path is pointing to a worktree or to a .git repository.

Thus, it's just on us to store the path to the .git directory instead, and up to code to be aware that there may be no worktree. This is relevant mostly for calls to gix status and when checking out a changed workspace.

Unassigned Files need a worktree to acquire, and file-watching is needed per worktree

The unassigned files component would probably be best if it would be able to display all worktrees at once. The file watcher needs to watch all workspaces, and would have to make clear where (i.e. which worktree) changes are coming from.

We have opened a bare repository

This still means we have access to the whole commit-graph, but can't watch files on the main worktree. We can still show, however, changed files in other worktrees.

Applying a branch to such a workspace would not require us to merge it - there is nothing to checkout the result to. This would allow the 'bare + worktrees' workflow in a conductor-like fashion where one is in control by looking at the prime repository.

How to make commits from 'prime'?

Uncommitted files would now be associated with a worktree, which in turn is associated with a particular branch. For commits to work, these should be committed to the worktree branch (and only there). The HEAD of that worktree must then be updated. Further updates have to be performed to update the 'prime' worktree if there is one, which is the first time it gets to observe these changes.

This is useful if multiple agents were to work on disjoint tasks.

There are (too) many unrelated worktrees

We could choose only show changes in worktrees that are discoverable by the workspace. This means we check which refs are reachable through the current workspace tip, and register interest only in these respective workspaces. This only works as long as workspaces merge cleanly, or we are in single-branch mode.

In any case, it seems valuable to discover workspaces associated with a reference to offer functionality from the UI.

How to deal with multiple conflicting worktrees

In agentic workflows, we might want to have multiple attempts to do the same thing. Reviewing these will need them to be 'solo'.

For viewing purposes only, where no worktree is needed, the 'prime' repo could be bare, which allows all agent branches to be in the same workspace despite being conflicting.

If the user wants to review individual worktrees from their 'home', which is the 'prime' worktree, then they can't easily maintain all agents in the same workspace if they conflict.

However… this is where 'ghost stacks' come into play, which is stacks that aren't merged into the workspace commit, but are mentioned in the workspace metadata. That way they are part of the graph, and could be made visible in the UI. From there, the UI could offer a 'solo' mode which merely checks out specific branches. The workspace will still be observed, but the worktree is only the one of that stack.

Working on a worktree like there is nothing else (like it's 'solo')

It should always be possible to open a new Window of GitButler on a worktree directly, while looking at it from the 'prime' repository. That instance would still know that this is not the prime repository, and by default we could just let it act like there is nothing else.

This would mean opening a 'prime' repository shows everything and offers the multi-worktree mode, while opening a worktree directly puts it to single-worktree mode.

This is probably mostly relevant to the UI.

Last but not least, this means we'd have to be able to be able to turn any ref into a workspace, or be able to change the HEAD of the worktree to point to the newly created workspace-specific gitbutler/<workspace-name>. The latter is probably what's easiest to do for now.

A workspace independent worktree-listing?

The workspace view only allows interacting with workspaces that are reachable from it. However, I think there should be a simple view that shows workspaces front and center, so it's clear how many they are, where they are, and how to delete them.

I'd think creating them could also be added here for completeness, even though doing this from the workspace view to add a means of isolation to a ref should be preferred.

Worktree creation

Worktrees can be created implicitly by agents in the codegen view, but would also be shown in the workspace view. There they can be created explicitly on any ref name, which the newly created worktree would checkout. This should work where ever the ref name is displayed, so at least in the workspace view and in the branch view.

It's notable that most users won't need worktrees as a quick solution to something as they are inconvenient to use with tooling - they feel like a fresh clone and IDEs need it added as project, and a lot of extra data is thrown into it as part of target and node_modules for instance. For that reason it might be worth considering to make them easy to use as part of codegen with some sort of 'isolation' feature.

The worktree has advanced its HEAD ref

This means that the tip of the worktree, the ref its HEAD points to, has advanced to another commit that isn't reachable by the workspace commit.

We can integrate that new state by re-merging it into the workspace commit if it's the supposed tip of the stack, or by rebasing the descendant segments on top of it (which also leads to a remerge of the workspace commit).

Changing the history of the worktree

The HEAD of the worktree can be seen as a source of mutation which in theory should freeze all the history that goes into it. If we change the history by editing commits, this may now affect more than our main worktree that the HEAD of the 'prime' repository points to, but also other worktrees whose HEAD references are affected.

This just means that we have to update multiple worktrees with functions like safe_checkout.

As an alternative, once could discourage changing the history of branches that are checked out in a worktree, maybe by 'freezing' it in the UI. However, that really seems unnecessary given that we can handle additional worktrees like the main one.

Related issues

  • #9661
    • Workspaces could have their own worktree, or no worktree
    • The name of the workspace reference could freely be chosen by the user
  • #4254
    • suggests a bare repository as 'prime' repo, with multiple worktrees, one for each software release to support
    • Here each worktree could act like its own repository if opened directly, or one could see all worktrees if opened on 'prime'
  • #7458
    • Watching should deal with Git repository changes, but also not fail if there is no worktree.
    • It should be easy to watch multiple worktrees, and follow and unfollow worktrees dynamically

Byron avatar Oct 16 '25 12:10 Byron

Personally, I think it would be great if GitButler could be opened in the same mode that offers a workspace with one more more virtual branch lanes, but have this all work inside a directory that is a git worktree rather than the original clone (when I tried this, it errored out - and to be clear, the original clone was not bare). I think this is asking for your last heading ("Working on a worktree like there is nothing else"), but perhaps can you confirm if that's something else.

rmacklin avatar Oct 16 '25 14:10 rmacklin

Thanks for sharing, and yes, I believe the heading "Working on a worktree like there is nothing else" covers it. To make that clearer, I added another paragraph:

[..] this means we'd have to be able to be able to turn any ref into a workspace, or be able to change the HEAD of the worktree to point to the newly created workspace-specific gitbutler/<workspace-name>. The latter is probably what's easiest to do for now.

Byron avatar Oct 17 '25 02:10 Byron

I just discovered (but have not yet tried) GitButler. I use Git worktrees extensively.

I was going to open my project in GitButler (0.17.3) from one of my worktrees--assuming it would be isolated to that worktree like any other--but GitButler insisted on being opened from the main project dir. Does it want to "take over" all my worktrees, or would it interfere with them?

I did not proceed to open my project with GitButler, in part because of this.

bmcdonnell-fb avatar Nov 01 '25 22:11 bmcdonnell-fb

Right now it really wants to checkout its gitbutler/workspace branch, which makes it incompatible to linked worktrees as these have their own dedicated branch checked out.

The goal of this tracking issue is to paint a path towards playing nicely with standard Git.

Byron avatar Nov 02 '25 07:11 Byron

@Byron thanks for the info. But I don't see how this answers my question.

I gather GitButler wants to check out its own actual branches in the main worktree (the one with the .git subdir, not .git files)? Beyond that, "does it want to 'take over' all my worktrees, or would it interfere with them?" Could you clarify?

bmcdonnell-fb avatar Nov 02 '25 21:11 bmcdonnell-fb

You are welcome, and let me try again.

It doesn't want to own anything, nor will it try to take control of worktrees. The reason it may appear a bit overbearing is its desire to checkout gitbutler/workspace on newly added repositories, but work is on the way to not do that anymore.

I hope this helps.

Byron avatar Nov 03 '25 08:11 Byron

So how would virtual branches work when GitButler is opened in a worktree directory, without checking out gitbutler/workspace?

rmacklin avatar Nov 03 '25 16:11 rmacklin

It would have to work with the currently checked out branch as as workspace tip, i.e. the branch that points to the workspace commit that merges the other branches into it.

And even though the new 'world view' is able to handle that, a whole lot of tests and probably fixes will have to be written for it. The only way this will be working is once none of the old code is still running, i.e. all code is aware of the new data structure.

Byron avatar Nov 03 '25 18:11 Byron

So how would virtual branches work when GitButler is opened in a worktree directory, without checking out gitbutler/workspace?

IMO, from a user perspective, it should operate the same in a worktree dir as it does in the main one. i.e. Let GitButler "own" that worktree (checking out gitbutler/workspace), unless/until the user "pauses" GitButler by checking out some other branch/commit.

(Mind you, I've just read a bit about GitButler, but not tried it yet. I'm very interested, though, pending satisfactory resolution to #10936, and maybe this issue, too. So I might be missing/misunderstanding something.)

bmcdonnell-fb avatar Nov 03 '25 19:11 bmcdonnell-fb

An alternative would be to let each worktree have its own workspace of course, like gitbutler/worktree-name, which is good for a nice isolation. It also feels like it's a bit redundant though, but it should be an easier first step towards making worktrees work.

Byron avatar Nov 04 '25 04:11 Byron

An alternative would be to let each worktree have its own workspace

I think that's the same thing I was suggesting in my preceding comment?

bmcdonnell-fb avatar Nov 05 '25 22:11 bmcdonnell-fb