Skip to content

Conversation

Byron
Copy link
Collaborator

@Byron Byron commented Sep 15, 2025

With stashing available, a single-branch aware version of branch apply and unapply is possible.

Follow-up of #10233.

Tasks

  • find changed files that overlap with those to checkout and handle them
  • implement and test workspace checkouts, also dealing with snapshots
    • with conflicts
    • without
    • test hunk-granularity when merging (i.e. stash/unstash)
  • see if the back-to-workspace button could just be a (safe) checkout
  • Use cv3 feature toggle
  • performance testing
  • test that checks out an empty tree!

Next PR?

  • put it into all the major places where we checkout (behind feature toggle)
  • cherry-pick index as well (but only conflicts)

Future Tasks

  • Permutations: starting point
    • test unborn
    • starting point without WS ref
    • starting point with WS ref but not WS-commit or metadata
    • starting point with WS ref and WS commit
  • Permutations: base position
    • base below
    • base above
  • but_workspace::checkout()
    • apply conflict
    • apply worktree snapshot conflict
    • unapply worktree snapshot conflict
  • Validate StackID handling in VB.toml adapter (assure it can keep unapplied branches correctly)
    • We need all the tests so at a later point we can possibly localise the stack-id and keep it with each branch instead.
  • without single-branch mode, it's possible to have a workspace with just one stack, or even no stacks at all.
  • don't apply the target branch!
  • try to fix but-graph issue

Shortcomings

  • Worktree change in a detached HEAD can't be stashed as there is no reference to associated the stash with.

Notes

General Rules

  • The workspace is a conflict-free zone
    • nothing that operates on the conflict must write conflicts into the index.
      This is as conflicts are currently hidden from view.
  • Symmetry
    • If apply is doing something, then unapply undoes exactly that, or in other words State + apply + unapply == State
  • There is no single-branch workspace when starting in single-branch mode
    • A workspace consists of at least two branches and a workspace commit
    • The workspace commit is optional if there is only one commit involved, i.e. when it's just a bunch of branches on top of a single commit
  • Workspace Commit ALWAYS for even for a single branch
    • The workspace backend can deal with anything, but commit() currently can't.
    • Have to add commit() and uncommit() as well to all apply-unapply tests so these can later be re-tested with different behaviour.
    • Implied by the previous rule

Follow-Ups

  • commit with auto-workspace-commit creation
    • At the same time, it needs uncommit() that is symmetric

Thus:

  • snapshots of worktree changes will be made to apply by forcing merge-conflicts to be... auto-resolved.
    This is a problem, but we can't have conflicts as the UI doesn't show them right now, nor does it allow interacting with them.

Unapply

  • Conflicting paths are passed added as extra commit at first, without additional special handling in apply just to be able to handle them.
    • This means assignments aren't taken care of in all cases (but we will see how all this interacts with stack-ids)

Research

Unapply: Assignments - with stashing

  • uncommitted but assigned changes should create a snapshot commit
  • when applying the same branch this snapshot is applied

However, the user should be able to interact with these.

Unapply: Assignments - with WIP commit

  • uncommitted but assigned changes should create a WIP commit
    • or just unassign these assignments and they are back in the unassigned changes of the workspace
  • MVP apply: do nothing with the WIP commit
  • final version: apply restores the assignments from the WIP commit (which then is tracked with metadata)

Unapply with worktree changes

  • worktree changes that don't re-apply cleanly

Possible Follow-Ups

  • Find a way to display and handle conflicts in the UI (Gitizen).
    • this would allow us to write conflicts as well and deal with them.

Copy link

vercel bot commented Sep 15, 2025

@Byron is attempting to deploy a commit to the GitButler Team on Vercel.

A member of the Team first needs to authorize it.

@Byron
Copy link
Collaborator Author

Byron commented Sep 16, 2025

Without v3

Screenshot 2025-09-16 at 13 26 02

With v3.

Screenshot 2025-09-16 at 13 26 16

This also makes the `v3` toggle re-usable, and it can mean many things
as we make progress, changing scope over time.
…it2` can't do what we need it to.

Locally this doesn't reproduce, and also not in a VM both on a shared and VM-local filesystem.
Since `gitoxide` is going to replace it, there is no issue really.
@Byron
Copy link
Collaborator Author

Byron commented Sep 16, 2025

Performance

I have a wonderful accidental test case where master has 1 file, and the workspace has 147000 (the LLVM project).

Screenshot 2025-09-16 at 17 29 47

The profile run shows (with one changed file) that the extra work we do basically is free (and scales with the amount of changes rather than the size of the tree), and then git2 takes 16.2s to checkout 147k files, so about 9k files/s.

Git does it in ~11s (and takes 7.5s to delete everything):

❯ time gco master
Updating files: 100% (147268/147268), done.
Switched to branch 'master'
Your branch is up to date with 'local/master'.
git checkout master  0.30s user 9.13s system 127% cpu 7.414 total

llvm-project ( master) [$] took 7s
❯ time gco gitbutler/workspace
Updating files: 100% (147268/147268), done.
Switched to branch 'gitbutler/workspace'
git checkout gitbutler/workspace  2.78s user 8.07s system 96% cpu 11.281 total

So overall, not bad, and something that gix could do just as fast in single-threaded mode at… 33k files/s!

llvm-project ( gitbutler/workspace) [$] via 🐍 took 2s
❯ time gix -t1 free index checkout-exclusive out
 17:40:42 checkout done 147.3K files in 4.35s (33.8K files/s)
 17:40:42  writing done 0B in 4.35s (0B/s)
 17:40:42 index-checkout Created 147250 empty files (0B)
gix -t1 free index checkout-exclusive out  0.18s user 4.11s system 97% cpu 4.402 total

llvm-project ( gitbutler/workspace) [$?] via 🐍 took 4s
❯ time gix -t2 free index checkout-exclusive out2
 17:41:10 checkout done 147.3K files in 2.82s (52.2K files/s)
 17:41:10  writing done 0B in 2.82s (0B/s)
 17:41:10 index-checkout Created 147250 empty files (0B)
gix -t2 free index checkout-exclusive out2  0.20s user 5.21s system 188% cpu 2.861 total

llvm-project ( gitbutler/workspace) [$?] via 🐍 took 2s
❯ time gix -t3 free index checkout-exclusive out2
Error: Refusing to checkout index into existing directory 'out2' - remove it and try again
gix -t3 free index checkout-exclusive out2  0.00s user 0.00s system 68% cpu 0.010 total

llvm-project ( gitbutler/workspace) [$?] via 🐍
❯ time gix -t3 free index checkout-exclusive out3
 17:41:23 checkout done 147.3K files in 2.22s (66.4K files/s)
 17:41:23  writing done 0B in 2.22s (0B/s)
 17:41:23 index-checkout Created 147250 empty files (0B)
gix -t3 free index checkout-exclusive out3  0.20s user 6.12s system 280% cpu 2.255 total

And it scales to some extent, so it seems feasible the 18.5s now becomes 2.3s!

@Byron Byron marked this pull request as ready for review September 16, 2025 17:45
@Byron Byron enabled auto-merge September 16, 2025 17:45
@Byron Byron disabled auto-merge September 16, 2025 18:48
@Byron Byron enabled auto-merge September 16, 2025 18:48
@Byron Byron merged commit 2683bcb into gitbutlerapp:master Sep 16, 2025
18 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@gitbutler/desktop rust Pull requests that update Rust code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant